
package com.ichi2.anki;

/****************************************************************************************
 * Copyright (c) 2009 Casey Link <unnamedrambler@gmail.com>                             *
 * Copyright (c) 2012 Norbert Nagold <norbert.nagold@gmail.com>                         *
 *                                                                                      *
 * This program is free software; you can redistribute it and/or modify it under        *
 * the terms of the GNU General Public License as published by the Free Software        *
 * Foundation; either version 3 of the License, or (at your option) any later           *
 * version.                                                                             *
 *                                                                                      *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
 *                                                                                      *
 * You should have received a copy of the GNU General Public License along with         *
 * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
 ****************************************************************************************/

import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.view.KeyEvent;

import com.ichi2.anim.ActivityTransitionAnimation;
import com.ichi2.anki.receiver.SdCardReceiver;
import com.ichi2.compat.CompatHelper;
import com.ichi2.libanki.Collection;
import com.ichi2.preferences.StepsPreference;
import com.ichi2.themes.Themes;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import timber.log.Timber;

/**
 * Preferences for the current deck.
 */
public class FilteredDeckOptions extends PreferenceActivity implements OnSharedPreferenceChangeListener {

    private JSONObject mDeck;
    private Collection mCol;
    private boolean mAllowCommit = true;

    private BroadcastReceiver mUnmountReceiver = null;

    // TODO: not anymore used in libanki?
    private String[] dynExamples = new String[] { null,
            "{'search'=\"is:new\", 'resched'=False, 'steps'=\"1\", 'order'=5}",
            "{'search'=\"added:1\", 'resched'=False, 'steps'=\"1\", 'order'=5}",
            "{'search'=\"rated:1:1\", 'order'=4}",
            "{'search'=\"prop:due<=2\", 'order'=6}",
            "{'search'=\"is:due tag:TAG\", 'order'=6}",
            "{'search'=\"is:due\", 'order'=3}",
            "{'search'=\"\", 'steps'=\"1 10 20\", 'order'=0}" };

    public class DeckPreferenceHack implements SharedPreferences {

        private Map<String, String> mValues = new HashMap<String, String>();
        private Map<String, String> mSummaries = new HashMap<String, String>();


        public DeckPreferenceHack() {
            this.cacheValues();
        }


