package org.forks.jsonrpc;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebView;

import org.apache.http.cookie.Cookie;

public final class RpcFacade {
    private static final PreferenceMonitor PREFERENCE_MONITOR = new PreferenceMonitor();
    public static final String SERVER_URI_PREFKEY = "ServerUri";
    private static final String DEBUG_PREFKEY = "Debug";
    private static final String TAG = "RpcFacade";
    private static final String REST_URI = "";
    private static String sRestUri = REST_URI;
    private static Context sContext;
    private static String sUsername = "";
    private static String sPasswd;
    private static final String LOGOUT_METHOD = "/MainSystem/MainSystem/Auth/Logout";
    private static final String LOGIN_METHOD = "/MainSystem/MainSystem/Auth/Login";
    static boolean sDebug = true;

    // keep reference
    @SuppressWarnings("unused")
    private static KeepSession sKeepSession;

    private RpcFacade() {
    }

    public static void syncCookie(WebView webView){
        CookieSyncManager.createInstance(webView.getContext());

        CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.setAcceptCookie(true);
        cookieManager.removeAllCookie();
        for(Cookie cookie:HttpJsonRpcClient.sCookieStore.getCookies()){
            if(cookie.getName().equals("EasyAuth")) {
                String cookieString = cookie.getName() + "=" + cookie.getValue() + "; path=" + cookie.getPath();
                cookieManager.setCookie(RpcFacade.getServerUri(), cookieString);
            }
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            cookieManager.flush();
        }

        CookieSyncManager.getInstance().sync();

    }

    public static void init(Context context, String appName) {
        Context appContext = context.getApplicationContext();
        if (sContext == null) {
            Log.v(TAG, "init RpcFacade, appname: " + appName);
            sContext = appContext;
            sKeepSession = new KeepSession();
            HttpJsonRpcClient.sSetClientAppName(appName);
            initPreference();
        } else if (sContext != appContext) {
            Log.v(TAG, "invalid reinit RpcFacade");
            String message = "RpcFacade is inited, can not init again";
            throw new JsonRpcRuntimeException(message);
        }
    }

    public static void setServerUri(String uri){
        SharedPreferences prefs = PreferenceManager
                .getDefaultSharedPreferences(sContext);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString(SERVER_URI_PREFKEY,uri);
        editor.commit();
    }


    public static boolean isInited() {
        return sContext != null;
    }

    private static void initPreference() {
        Log.d(TAG,
                "restore settings from preference, monitor preference change");
        SharedPreferences prefs = PreferenceManager
                .getDefaultSharedPreferences(sContext);
        restoreDebugFromPref(prefs);
        restoreServerUriFromPref(prefs);

        // OnSharedPreferenceChangeListener is a weeked event,
        // we must keep the event listener instance ourself
        prefs.registerOnSharedPreferenceChangeListener(PREFERENCE_MONITOR);
    }

    public static JsonRpcResult rpcCall(String method, Object... args)
            throws JsonRpcException {
        try {
            return doRpcCall(method, args);
        } catch (JsonRpcException e) {
            if (e.getCode() == 401 && isLogedIn()) {
                autoLogin();
                return doRpcCall(method, args);
            }
            throw e;
        }
    }

    public static String getUsername() {
        return sUsername;
    }

    public static void login(String user, String passwd)
            throws JsonRpcException {
        Log.i(TAG, "Start login: " + user);
        callLogin(user, passwd);
        sUsername = user;
        sPasswd = passwd;
    }

    public static void restoreLogined(String username,String password){
        sUsername = username;
        sPasswd = password;
    }

    public static void logout() {
        try {
            if (isLogedIn()) {
                Log.i(TAG, "logout user: " + sUsername);
                sUsername = "";
                doRpcCall(LOGOUT_METHOD);
            }
        } catch (JsonRpcException e) {
        }
    }

    private static void autoLogin() throws JsonRpcException {
        String user = sUsername;
        String pwd = sPasswd;
        if (user.length() > 0) {
            callLogin(user, pwd);
        }
    }

    private static JsonRpcResult doRpcCall(String method, Object... args)
            throws JsonRpcException {
        JsonRpcClient client = new HttpJsonRpcClient(getRestUri(method), method);
        return client.call(args);
    }

    private static void callLogin(String user, String passwd)
            throws JsonRpcException {
        doRpcCall(LOGIN_METHOD, user, passwd);
    }

    private static String getRestUri(String method) {
        StringBuilder sb = new StringBuilder();
        appendServerUri(sb);

        String subSystem = getSystem(method);
        if (!IsMainSystem(subSystem)) {
            appendSubSystem(sb, subSystem);
        }

        sb.append("Rest.aspx");
        return sb.toString();
    }

    private static boolean IsMainSystem(String subSystem) {
        return subSystem.equals("MainSystem");
    }

    private static void appendSubSystem(StringBuilder sb, String subSystem) {
        sb.append("O/");
        sb.append(subSystem);
        sb.append('/');
    }

    private static void appendServerUri(StringBuilder sb) {
        String serverUri = getServerUri();
        if (serverUri.charAt(serverUri.length() - 1) != '/') {
            sb.append('/');
        }
        sb.append(serverUri);
    }

    public static String getServerUri() {
        return sRestUri;
    }

    static String getSystem(String method) {
        int endIdx = method.indexOf('/', 1);
        return method.substring(1, endIdx);
    }

    public static String toFullErtPathByPrefixedErt(String objPath,
            String subMethodPath) {
        String subSystem = getSystem(objPath);
        return "/" + subSystem + subMethodPath;
    }

    public static boolean isLogedIn() {
        return sUsername.length() > 0;
    }

    private static void restoreServerUriFromPref(SharedPreferences pref) {
        sRestUri = pref.getString(SERVER_URI_PREFKEY, REST_URI);
        Log.d(TAG, "get serverUri from preference: " + sRestUri);
    }

    private static void restoreDebugFromPref(SharedPreferences pref) {
        sDebug = pref.getBoolean(DEBUG_PREFKEY, true);
        Log.d(TAG, "get debug mode from preference: " + sDebug);
    }

    private static class KeepSession implements Runnable {
        private static Handler handler;

        public KeepSession() {
            if (handler != null) {
                throw new RuntimeException("More than one instance?");
            }
            handler = new Handler();
            startAgain();
        }

        public void run() {
            if (isLogedIn()) {
                Log.i(TAG, "connecting to server to maintain session");
                try {
                    heartBeat();
                } catch (Exception e) {
                    Log.w(TAG, e.toString());
                }
            } else {
                Log.i(TAG, "no need to connect server, not logged in");
            }
            startAgain();
        }

        private static void heartBeat() throws JsonRpcException {
            // use auto login, because the cell phone maybe just awake after
            // sleep, and server is kick the user out because of not activity
            // in 2 minutes.
            rpcCall("/MainSystem/MainSystem/Auth/Ping");
        }

        private void startAgain() {
            handler.postDelayed(this, 60 * 1000);
        }
    }

    private static final class PreferenceMonitor implements
            OnSharedPreferenceChangeListener {

        public void onSharedPreferenceChanged(
                SharedPreferences sharedPreferences, String key) {
            Log.d(TAG, "preference changed, key: " + key);
            if (key.equals(DEBUG_PREFKEY)) {
                restoreDebugFromPref(sharedPreferences);
            } else if (key.equals(SERVER_URI_PREFKEY)) {
                restoreServerUriFromPref(sharedPreferences);
            }
        }

    }
}
