package org.edublogs.android;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.preference.PreferenceManager;
import android.util.Log;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
import com.google.android.gcm.GCMRegistrar;
import com.wordpress.rest.Oauth;
import com.wordpress.rest.RestRequest;

import org.edublogs.android.lockmanager.AppLockManager;
import org.edublogs.android.models.Blog;
import org.edublogs.android.models.Comment;
import org.edublogs.android.models.Post;
import org.edublogs.android.util.BitmapLruCache;
import org.edublogs.android.util.WPRestClient;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlrpc.android.WPComXMLRPCApi;


public class WordPress extends Application {

    public static final String NOTES_CACHE="notifications.json";
    public static final String ACCESS_TOKEN_PREFERENCE="wp_pref_wpcom_access_token";
    public static final String WPCOM_USERNAME_PREFERENCE="wp_pref_wpcom_username";
    public static final String WPCOM_PASSWORD_PREFERENCE="wp_pref_wpcom_password";
    private static final String APP_ID_PROPERTY="oauth.app_id";
    private static final String APP_SECRET_PROPERTY="oauth.app_secret";
    private static final String APP_REDIRECT_PROPERTY="oauth.redirect_uri";

    public static String versionName;
    public static Blog currentBlog;
    public static Comment currentComment;
    public static Post currentPost;
    public static WordPressDB wpDB;
    public static OnPostUploadedListener onPostUploadedListener = null;
    public static boolean postsShouldRefresh;
    public static boolean shouldRestoreSelectedActivity;
    public static WPRestClient restClient;
    public static RequestQueue requestQueue;
    public static ImageLoader imageLoader;
    public static JSONObject latestNotes;

    public static final String TAG="WordPress";
    
    @Override
    public void onCreate() {
        versionName = getVersionName();
        wpDB = new WordPressDB(this);
        
        // Volley networking setup
        requestQueue = Volley.newRequestQueue(this);
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // Use a small slice of available memory for the image cache
        int cacheSize = maxMemory / 32;
        imageLoader = new ImageLoader(requestQueue, new BitmapLruCache(cacheSize));
        
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);  
        if (settings.getInt("wp_pref_last_activity", -1) >= 0)
            shouldRestoreSelectedActivity = true;

        restClient = new WPRestClient(requestQueue, new OauthAuthenticator(), settings.getString(ACCESS_TOKEN_PREFERENCE, null));
        registerForCloudMessaging(this);
        
        //Uncomment this line if you want to test the app locking feature
        AppLockManager.getInstance().enableDefaultAppLockIfAvailable(this);

