/*
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership.  The ASF licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License.  You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 */

package de.appplant.cordova.emailcomposer;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.net.Uri;
import android.util.Base64;
import android.util.Log;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;

import static de.appplant.cordova.emailcomposer.EmailComposer.LOG_TAG;

final class AssetUtil {

    // Path where to put tmp the attachments.
    private static final String ATTACHMENT_FOLDER = "/email_composer";

    // Application context
    private final Context ctx;

    /**
     * Initializes the asset utils.
     *
     * @param ctx The application context.
     */
    AssetUtil (Context ctx) {
        this.ctx = ctx;
    }

    /**
     * Cleans the attachment folder.
     *
     * @param ctx The application context.
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    static void cleanupAttachmentFolder(Context ctx) {
        try {
            String path = ctx.getExternalCacheDir() + ATTACHMENT_FOLDER;
            File dir    = new File(path);

            if (!dir.isDirectory())
                return;

            File[] files = dir.listFiles();

            for (File file : files) { file.delete(); }
        } catch (Exception npe){
            Log.w(LOG_TAG, "Missing external cache dir");
        }
    }

    /**
     * The URI for an attachment path.
     *
     * @param path  The given path to the attachment.
     * @return      The URI pointing to the given path.
     */
    Uri parse (String path) {
        Uri uri;

        if (path.startsWith("res:")) {
            uri = getUriForResourcePath(path);
        } else if (path.startsWith("app://")) {
            uri = getUriForAppInternalPath(path);
        } else if (path.startsWith("file:///")) {
            uri = getUriForAbsolutePath(path);
        } else if (path.startsWith("file://")) {
            uri = getUriForAssetPath(path);
        } else if (path.startsWith("base64:")) {
            uri = getUriForBase64Content(path);
        } else {
            uri = Uri.parse(path);
        }

        return uri;
    }

    /**
     * The URI for a file.
     *
     * @param path  The given absolute path.
     * @return      The URI pointing to the given path.
     */
    private Uri getUriForAbsolutePath (String path) {
        String absPath = path.replaceFirst("file://", "");
        File file      = new File(absPath);

        if (!file.exists()) {
            Log.e(LOG_TAG, "File not found: " + absPath);
        }

        return getUriForFile(ctx, file);
    }