        protected void cacheValues() {
            Timber.d("cacheValues()");

            try {
                JSONArray ar = mDeck.getJSONArray("terms").getJSONArray(0);
                mValues.put("search", ar.getString(0));
                mValues.put("limit", ar.getString(1));
                mValues.put("order", ar.getString(2));
                try {
                    mValues.put("steps", StepsPreference.convertFromJSON(mDeck.getJSONArray("delays")));
                    mValues.put("stepsOn", Boolean.toString(true));
                } catch (JSONException e) {
                    mValues.put("steps", "1 10");
                    mValues.put("stepsOn", Boolean.toString(false));
                }
                mValues.put("resched", Boolean.toString(mDeck.getBoolean("resched")));
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        }

        public class Editor implements SharedPreferences.Editor {

            private ContentValues mUpdate = new ContentValues();


            @Override
            public SharedPreferences.Editor clear() {
                Timber.d("clear()");
                mUpdate = new ContentValues();
                return this;
            }


            @Override
            public boolean commit() {
                Timber.d("commit() changes back to database");

                try {
                    for (Entry<String, Object> entry : mUpdate.valueSet()) {
                        Timber.i("Change value for key '" + entry.getKey() + "': " + entry.getValue());
                        if (entry.getKey().equals("search")) {
                            JSONArray ar = mDeck.getJSONArray("terms");
                            ar.getJSONArray(0).put(0, (String) entry.getValue());
                            mDeck.put("terms", ar);
                        } else if (entry.getKey().equals("limit")) {
                            JSONArray ar = mDeck.getJSONArray("terms");
                            ar.getJSONArray(0).put(1, (Integer) entry.getValue());
                            mDeck.put("terms", ar);
                        } else if (entry.getKey().equals("order")) {
                            JSONArray ar = mDeck.getJSONArray("terms");
                            ar.getJSONArray(0).put(2, Integer.parseInt((String) entry.getValue()));
                            mDeck.put("terms", ar);
                        } else if (entry.getKey().equals("resched")) {
                            mDeck.put("resched", (Boolean) entry.getValue());
                        } else if (entry.getKey().equals("stepsOn")) {
                            boolean on = (Boolean) entry.getValue();
                            if (on) {
                                mDeck.put("delays", StepsPreference.convertToJSON((String) mValues.get("steps")));
                            } else {
                                mValues.put("steps", "1 10");
                                mDeck.put("delays", new JSONArray());
                            }
                        } else if (entry.getKey().equals("steps")) {
                            mDeck.put("delays", StepsPreference.convertToJSON((String) entry.getValue()));
                        } else if (entry.getKey().equals("preset")) {
                            int i = Integer.parseInt((String) entry.getValue());
                            if (i > 0) {
                                JSONObject presetValues = new JSONObject(dynExamples[i]);
                                JSONArray ar = presetValues.names();
                                for (int j = 0; j < ar.length(); j++) {
                                    String name = ar.getString(j);
                                    if (name.equals("steps")) {
                                        mUpdate.put("stepsOn", true);
                                    }
                                    if (name.equals("resched")) {
                                        mUpdate.put(name, presetValues.getBoolean(name));
                                        mValues.put(name, Boolean.toString(presetValues.getBoolean(name)));
                                    } else {
                                        mUpdate.put(name, presetValues.getString(name));
                                        mValues.put(name, presetValues.getString(name));
                                    }
                                }
                                mUpdate.put("preset", "0");
                                commit();
                            }
                        }
                    }
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }

                // save deck
                try {
                    mCol.getDecks().save(mDeck);
                } catch (RuntimeException e) {
                    Timber.e(e, "RuntimeException on saving deck");
                    AnkiDroidApp.sendExceptionReport(e, "FilteredDeckOptionsSaveDeck");
                    setResult(DeckPicker.RESULT_DB_ERROR);
                    finish();
                }

                // make sure we refresh the parent cached values
                cacheValues();
                updateSummaries();

                // and update any listeners
                for (OnSharedPreferenceChangeListener listener : listeners) {
                    listener.onSharedPreferenceChanged(DeckPreferenceHack.this, null);
                }

                return true;
            }


            @Override
            public SharedPreferences.Editor putBoolean(String key, boolean value) {
                mUpdate.put(key, value);
                return this;
            }


            @Override
            public SharedPreferences.Editor putFloat(String key, float value) {
                mUpdate.put(key, value);
                return this;
            }


            @Override
            public SharedPreferences.Editor putInt(String key, int value) {
                mUpdate.put(key, value);
                return this;
            }


            @Override
            public SharedPreferences.Editor putLong(String key, long value) {
                mUpdate.put(key, value);
                return this;
            }


            @Override
            public SharedPreferences.Editor putString(String key, String value) {
                mUpdate.put(key, value);
                return this;
            }


            @Override
            public SharedPreferences.Editor remove(String key) {
                Timber.d("Editor.remove(key=%s)", key);
                mUpdate.remove(key);
                return this;
            }


            public void apply() {
                if (mAllowCommit) {
                    commit();
                }
            }


            // @Override On Android 1.5 this is not Override
            public android.content.SharedPreferences.Editor putStringSet(String arg0, Set<String> arg1) {
                // TODO Auto-generated method stub
                return null;
            }

        }


        @Override
        public boolean contains(String key) {
            return mValues.containsKey(key);
        }


        @Override
        public Editor edit() {
            return new Editor();
        }


        @Override
        public Map<String, ?> getAll() {
            return mValues;
        }


        @Override
        public boolean getBoolean(String key, boolean defValue) {
            return Boolean.valueOf(this.getString(key, Boolean.toString(defValue)));
        }


        @Override
        public float getFloat(String key, float defValue) {
            return Float.valueOf(this.getString(key, Float.toString(defValue)));
        }


        @Override
        public int getInt(String key, int defValue) {
            return Integer.valueOf(this.getString(key, Integer.toString(defValue)));
        }


        @Override
        public long getLong(String key, long defValue) {
            return Long.valueOf(this.getString(key, Long.toString(defValue)));
        }


        @Override
        public String getString(String key, String defValue) {
            Timber.d("getString(key=%s, defValue=%s)", key, defValue);
            if (!mValues.containsKey(key)) {
                return defValue;
            }
            return mValues.get(key);
        }

        public List<OnSharedPreferenceChangeListener> listeners = new LinkedList<OnSharedPreferenceChangeListener>();


        @Override
        public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
            listeners.add(listener);
        }


