/*
 * Copyright 2013 serso aka se.solovyev
 *
 * Licensed 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.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Contact details
 *
 * Email: se.solovyev@gmail.com
 * Site:  http://se.solovyev.org
 */

package org.solovyev.android.calculator;

import android.app.Application;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.provider.Settings;
import android.support.annotation.*;
import android.support.v7.view.ContextThemeWrapper;
import android.text.TextUtils;
import android.util.SparseArray;
import org.solovyev.android.Check;
import org.solovyev.android.calculator.about.AboutActivity;
import org.solovyev.android.calculator.functions.FunctionsActivity;
import org.solovyev.android.calculator.history.HistoryActivity;
import org.solovyev.android.calculator.language.Languages;
import org.solovyev.android.calculator.operators.OperatorsActivity;
import org.solovyev.android.calculator.preferences.PreferenceEntry;
import org.solovyev.android.calculator.preferences.PreferencesActivity;
import org.solovyev.android.calculator.variables.VariablesActivity;
import org.solovyev.android.calculator.wizard.WizardActivity;
import org.solovyev.android.prefs.*;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import java.util.EnumMap;
import java.util.Map;

import static org.solovyev.android.prefs.IntegerPreference.DEF_VALUE;

public final class Preferences {

    private static final Preference<Integer> version = IntegerPreference.of("version", 3);

    private Preferences() {
        throw new AssertionError();
    }

    static void init(@Nonnull Application application, @Nonnull SharedPreferences preferences) {
        final int currentVersion = getVersion(preferences);
        if (currentVersion == 0) {
            final SharedPreferences.Editor editor = preferences.edit();
            setInitialDefaultValues(application, preferences, editor);
            editor.apply();
        } else if (currentVersion == 1) {
            final SharedPreferences.Editor editor = preferences.edit();
            if (!Gui.vibrateOnKeypress.isSet(preferences)) {
                Gui.vibrateOnKeypress.putPreference(editor, Deleted.hapticFeedback.getPreference(preferences) > 0);
            }
            migratePreference(preferences, editor, Gui.keepScreenOn, Deleted.preventScreenFromFading);
            migratePreference(preferences, editor, Gui.theme, Deleted.theme);
            migratePreference(preferences, editor, Gui.useBackAsPrevious, Deleted.usePrevAsBack);
            migratePreference(preferences, editor, Gui.showReleaseNotes, Deleted.showReleaseNotes);
            migratePreference(preferences, editor, Gui.rotateScreen, Deleted.autoOrientation);
            final String layout = Deleted.layout.getPreference(preferences);
            if (TextUtils.equals(layout, "main_calculator")) {
                Gui.mode.putPreference(editor, Gui.Mode.engineer);
            } else if (TextUtils.equals(layout, "simple")) {
                Gui.mode.putPreference(editor, Gui.Mode.simple);
            } else if (!Gui.mode.isSet(preferences)) {
                Gui.mode.putDefault(editor);
            }
            version.putDefault(editor);
            editor.apply();
        } else if (currentVersion == 2) {
            final SharedPreferences.Editor editor = preferences.edit();
            Gui.highContrast.tryPutDefault(preferences, editor);
            version.putDefault(editor);
            editor.apply();
        }
    }

    private static int getVersion(@Nonnull SharedPreferences preferences) {
        if (version.isSet(preferences)) {
            return version.getPreference(preferences);
        } else if (Deleted.appVersion.isSet(preferences)) {
            return  1;
        }
        return 0;
    }

    private static <T> void migratePreference(@Nonnull SharedPreferences preferences, @NonNull SharedPreferences.Editor editor, @NonNull Preference<T> to, @NonNull Preference<T> from) {
        if (!to.isSet(preferences)) {
            to.putPreference(editor, from.getPreferenceNoError(preferences));
        }
    }