    /**
     * The URI for an asset.
     *
     * @param path  The given asset path.
     * @return      The URI pointing to the given path.
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    private Uri getUriForAssetPath (String path) {
        String resPath  = path.replaceFirst("file:/", "www");
        String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
        File dir        = ctx.getExternalCacheDir();

        if (dir == null) {
            Log.e(LOG_TAG, "Missing external cache dir");
            return Uri.EMPTY;
        }

        String storage  = dir.toString() + ATTACHMENT_FOLDER;
        File file       = new File(storage, fileName);
        new File(storage).mkdir();

        try {
            AssetManager assets  = ctx.getAssets();
            InputStream in       = assets.open(resPath);
            FileOutputStream out = new FileOutputStream(file);
            copyStream(in, out);
        } catch (Exception e) {
            Log.e(LOG_TAG, "File not found: " + resPath);
            e.printStackTrace();
        }

        return getUriForFile(ctx, file);
    }

    /**
     * The URI for an internal file.
     *
     * @param path The given asset path.
     * @return     The URI pointing to the given path.
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    private Uri getUriForAppInternalPath (String path) {
        String resPath  = path.replaceFirst("app:/", "");
        String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
        File dir        = ctx.getExternalCacheDir();

        if (dir == null) {
            Log.e("EmailComposer", "Missing external cache dir");
            return Uri.EMPTY;
        }

        String storage  = dir.toString() + ATTACHMENT_FOLDER;
        File file       = new File(storage, fileName);
        new File(storage).mkdir();

        File filesDir  = ctx.getFilesDir();
        String absPath = filesDir.getAbsolutePath() + "/.." + resPath;

        try {
            InputStream in       = new FileInputStream(absPath);
            FileOutputStream out = new FileOutputStream(file);
            copyStream(in, out);
        } catch (Exception e) {
            Log.e(LOG_TAG, "File not found: " + absPath);
            e.printStackTrace();
        }

        return getUriForFile(ctx, file);
    }

    /**
     * The URI for a resource.
     *
     * @param path  The given relative path.
     * @return      The URI pointing to the given path
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    private Uri getUriForResourcePath (String path) {
        String resPath   = path.replaceFirst("res://", "");
        String fileName  = resPath.substring(resPath.lastIndexOf('/') + 1);
        String resName   = fileName.substring(0, fileName.lastIndexOf('.'));
        String extension = resPath.substring(resPath.lastIndexOf('.'));
        File dir         = ctx.getExternalCacheDir();
        int resId        = getResId(resPath);

        if (dir == null) {
            Log.e(LOG_TAG, "Missing external cache dir");
            return Uri.EMPTY;
        }

        if (resId == 0) {
            Log.e(LOG_TAG, "File not found: " + resPath);
        }

        String storage   = dir.toString() + ATTACHMENT_FOLDER;
        File file        = new File(storage, resName + extension);
        new File(storage).mkdir();

        try {
            Resources res        = ctx.getResources();
            InputStream in       = res.openRawResource(resId);
            FileOutputStream out = new FileOutputStream(file);
            copyStream(in, out);
        } catch (Exception e) {
            Log.e(LOG_TAG, "File not found: " + resPath);
            e.printStackTrace();
        }

        return getUriForFile(ctx, file);
    }

    /**
     * The URI for a base64 encoded content.
     *
     * @param str   The given base64 encoded content.
     * @return      The URI including the given content.
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    private Uri getUriForBase64Content (String str) {
        String resName = str.substring(str.indexOf(":") + 1, str.indexOf("//"));
        String resData = str.substring(str.indexOf("//") + 2);
        File dir       = ctx.getExternalCacheDir();

        if (dir == null) {
            Log.e(LOG_TAG, "Missing external cache dir");
            return Uri.EMPTY;
        }

        String storage = dir.toString() + ATTACHMENT_FOLDER;
        File file      = new File(storage, resName);
        new File(storage).mkdir();

        try {
            byte[] bytes         = Base64.decode(resData, 0);
            InputStream in       = new ByteArrayInputStream(bytes);
            FileOutputStream out = new FileOutputStream(file);
            copyStream(in, out);
        } catch (Exception e) {
            Log.e(LOG_TAG, "Invalid Base64 string");
            e.printStackTrace();
        }

        return getUriForFile(ctx, file);
    }

    /**
     * Get content URI for the specified file.
     *
     * @param ctx The application context.
     * @param file The file to get the URI.
     *
     * @return content://...
     */
    private Uri getUriForFile(Context ctx, File file) {
        String authority = ctx.getPackageName() + ".emailcomposer.provider";

        try {
            return Provider.getUriForFile(ctx, authority, file);
        } catch (Exception e) {
            Log.e(LOG_TAG, "Failed to get uri for file");
            e.printStackTrace();
            return Uri.EMPTY;
        }
    }

    /**
     * Writes an InputStream to an OutputStream
     *
     * @param in    The input stream.
     * @param out   The output stream.
     */
    private void copyStream (InputStream in, FileOutputStream out) {
        byte[] buffer = new byte[1024];
        int read;

        try {
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Returns the resource ID for the given resource path.
     *
     * @return The resource ID for the given resource.
     */
    private int getResId (String resPath) {
        Resources res   = ctx.getResources();
        String pkgName  = ctx.getPackageName();
        String dirName  = "drawable";
        String fileName = resPath;

        if (resPath.contains("/")) {
            dirName  = resPath.substring(0, resPath.lastIndexOf('/'));
            fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
        }

        String resName = fileName.substring(0, fileName.lastIndexOf('.'));
        int resId      = res.getIdentifier(resName, dirName, pkgName);

        if (resId == 0) {
            resId = res.getIdentifier(resName, "mipmap", pkgName);
        }

        if (resId == 0) {
            resId = res.getIdentifier(resName, "drawable", pkgName);
        }

        return resId;
    }

}
