package cloud.hmml.mmw;

import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;

import net.arnx.jsonic.JSON;

import org.apache.commons.io.FileUtils;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;


public class Theme {
    public static final int[] BUILTIN_THEMES = {1, 2, 3, 4};
    public static final int TYPE_BUILTIN = 1;
    public static final int TYPE_MIKU_WEATHER = 2;
    public static final int TYPE_IN_STORAGE = 3;
    public static final String THEMES_DIR = "themes";

    public static final String W_CLEAR_D = "d";
    public static final String W_CLEAR_N = "n";
    public static final String W_THUNDER = "t";
    public static final String W_SNOW    = "s";
    public static final String W_RAIN    = "r";
    public static final String W_CLOUD   = "c";

    protected int type = TYPE_BUILTIN;
    public int schema_version = 1;
    public int id = 0;
    public String name;
    public String author;
    public String url;
    public String day_frame;
    public String short_desc;
    public int revision = 1;
    public WeakReference<Context> context;

    public static final Map<String, String> W_FNAME_MAP = new HashMap<String, String>();
    static {
        Map<String, String> fname_map = new HashMap<String, String>();
        fname_map.put(W_CLEAR_D, "ClearD");
        fname_map.put(W_CLEAR_N, "ClearN");
        fname_map.put(W_RAIN,    "Rain");
        fname_map.put(W_THUNDER, "Thunder");
        fname_map.put(W_SNOW,    "Snow");
        fname_map.put(W_CLOUD,   "Cloud");

        Map<String, String> m = W_FNAME_MAP;
        for (String w1_key: fname_map.keySet()) {
            m.put(w1_key, "wi-" + fname_map.get(w1_key) + ".webp");
            for (String w2_key: fname_map.keySet()) {
                m.put(w1_key + w2_key, "wi-" + fname_map.get(w1_key) + "_" + fname_map.get(w2_key) + ".webp");
            }
        }
    }

    public static final Map<String, String> MW_RES_MAP = new HashMap<String, String>();
    static {
        Map<String, String> m = MW_RES_MAP;
        m.put(W_CLEAR_D, "hare");
        m.put(W_CLEAR_N, "hare_yoru");
        m.put(W_THUNDER, "kaminari");
        m.put(W_SNOW, "yuki");
        m.put(W_RAIN, "ame");
        m.put(W_CLOUD, "kumori");
        m.put(W_CLEAR_D + W_CLOUD, "hare_kumori");
        m.put(W_CLEAR_D + W_RAIN, "hare_ame");
        m.put(W_CLEAR_D + W_SNOW, "hare_yuki");
        m.put(W_CLEAR_D + W_THUNDER, "hare_kaminari");
        m.put(W_CLEAR_N + W_CLOUD, "hare_kumori");
        m.put(W_CLEAR_N + W_RAIN, "hare_ame");
        m.put(W_CLEAR_N + W_SNOW, "hare_yuki");
        m.put(W_CLEAR_N + W_THUNDER, "hare_kaminari");
        m.put(W_CLOUD + W_RAIN, "kumori_ame");
        m.put(W_CLOUD + W_CLEAR_D, "kumori_hare");
        m.put(W_CLOUD + W_CLEAR_N, "kumori_hare_yoru");
        m.put(W_CLOUD + W_THUNDER, "kumori_kaminari");
        m.put(W_CLOUD + W_SNOW, "kumori_yuki");
        m.put(W_CLOUD + W_SNOW + W_THUNDER, "kumori_yuki_kaminari");
        m.put(W_SNOW + W_RAIN, "yuki_ame");
        m.put(W_SNOW + W_CLEAR_N, "yuki_hare_yoru");
        m.put(W_SNOW + W_CLEAR_D, "yuki_hare");
        m.put(W_SNOW + W_THUNDER, "yuki_kaminari");
        m.put(W_SNOW + W_CLOUD, "yuki_kumori");
        m.put(W_RAIN + W_CLEAR_N, "ame_hare_yoru");
        m.put(W_RAIN + W_CLEAR_D, "ame_hare");
        m.put(W_RAIN + W_CLOUD, "ame_kumori");
        m.put(W_RAIN + W_THUNDER, "ame_kaminari");
        m.put(W_RAIN + W_SNOW, "ame_yuki");
    };

    static class LoadFailed extends RuntimeException {
        public LoadFailed(String s) {
            super(s);
        }
    }

    public int getType() {
        return type;
    }

    public int getId() { return id; }

    public Context getContext() {
        return context.get();
    }

    public String getAuthor() {return author;}
    public String getShortDesc() {return short_desc;}
    public String getName() {return name;}
    public Uri getUri() {
        if (this.url == null || this.url.length() < 1)
            return null;
        try {
            return Uri.parse(this.url);
        } catch (RuntimeException e) {
            Log.e("them", "Invalid URL in theme "+ type + "/" + id + ": " + this.url);
            return null;
        }
    }

    public static Theme createByContentUrl(Context context, String url) {
        return create(context, Uri.parse(url));
    }