        loadNotifications(this);
        super.onCreate();
    }
    
    public static void registerForCloudMessaging(Context ctx) {
        
        if (WordPress.hasValidWPComCredentials(ctx)) {
            String token = null;
            try {
                // Register for Google Cloud Messaging
                GCMRegistrar.checkDevice(ctx);
                GCMRegistrar.checkManifest(ctx);
                token = GCMRegistrar.getRegistrationId(ctx);
                String gcmId = Config.GCM_ID;
                if (gcmId != null && token.equals("")) {
                    GCMRegistrar.register(ctx, gcmId);
                } else {
                    // Send the token to WP.com in case it was invalidated
                    
                    new WPComXMLRPCApi(ctx).registerWPComToken(ctx, token);
                    Log.v("WORDPRESS", "Already registered for GCM");
                }
            } catch (Exception e) {
                Log.v("WORDPRESS", "Could not register for GCM: " + e.getMessage());
            }
        }
    }
        
    /**
     * Get versionName from Manifest.xml
     * @return versionName
     */
    private String getVersionName(){
        PackageManager pm = getPackageManager();
        try {
            PackageInfo pi = pm.getPackageInfo(getPackageName(), 0);
            return pi.versionName;
        } catch (NameNotFoundException e) {
            return "";
        }
    }

    public interface OnPostUploadedListener {
        public abstract void OnPostUploaded();
    }

    public static void setOnPostUploadedListener(OnPostUploadedListener listener) {
        onPostUploadedListener = listener;
    }

    public static void postUploaded() {
        if (onPostUploadedListener != null) {
            try {
                onPostUploadedListener.OnPostUploaded();
            } catch (Exception e) {
                postsShouldRefresh = true;
            }
        } else {
            postsShouldRefresh = true;
        }

    }

    /**
     * Get the currently active blog.
     * <p>
     * If the current blog is not already set, try and determine the last active blog from the last
     * time the application was used. If we're not able to determine the last active blog, just
     * select the first one.
     */
    public static Blog getCurrentBlog() {
        if (currentBlog == null) {
            // attempt to restore the last active blog
            setCurrentBlogToLastActive();

            // fallback to just using the first blog
            List<Map<String, Object>> accounts = WordPress.wpDB.getAccounts();
            if (currentBlog == null && accounts.size() > 0) {
                int id = Integer.valueOf(accounts.get(0).get("id").toString());
                setCurrentBlog(id);
                wpDB.updateLastBlogId(id);
            }
        }

        return currentBlog;
    }

    /**
     * Get the blog with the specified ID.
     *
     * @param id ID of the blog to retrieve.
     * @return the blog with the specified ID, or null if blog could not be retrieved.
     */
    public static Blog getBlog(int id) {
        try {
            Blog blog = new Blog(id);
            return blog;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Set the last active blog as the current blog.
     * 
     * @return the current blog
     */
    public static Blog setCurrentBlogToLastActive() {
        List<Map<String, Object>> accounts = WordPress.wpDB.getAccounts();

        int lastBlogId = WordPress.wpDB.getLastBlogId();
        if (lastBlogId != -1) {
            for (Map<String, Object> account : accounts) {
                int id = Integer.valueOf(account.get("id").toString());
                if (id == lastBlogId) {
                    setCurrentBlog(id);
                }
            }
        }

        return currentBlog;
    }

    /**
     * Set the blog with the specified id as the current blog.
     * 
     * @param id id of the blog to set as current
     * @return the current blog
     */
    public static Blog setCurrentBlog(int id) {
        try {
            currentBlog = new Blog(id);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return currentBlog;
    }
    /**
     * Restores notifications from cached file
     */
    public static void loadNotifications(Context context){
        File file = new File(context.getCacheDir(), NOTES_CACHE);
        BufferedReader buf = null;
        StringBuilder json = null;
        try {
            buf = new BufferedReader(new FileReader(file));
            json = new StringBuilder();
            String line;
            do {
                line = buf.readLine();
                json.append(line);
            } while(line == null);
        } catch (java.io.FileNotFoundException e) {
            json = null;
            Log.e(TAG, "No cached notes", e);
        } catch (IOException e) {
            json = null;
            Log.e(TAG, "Could not read cached notes", e);
        } finally {
            try {
                if (buf != null) {
                    buf.close();
                }
            } catch (IOException e) {
                Log.e(TAG, "Couldn't close file buffer", e);
            }
        }
        if (json == null) {
            Log.d(TAG, "Could not find cached notes");
            return;
        }
        try {
            latestNotes = new JSONObject(json.toString());
        } catch (JSONException e) {
            latestNotes = null;
            Log.e(TAG, "Failed to parse note json", e);
            return;
        }
        Log.d(TAG, "Restored notes");
    }
    /**
     * Refreshes latest notes and stores a copy of the response to disk.
     */
    public static void refreshNotifications(final Context context,
                                            final RestRequest.Listener listener,
                                            final RestRequest.ErrorListener errorListener){
        restClient.getNotifications(
            new RestRequest.Listener(){
                public void onResponse(JSONObject response){
                    latestNotes = response;
                    File file = new File(context.getCacheDir(), NOTES_CACHE);
                    if (file.exists()) {
                        file.delete();
                    }
                    FileWriter writer = null;
                    try {
                        String json = response.toString();
                        writer = new FileWriter(file);
                        writer.write(json, 0, json.length());
                        writer.close();
                        Log.d(TAG, String.format("Wrote notes json to %s", file));
                    } catch (IOException e) {
                        Log.d(TAG, String.format("Failed to cache notifications to %s", file));
                    } finally {
                        try {
                            if (writer != null) {
                                writer.close();                                    
                            }
                        } catch (IOException e) {
                            writer = null;
                        }
                    }
                    Log.d(TAG, String.format("Store file here %s", file));
                    if (listener != null) {
                        listener.onResponse(response);                        
                    }
                }
            },
            new RestRequest.ErrorListener(){
                public void onErrorResponse(VolleyError error){
                    if (errorListener != null) {
                        errorListener.onErrorResponse(error)                      ;
                    }
                }
            }
        );
    }
    /**
     * Delete cached notifications, usually due to account logout
     */
    public static void deleteCachedNotifications(Context context){
        File file = new File(context.getCacheDir(), NOTES_CACHE);
        if (file.exists()) {
            file.delete();
        }
        latestNotes = null;
    }
    
    /**
     * Checks for WordPress.com credentials
     * 
     * @return true if we have credentials or false if not
     */
    public static boolean hasValidWPComCredentials(Context context) {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
        String username = settings.getString(WPCOM_USERNAME_PREFERENCE, null);
        String password = settings.getString(WPCOM_PASSWORD_PREFERENCE, null);
        
        if (username != null && password != null)
            return true;
        else 
            return false;
    }
    
    /**
     * Returns WordPress.com Auth Token
     * 
     * @return String - The wpcom Auth token, or null if not authenticated.
     */
    public static String getWPComAuthToken(Context context) {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
        return settings.getString(WordPress.ACCESS_TOKEN_PREFERENCE, null);
    }
    
    class OauthAuthenticator implements WPRestClient.Authenticator {
        private final RequestQueue mQueue = Volley.newRequestQueue(WordPress.this);
        public void authenticate(WPRestClient.Request request){
            // set the access token if we have one
            if (!hasValidWPComCredentials(WordPress.this)) {
                request.abort(new VolleyError("Missing WordPress.com Account"));
            } else {
                requestAccessToken(request);
            }
        }
        public void requestAccessToken(final WPRestClient.Request request){
            final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(WordPress.this);
            String username = settings.getString(WPCOM_USERNAME_PREFERENCE, null);
            String password = WordPressDB.decryptPassword(settings.getString(WPCOM_PASSWORD_PREFERENCE, null));
            Oauth oauth = new Oauth(Config.OAUTH_APP_ID, Config.OAUTH_APP_SECRET, Config.OAUTH_REDIRECT_URI);
            // make oauth volley request
            Request oauthRequest = oauth.makeRequest(username, password,
                new Oauth.Listener(){
                    public void onResponse(Oauth.Token token){
                        settings.edit().putString(ACCESS_TOKEN_PREFERENCE, token.toString())
                            .commit();
                        request.setAccessToken(token);
                        request.send();
                    }
                },
                new Oauth.ErrorListener(){
                    public void onErrorResponse(VolleyError error){
                        request.abort(error);
                    }
                }
            );
            mQueue.add(oauthRequest);
            // add oauth request to the request queue
            
        }
    }

    /**
     * Sign out from all accounts by clearing out the password, which will require user to sign in
     * again
     */
    public static void signOut(Context context) {
        new WPComXMLRPCApi(context).unregisterWPComToken(
                context,
                GCMRegistrar.getRegistrationId(context));
        try {
            GCMRegistrar.checkDevice(context);
            GCMRegistrar.unregister(context);
        } catch (Exception e) {
            Log.v("WORDPRESS", "Could not unregister for GCM: " + e.getMessage());
        }
        SharedPreferences.Editor editor = PreferenceManager
                .getDefaultSharedPreferences(context).edit();
        editor.remove(WordPress.WPCOM_USERNAME_PREFERENCE);
        editor.remove(WordPress.WPCOM_PASSWORD_PREFERENCE);
        editor.remove(WordPress.ACCESS_TOKEN_PREFERENCE);
        editor.commit();
        wpDB.deactivateAccounts();
        wpDB.updateLastBlogId(-1);
        currentBlog = null;
        restClient.clearAccessToken();
    }
}