    private static void setInitialDefaultValues(@Nonnull Application application, @Nonnull SharedPreferences preferences, @Nonnull SharedPreferences.Editor editor) {
        Gui.theme.tryPutDefault(preferences, editor);
        Gui.mode.tryPutDefault(preferences, editor);
        Gui.showReleaseNotes.tryPutDefault(preferences, editor);
        Gui.useBackAsPrevious.tryPutDefault(preferences, editor);
        Gui.rotateScreen.tryPutDefault(preferences, editor);
        Gui.keepScreenOn.tryPutDefault(preferences, editor);
        Gui.language.tryPutDefault(preferences, editor);
        Gui.highContrast.tryPutDefault(preferences, editor);

        Calculations.calculateOnFly.tryPutDefault(preferences, editor);

        Onscreen.showAppIcon.tryPutDefault(preferences, editor);
        Onscreen.theme.tryPutDefault(preferences, editor);

        Widget.theme.tryPutDefault(preferences, editor);
        version.putDefault(editor);

        final ContentResolver cr = application.getContentResolver();
        if (cr != null) {
            final boolean vibrateOnKeyPress = Settings.System.getInt(cr, Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0;
            Gui.vibrateOnKeypress.putPreference(editor, vibrateOnKeyPress);
        }
    }

    public enum SimpleTheme {

        default_theme(0, 0, null),
        metro_blue_theme(R.layout.onscreen_layout, R.layout.widget_layout, Gui.Theme.metro_blue_theme),
        material_theme(R.layout.onscreen_layout_material, R.layout.widget_layout_material, Gui.Theme.material_theme),
        material_light_theme(R.layout.onscreen_layout_material_light, R.layout.widget_layout_material_light, Gui.Theme.material_light_theme, true);

        @LayoutRes
        private final int onscreenLayout;

        @LayoutRes
        private final int widgetLayout;

        @Nullable
        private final Gui.Theme appTheme;

        public final boolean light;

        @Nonnull
        private final Map<Gui.Theme, SimpleTheme> cache = new EnumMap<>(Gui.Theme.class);

        SimpleTheme(int onscreenLayout, int widgetLayout, @Nullable Gui.Theme appTheme) {
            this(onscreenLayout, widgetLayout, appTheme, false);
        }

        SimpleTheme(int onscreenLayout, int widgetLayout, @Nullable Gui.Theme appTheme, boolean light) {
            this.onscreenLayout = onscreenLayout;
            this.widgetLayout = widgetLayout;
            this.appTheme = appTheme;
            this.light = light;
        }

        public int getOnscreenLayout(@Nonnull Gui.Theme appTheme) {
            return resolveThemeFor(appTheme).onscreenLayout;
        }

        public int getWidgetLayout(@Nonnull Gui.Theme appTheme) {
            return resolveThemeFor(appTheme).widgetLayout;
        }

        @Nonnull
        public SimpleTheme resolveThemeFor(@Nonnull Gui.Theme appTheme) {
            if (this == default_theme) {
                SimpleTheme theme = cache.get(appTheme);
                if (theme == null) {
                    theme = lookUpThemeFor(appTheme);
                    cache.put(appTheme, theme);
                }
                return theme;
            }
            return this;
        }

        @Nonnull
        private SimpleTheme lookUpThemeFor(@Nonnull Gui.Theme appTheme) {
            Check.isTrue(this == default_theme);
            // find direct match
            for (SimpleTheme theme : values()) {
                if (theme.appTheme == appTheme) {
                    return theme;
                }
            }

            // for metro themes return metro theme
            if (appTheme == Gui.Theme.metro_green_theme || appTheme == Gui.Theme.metro_purple_theme) {
                return metro_blue_theme;
            }

            // for old themes return dark material
            return material_theme;
        }

        @Nullable
        public Gui.Theme getAppTheme() {
            return appTheme;
        }

        @ColorRes
        public int getDisplayTextColor(boolean error) {
            if (error) {
                return light ? R.color.cpp_text_inverse_error : R.color.cpp_text_error;
            }
            return light ? R.color.cpp_text_inverse : R.color.cpp_text;
        }
    }

    public static class Widget {
        public static final Preference<SimpleTheme> theme = StringPreference.ofEnum("widget.theme", SimpleTheme.default_theme, SimpleTheme.class);

        @Nonnull
        public static SimpleTheme getTheme(@Nonnull SharedPreferences preferences) {
            return theme.getPreferenceNoError(preferences);
        }

    }

    public static class Onscreen {
        public static final Preference<Boolean> showAppIcon = BooleanPreference.of("onscreen_show_app_icon", true);
        public static final Preference<SimpleTheme> theme = StringPreference.ofEnum("onscreen.theme", SimpleTheme.default_theme, SimpleTheme.class);

        @Nonnull
        public static SimpleTheme getTheme(@Nonnull SharedPreferences preferences) {
            return theme.getPreferenceNoError(preferences);
        }
    }

    public static class Calculations {
        public static final Preference<Boolean> calculateOnFly = BooleanPreference.of("calculations_calculate_on_fly", true);
    }

    public static class App {
    }

    public static class Gui {

        public static final Preference<Theme> theme = StringPreference.ofEnum("gui.theme", Theme.material_theme, Theme.class);
        public static final Preference<Mode> mode = StringPreference.ofEnum("gui.mode", Mode.simple, Mode.class);
        public static final Preference<String> language = StringPreference.of("gui.language", Languages.SYSTEM_LANGUAGE_CODE);
        public static final Preference<Boolean> showReleaseNotes = BooleanPreference.of("gui.showReleaseNotes", true);
        public static final Preference<Boolean> useBackAsPrevious = BooleanPreference.of("gui.useBackAsPrevious", false);
        public static final Preference<Boolean> rotateScreen = BooleanPreference.of("gui.rotateScreen", true);
        public static final Preference<Boolean> keepScreenOn = BooleanPreference.of("gui.keepScreenOn", true);
        public static final Preference<Boolean> highContrast = BooleanPreference.of("gui.highContrast", false);
        public static final Preference<Boolean> vibrateOnKeypress = BooleanPreference.of("gui.vibrateOnKeypress", true);

        @Nonnull
        public static Theme getTheme(@Nonnull SharedPreferences preferences) {
            return theme.getPreferenceNoError(preferences);
        }

        @Nonnull
        public static Mode getMode(@Nonnull SharedPreferences preferences) {
            return mode.getPreferenceNoError(preferences);
        }

        public enum Theme implements PreferenceEntry {

            default_theme(R.style.Cpp_Theme_Gray),
            violet_theme(R.style.Cpp_Theme_Violet),
            light_blue_theme(R.style.Cpp_Theme_Blue),
            metro_blue_theme(R.string.cpp_theme_metro_blue, R.style.Cpp_Theme_Metro_Blue, R.style.Cpp_Theme_Metro_Blue_Calculator, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Metro_Blue_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert),
            metro_purple_theme(R.string.p_metro_purple_theme, R.style.Cpp_Theme_Metro_Purple, R.style.Cpp_Theme_Metro_Purple_Calculator, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Metro_Purple_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert),
            metro_green_theme(R.string.p_metro_green_theme, R.style.Cpp_Theme_Metro_Green, R.style.Cpp_Theme_Metro_Green_Calculator, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Metro_Green_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert),
            material_theme(R.string.cpp_theme_dark, R.style.Cpp_Theme_Material, R.style.Cpp_Theme_Material_Calculator),
            material_black_theme(R.string.cpp_theme_black, R.style.Cpp_Theme_Material_Black, R.style.Cpp_Theme_Material_Black_Calculator) {
                @NonNull
                @Override
                public String getName(@NonNull Context context) {
                    return context.getString(name, material_theme.getName(context));
                }
            },
            material_light_theme(R.string.cpp_theme_light, R.style.Cpp_Theme_Material_Light, R.style.Cpp_Theme_Material_Light_Calculator, R.style.Cpp_Theme_Wizard_Light, R.style.Cpp_Theme_Material_Light_Dialog, R.style.Cpp_Theme_Material_Light_Dialog_Alert);

            private static final SparseArray<TextColor> textColors = new SparseArray<>();

            @StringRes
            protected final int name;
            @StyleRes
            public final int theme;
            @StyleRes
            public final int calculatorTheme;
            @StyleRes
            public final int wizardTheme;
            @StyleRes
            public final int dialogTheme;
            @StyleRes
            public final int alertDialogTheme;
            public final boolean light;

            Theme(@StyleRes int theme) {
                this(R.string.cpp_theme_dark, theme, theme);
            }

            Theme(@StringRes int name, @StyleRes int theme, @StyleRes int calculatorTheme) {
                this(name, theme, calculatorTheme, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Material_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert);
            }

            Theme(@StringRes int name, @StyleRes int theme, @StyleRes int calculatorTheme, @StyleRes int wizardTheme, @StyleRes int dialogTheme, @StyleRes int alertDialogTheme) {
                this.name = name;
                this.theme = theme;
                this.calculatorTheme = calculatorTheme;
                this.wizardTheme = wizardTheme;
                this.dialogTheme = dialogTheme;
                this.alertDialogTheme = alertDialogTheme;
                this.light = theme == R.style.Cpp_Theme_Material_Light;
            }

            public int getThemeFor(@Nonnull Context context) {
                if (context instanceof CalculatorActivity) {
                    return calculatorTheme;
                }
                if (context instanceof WizardActivity) {
                    return wizardTheme;
                }
                if (context instanceof FunctionsActivity.Dialog) {
                    return dialogTheme;
                }
                if (context instanceof PreferencesActivity.Dialog) {
                    return dialogTheme;
                }
                if (context instanceof VariablesActivity.Dialog) {
                    return dialogTheme;
                }
                if (context instanceof OperatorsActivity.Dialog) {
                    return dialogTheme;
                }
                if (context instanceof HistoryActivity.Dialog) {
                    return dialogTheme;
                }
                if (context instanceof AboutActivity.Dialog) {
                    return dialogTheme;
                }
                return theme;
            }

            @Nonnull
            public TextColor getTextColorFor(@Nonnull Context context) {
                final int themeId = getThemeFor(context);
                TextColor textColor = textColors.get(themeId);
                if (textColor == null) {
                    final ContextThemeWrapper themeContext = new ContextThemeWrapper(context, themeId);
                    final TypedArray a = themeContext.obtainStyledAttributes(themeId, new int[]{R.attr.cpp_text_color, R.attr.cpp_text_color_error});
                    final int normal = a.getColor(0, Color.BLACK);
                    final int error = a.getColor(1, Color.WHITE);
                    a.recycle();
                    textColor = new TextColor(normal, error);
                    textColors.append(themeId, textColor);
                }
                return textColor;
            }

            @Override
            @NonNull
            public String getName(@NonNull Context context) {
                return context.getString(name);
            }

            @NonNull
            @Override
            public CharSequence getId() {
                return name();
            }
        }

        public enum Mode {
            engineer(R.string.cpp_mode_engineer),
            simple(R.string.cpp_mode_simple);

            @StringRes
            public final int name;

            Mode(@StringRes int name) {
                this.name = name;
            }
        }

        public static final class TextColor {
            public final int normal;
            public final int error;

            TextColor(int normal, int error) {
                this.normal = normal;
                this.error = error;
            }
        }
    }

    @SuppressWarnings("unused")
    static class Deleted {
        static final Preference<Integer> appVersion = IntegerPreference.of("application.version", DEF_VALUE);
        static final Preference<Boolean> feedbackWindowShown = BooleanPreference.of("feedback_window_shown", false);
        static final Preference<Integer> appOpenedCounter = IntegerPreference.of("app_opened_counter", 0);
        static final Preference<Long> hapticFeedback = NumberToStringPreference.of("hapticFeedback", 60L, Long.class);
        static final Preference<Boolean> colorDisplay = BooleanPreference.of("org.solovyev.android.calculator.CalculatorModel_color_display", true);
        static final Preference<Boolean> preventScreenFromFading = BooleanPreference.of("preventScreenFromFading", true);
        static final Preference<Gui.Theme> theme = StringPreference.ofEnum("org.solovyev.android.calculator.CalculatorActivity_calc_theme", Gui.Theme.material_theme, Gui.Theme.class);
        static final StringPreference<String> layout = StringPreference.of("org.solovyev.android.calculator.CalculatorActivity_calc_layout", "simple");
        static final Preference<Boolean> showReleaseNotes = BooleanPreference.of("org.solovyev.android.calculator.CalculatorActivity_show_release_notes", true);
        static final Preference<Boolean> usePrevAsBack = BooleanPreference.of("org.solovyev.android.calculator.CalculatorActivity_use_back_button_as_prev", false);
        static final Preference<Boolean> showEqualsButton = BooleanPreference.of("showEqualsButton", true);
        static final Preference<Boolean> autoOrientation = BooleanPreference.of("autoOrientation", true);
        static final Preference<Boolean> startOnBoot = BooleanPreference.of("onscreen_start_on_boot", false);
        static final Preference<Boolean> plotImag = BooleanPreference.of("graph_plot_imag", false);
    }
}