    public void delete() throws IOException {
        if (type != TYPE_IN_STORAGE)
            throw new RuntimeException("Cannot delete theme except in storage.");
        FileUtils.deleteDirectory(getStorageThemeDir(getContext(), getId()));
    }

    public static Theme create(Context context, Uri uri) {
        try {
            Theme theme = createThemeInstance(context, context.getContentResolver().openInputStream(uri));
            if (theme == null)
                return null;
            if (!extractTheme(context, theme.id, context.getContentResolver().openInputStream(uri)))
                return null;
            return theme;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
    public static Theme create(Context context, File file) {
        try {
            Theme theme = createThemeInstance(context, new FileInputStream(file));
            if (theme == null)
                return null;
            if (!extractTheme(context, theme.id, new FileInputStream(file)))
                return null;
            return theme;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static Theme createThemeInstance(Context context, InputStream is) {
        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
        ZipEntry ze;
        Theme theme = null;

        // scan theme.json
        try {
            while((ze = zis.getNextEntry()) != null) {
                if (!ze.getName().equals("theme.json"))
                    continue;
                theme = JSON.decode(zis, Theme.class);
                if (theme.id == 0) {
                    Log.e("theme-creation", "theme.json has invalid ID, Skip!");
                    return null;
                }

                theme.type = TYPE_IN_STORAGE;
                break;
            }
            zis.close();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        if (theme == null) {
            Log.e("theme-creation", "theme.json is not found in the archive. Skip!");
            return null;
        }
        return theme;
    }

    private static boolean extractTheme(Context context, int id, InputStream is) {
        if (id == 0)
            return false;
        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
        ZipEntry ze;

        File themedir = getStorageThemeDir(context, id);
        if (themedir == null) {
            Log.e("theme-creation", "Cannot get extract directy. You may need to insert SD card. Abort!");
            return false;
        }

        // Now theme json is OK, extract files...
        Log.i("theme-creation", "Extracting theme to " + themedir.getPath());
        try {
            while((ze = zis.getNextEntry()) != null) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[16 * 1024];
                int count;

                String filename = ze.getName();
                File outfile = new File(themedir, filename);
                (new File(outfile.getParent())).mkdirs();
                FileOutputStream fout = new FileOutputStream(outfile);

                while((count = zis.read(buffer)) != -1) {
                    baos.write(buffer, 0, count);
                    byte[] bytes = baos.toByteArray();
                    fout.write(bytes);
                    baos.reset();
                }

                fout.close();
                zis.closeEntry();
            }
            zis.close();

        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        Log.d("theme-creation", "Theme extract has been completed!");
        return true;
    }

    public static List<Theme> getAll(Context context) {
        List<Theme> list = new ArrayList<Theme>();

        // Miku Weather
        if (hasMikuWeather(context)) {
            list.add(getMikuWeatherTheme(context));
        }

        // On storage
        File themesDir = getStorageThemesDir(context);
        if (themesDir != null && themesDir.isDirectory()) {
            for (String tid: themesDir.list()) {
                File theme_def = new File(themesDir, tid + "/theme.json");
                if (!theme_def.exists())
                    continue;
                try {
                    list.add(load(context, TYPE_IN_STORAGE, Integer.parseInt(tid)));
                } catch (Exception e) {
                    Log.e("theme", "Failed to load theme from " + theme_def.getPath() + ": " + e.getMessage());
                }
            }
        }

        // builtin
        AssetManager as = context.getAssets();
        for (int id: BUILTIN_THEMES) {
            try {
                Log.d("theme", "Loading builtin "+id);
                list.add(load(context, TYPE_BUILTIN, id));
            } catch (Exception e) {
                Log.e("theme", "Failed to load builtin theme " + id +": " + e.getMessage());
                e.printStackTrace();
            }
        }

        return list;
    }

    public static boolean exists(Context context, int type, int id) {
        if (type == TYPE_MIKU_WEATHER) {
            return hasMikuWeather(context);
        } else if (type == TYPE_BUILTIN) {
            String asset_path = THEMES_DIR + "/" + id + "/theme.json";
            try {
                context.getAssets().open(asset_path).close();
            } catch (IOException e) {
                return false;
            }
            return true;
        } else if (type == TYPE_IN_STORAGE) {
            return (new File(getStorageThemeDir(context, id), "theme.json")).exists();
        } else {
            return false;
        }
    }

    public static File getStorageThemesDir(Context context) {
        File baseDir = context.getExternalFilesDir(null);
        if (baseDir == null)
            return null;
        return new File(baseDir, THEMES_DIR);
    }

    public static File getStorageThemeDir(Context context, int id) {
        File base = getStorageThemesDir(context);
        if (base == null)
            return null;
        return new File(base, Integer.toString(id));
    }

    public static boolean hasMikuWeather(Context context) {
        try {
            context.createPackageContext("com.mikuxperia.mikuweatherwidget", Context.CONTEXT_RESTRICTED);
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    public static Theme getMikuWeatherTheme(Context context) {
        if (!hasMikuWeather(context))
            return null;
        Theme theme = new Theme();
        theme.context = new WeakReference<Context>(context.getApplicationContext());
        theme.type = TYPE_MIKU_WEATHER;
        theme.day_frame = "bg_weather";
        theme.name = "Miku Weather";
        theme.author = "Xperia™ feat. HATSUNE MIKU";
        theme.url = "http://www.sonymobile.co.jp/special/voices39/";
        theme.id = 1;
        return theme;
    }

    public static Theme load(Context context, int type, int themeId) {
        InputStream istream = null;
        Log.d("theme", "Loading theme type="+type+", id="+themeId);
        switch (type) {
            case TYPE_MIKU_WEATHER:
                return getMikuWeatherTheme(context);
            case TYPE_BUILTIN:
                try {
                    istream = context.getAssets().open(THEMES_DIR + "/" + themeId + "/theme.json");
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
                break;
            case TYPE_IN_STORAGE:
                File theme_def = new File(getStorageThemeDir(context, themeId) + "/theme.json");
                if (!theme_def.exists())
                    throw new LoadFailed("Theme def file not found:" + theme_def.getPath());

                try {
                    istream =  new FileInputStream(theme_def);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    return null;
                }
                break;
        }

        Theme theme = null;
        try {
            theme = JSON.decode(istream, Theme.class);
        } catch (IOException e) {
            Log.e("theme", "Failed to decode theme definition json: " + e.getMessage());
            e.printStackTrace();
            return null;
        }
        theme.type = type;
        theme.context = new WeakReference<Context>(context.getApplicationContext());
        return theme;
    }

    public Theme() {
        // null constructor for JSON
    }

    public String getIconURL(String iconIdent) {
        if (type == TYPE_MIKU_WEATHER) {
            String icon_name = MW_RES_MAP.get(iconIdent);
            if (icon_name == null)
                return null;
            return "mikuweather-icon:tenki_"+icon_name;
        } else if (type == TYPE_BUILTIN) {
            return "assets://" + THEMES_DIR + "/" + id + "/" + W_FNAME_MAP.get(iconIdent);
        } else if (type == TYPE_IN_STORAGE) {
            File baseDir = getContext().getExternalFilesDir(null);
            if (baseDir == null) {
                Log.d("theme", "Directory not found: " + baseDir.getPath());
                return null;
            }

            File theme_icon = new File(getStorageThemeDir(getContext(), id), W_FNAME_MAP.get(iconIdent));
            if (!theme_icon.exists()) {
                Log.d("theme", "Theme icon file not found:" + theme_icon.getPath());
                return null;
            }
            return "file://" + theme_icon.getPath();
        } else {
            Log.e("theme", "[BUG] SHOULD NOT BE REACHED: Unknown theme Type!");
            throw new RuntimeException("[BUG] Unknown theme type");
        }
    }

    public Bitmap getIconBitmap(String iconIdent) {
        if (type == TYPE_MIKU_WEATHER) {
            String icon_name = MW_RES_MAP.get(iconIdent);
            if (icon_name == null)
                return null;
            return getMikuWeatherBitmap("tenki_"+icon_name);
        } else if (type == TYPE_BUILTIN) {
            InputStream istream;
            String asset_path = THEMES_DIR + "/" + id + "/" + W_FNAME_MAP.get(iconIdent);
            try {
                istream = getContext().getAssets().open(asset_path);
            } catch (IOException e) {
                Log.e("theme", "Failed to load asset " + asset_path + ": " + e.getMessage());
                e.printStackTrace();
                return null;
            }
            return  BitmapFactory.decodeStream(istream);
        } else if (type == TYPE_IN_STORAGE) {
            File baseDir = getContext().getExternalFilesDir(null);
            if (baseDir == null) {
                Log.d("theme", "Directory not found: " + baseDir.getPath());
                return null;
            }

            File theme_icon = new File(getStorageThemeDir(getContext(), id), W_FNAME_MAP.get(iconIdent));
            if (!theme_icon.exists()) {
                Log.d("theme", "Theme icon file not found:" + theme_icon.getPath());
                return null;
            }
            return BitmapFactory.decodeFile(theme_icon.getPath());
        } else {
            Log.e("theme", "[BUG] SHOULD NOT BE REACHED: Unknown theme Type!");
            throw new RuntimeException("[BUG] Unknown theme type");
        }
    }

    private Bitmap getMikuWeatherBitmap(String resname) {
        if (resname == null)
            return null;
        try {
            Context mx_context = context.get().createPackageContext("com.mikuxperia.mikuweatherwidget", Context.CONTEXT_RESTRICTED);
            Resources resources = mx_context.getResources();
            int resourceId = resources.getIdentifier(resname, "drawable", "com.mikuxperia.mikuweatherwidget");
            return BitmapFactory.decodeResource(resources, resourceId);
        } catch (PackageManager.NameNotFoundException e) {
            Log.e("theme", "Failed to get Miku Weather resource: " + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }
}