        @Override
        public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
            listeners.remove(listener);
        }


        // @Override On Android 1.5 this is not Override
        public Set<String> getStringSet(String arg0, Set<String> arg1) {
            // TODO Auto-generated method stub
            return null;
        }

    }

    private DeckPreferenceHack mPref;


    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        Timber.d("getSharedPreferences(name=%s)", name);
        return mPref;
    }


    @Override
    protected void onCreate(Bundle icicle) {
        // Workaround for bug 4611: http://code.google.com/p/android/issues/detail?id=4611
        // This screen doesn't suffer from this bug really as it doesn't have nested
        // PreferenceScreens, but we have change the background for consistency with
        // DeckOptions background.
        if (!CompatHelper.isHoneycomb()) {
            Themes.applyTheme(this, Themes.THEME_ANDROID_DARK);
        }
        super.onCreate(icicle);

        mCol = CollectionHelper.getInstance().getCol(this);
        if (mCol == null) {
            finish();
            return;
        }
        Bundle extras = getIntent().getExtras();
        if (extras != null && extras.containsKey("did")) {
            mDeck = mCol.getDecks().get(extras.getLong("did"));
        } else {
            mDeck = mCol.getDecks().current();
        }

        registerExternalStorageListener();

        try {
            if (mCol == null || mDeck.getInt("dyn") != 1) {
                Timber.w("No Collection loaded or deck is not a dyn deck");
                finish();
                return;
            } else {
                mPref = new DeckPreferenceHack();
                mPref.registerOnSharedPreferenceChangeListener(this);

                this.addPreferencesFromResource(R.xml.cram_deck_options);
                this.buildLists();
                this.updateSummaries();
            }
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        
        // Set the activity title to include the name of the deck
        String title = getResources().getString(R.string.deckpreferences_title);
        if (title.contains("XXX")) {
            try {
                title = title.replace("XXX", mDeck.getString("name"));
            } catch (JSONException e) {
                title = title.replace("XXX", "???");
            }
        }
        this.setTitle(title);
    }


    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        // update values on changed preference
        this.updateSummaries();
    }


    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            Timber.i("DeckOptions - onBackPressed()");
            finish();
            ActivityTransitionAnimation.slide(this, ActivityTransitionAnimation.FADE);
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mUnmountReceiver != null) {
            unregisterReceiver(mUnmountReceiver);
        }
    }


    protected void updateSummaries() {
        mAllowCommit = false;
        // for all text preferences, set summary as current database value
        for (String key : mPref.mValues.keySet()) {
            Preference pref = this.findPreference(key);
            String value = null;
            if (pref == null) {
                continue;
            } else if (pref instanceof CheckBoxPreference) {
                continue;
            } else if (pref instanceof ListPreference) {
                ListPreference lp = (ListPreference) pref;
                CharSequence entry = lp.getEntry();
                if (entry != null) {
                    value = entry.toString();
                } else {
                    value = "";
                }
            } else {
                value = this.mPref.getString(key, "");
            }
            // update value for EditTexts
            if (pref instanceof EditTextPreference) {
                ((EditTextPreference) pref).setText(value);
            }
            // update summary
            if (!mPref.mSummaries.containsKey(key)) {
                CharSequence s = pref.getSummary();
                mPref.mSummaries.put(key, s != null ? pref.getSummary().toString() : null);
            }
            String summ = mPref.mSummaries.get(key);
            if (summ != null && summ.contains("XXX")) {
                pref.setSummary(summ.replace("XXX", value));
            } else {
                pref.setSummary(value);
            }
        }
        mAllowCommit = true;
    }


    protected void buildLists() {
        ListPreference newOrderPref = (ListPreference) findPreference("order");
        newOrderPref.setEntries(R.array.cram_deck_conf_order_labels);
        newOrderPref.setEntryValues(R.array.cram_deck_conf_order_values);
        newOrderPref.setValue(mPref.getString("order", "0"));
    }

    /**
     * finish when sd card is ejected
     */
    private void registerExternalStorageListener() {
        if (mUnmountReceiver == null) {
            mUnmountReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (intent.getAction().equals(SdCardReceiver.MEDIA_EJECT)) {
                        finish();
                    }
                }
            };
            IntentFilter iFilter = new IntentFilter();
            iFilter.addAction(SdCardReceiver.MEDIA_EJECT);
            registerReceiver(mUnmountReceiver, iFilter);
        }
    }

}
