/*
 * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
 *
 * 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.
 */

package net.micode.notes.gtask.remote;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

// GTaskClient 类用于与 Google Tasks 服务进行交互，包括登录、创建任务、创建任务列表等操作
public class GTaskClient {
    // 日志标签，用于在日志中标识该类的信息
    private static final String TAG = GTaskClient.class.getSimpleName();
    // Google Tasks 的基础 URL
    private static final String GTASK_URL = "https://mail.google.com/tasks/";
    // 用于获取数据的 URL
    private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
    // 用于提交数据的 URL
    private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
    // 单例模式的实例
    private static GTaskClient mInstance = null;
    // HTTP 客户端，用于发送 HTTP 请求
    private DefaultHttpClient mHttpClient;
    // 当前使用的获取数据的 URL
    private String mGetUrl;
    // 当前使用的提交数据的 URL
    private String mPostUrl;
    // 客户端版本号
    private long mClientVersion;
    // 登录状态标识
    private boolean mLoggedin;
    // 上次登录时间
    private long mLastLoginTime;
    // 操作 ID，用于标识每个操作
    private int mActionId;
    // 当前使用的 Google 账户
    private Account mAccount;
    // 待更新的操作数组
    private JSONArray mUpdateArray;

    // 私有构造函数，确保单例模式
    private GTaskClient() {
        // 初始化 HTTP 客户端为 null
        mHttpClient = null;
        // 初始化获取数据的 URL
        mGetUrl = GTASK_GET_URL;
        // 初始化提交数据的 URL
        mPostUrl = GTASK_POST_URL;
        // 初始化客户端版本号为 -1
        mClientVersion = -1;
        // 初始化登录状态为未登录
        mLoggedin = false;
        // 初始化上次登录时间为 0
        mLastLoginTime = 0;
        // 初始化操作 ID 为 1
        mActionId = 1;
        // 初始化账户为 null
        mAccount = null;
        // 初始化待更新操作数组为 null
        mUpdateArray = null;
    }

    // 获取单例实例的静态方法
    public static synchronized GTaskClient getInstance() {
        // 如果实例为空，则创建一个新的实例
        if (mInstance == null) {
            mInstance = new GTaskClient();
        }
        // 返回实例
        return mInstance;
    }

    // 登录 Google Tasks 服务的方法
    public boolean login(Activity activity) {
        // 假设 cookie 有效期为 5 分钟，超过该时间需要重新登录
        final long interval = 1000 * 60 * 5;
        // 如果距离上次登录超过 5 分钟，将登录状态设置为未登录
        if (mLastLoginTime + interval < System.currentTimeMillis()) {
            mLoggedin = false;
        }

        // 如果已登录，但账户发生切换，需要重新登录
        if (mLoggedin
                && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
                        .getSyncAccountName(activity))) {
            mLoggedin = false;
        }

        // 如果已经登录，打印日志并返回 true
        if (mLoggedin) {
            Log.d(TAG, "already logged in");
            return true;
        }

        // 更新上次登录时间
        mLastLoginTime = System.currentTimeMillis();
        // 登录 Google 账户并获取认证令牌
        String authToken = loginGoogleAccount(activity, false);
        // 如果认证令牌为空，登录失败，打印错误日志并返回 false
        if (authToken == null) {
            Log.e(TAG, "login google account failed");
            return false;
        }

        // 如果账户不是 gmail.com 或 googlemail.com 结尾，使用自定义域名登录
        if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
                .endsWith("googlemail.com"))) {
            // 构建自定义域名的 URL
            StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
            int index = mAccount.name.indexOf('@') + 1;
            String suffix = mAccount.name.substring(index);
            url.append(suffix + "/");
            mGetUrl = url.toString() + "ig";
            mPostUrl = url.toString() + "r/ig";

            // 尝试使用自定义域名登录 Google Tasks
            if (tryToLoginGtask(activity, authToken)) {
                mLoggedin = true;
            }
        }

        // 如果使用自定义域名登录失败，尝试使用 Google 官方 URL 登录
        if (!mLoggedin) {
            mGetUrl = GTASK_GET_URL;
            mPostUrl = GTASK_POST_URL;
            // 如果登录失败，打印错误日志并返回 false
            if (!tryToLoginGtask(activity, authToken)) {
                return false;
            }
        }

        // 登录成功，设置登录状态为已登录并返回 true
        mLoggedin = true;
        return true;
    }

    // 登录 Google 账户并获取认证令牌的方法
    private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
        String authToken;
        // 获取账户管理器
        AccountManager accountManager = AccountManager.get(activity);
        // 获取所有 Google 账户
        Account[] accounts = accountManager.getAccountsByType("com.google");

        // 如果没有可用的 Google 账户，打印错误日志并返回 null
        if (accounts.length == 0) {
            Log.e(TAG, "there is no available google account");
            return null;
        }

        // 获取设置中同步的账户名
        String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
        Account account = null;
        // 查找与设置中账户名相同的账户
        for (Account a : accounts) {
            if (a.name.equals(accountName)) {
                account = a;
                break;
            }
        }
        // 如果找到账户，将其赋值给 mAccount
        if (account != null) {
            mAccount = account;
        } else {
            // 如果未找到账户，打印错误日志并返回 null
            Log.e(TAG, "unable to get an account with the same name in the settings");
            return null;
        }

        // 获取认证令牌
        AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
                "goanna_mobile", null, activity, null, null);
        try {
            // 获取认证令牌的结果
            Bundle authTokenBundle = accountManagerFuture.getResult();
            // 从结果中获取认证令牌
            authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
            // 如果需要使令牌失效，调用 invalidateAuthToken 方法并重新获取令牌
            if (invalidateToken) {
                accountManager.invalidateAuthToken("com.google", authToken);
                loginGoogleAccount(activity, false);
            }
        } catch (Exception e) {
            // 如果获取认证令牌失败，打印错误日志并将 authToken 设置为 null
            Log.e(TAG, "get auth token failed");
            authToken = null;
        }

        // 返回认证令牌
        return authToken;
    }

    // 尝试登录 Google Tasks 服务的方法
    private boolean tryToLoginGtask(Activity activity, String authToken) {
        // 首先尝试使用当前认证令牌登录
        if (!loginGtask(authToken)) {
            // 如果登录失败，可能是认证令牌过期，使令牌失效并重新获取
            authToken = loginGoogleAccount(activity, true);
            // 如果重新获取令牌失败，打印错误日志并返回 false
            if (authToken == null) {
                Log.e(TAG, "login google account failed");
                return false;
            }

            // 再次尝试使用新的认证令牌登录
            if (!loginGtask(authToken)) {
                // 如果仍然登录失败，打印错误日志并返回 false
                Log.e(TAG, "login gtask failed");
                return false;
            }
        }
        // 登录成功，返回 true
        return true;
    }

    // 使用认证令牌登录 Google Tasks 服务的方法
    private boolean loginGtask(String authToken) {
        // 设置连接超时时间为 10 秒
        int timeoutConnection = 10000;
        // 设置套接字超时时间为 15 秒
        int timeoutSocket = 15000;
        // 创建 HTTP 参数对象
        HttpParams httpParameters = new BasicHttpParams();
        // 设置连接超时时间
        HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
        // 设置套接字超时时间
        HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
        // 创建 HTTP 客户端
        mHttpClient = new DefaultHttpClient(httpParameters);
        // 创建 cookie 存储对象
        BasicCookieStore localBasicCookieStore = new BasicCookieStore();
        // 设置 HTTP 客户端的 cookie 存储
        mHttpClient.setCookieStore(localBasicCookieStore);
        // 设置不使用 Expect: 100-continue 机制
        HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);

        // 构建登录 URL
        try {
            String loginUrl = mGetUrl + "?auth=" + authToken;
            // 创建 HTTP GET 请求
            HttpGet httpGet = new HttpGet(loginUrl);
            HttpResponse response = null;
            // 执行 HTTP GET 请求
            response = mHttpClient.execute(httpGet);

            // 获取 cookie
            List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
            boolean hasAuthCookie = false;
            // 检查是否存在包含 "GTL" 的 cookie
            for (Cookie cookie : cookies) {
                if (cookie.getName().contains("GTL")) {
                    hasAuthCookie = true;
                }
            }
            // 如果没有找到认证 cookie，打印警告日志
            if (!hasAuthCookie) {
                Log.w(TAG, "it seems that there is no auth cookie");
            }

            // 获取响应内容
            String resString = getResponseContent(response.getEntity());
            // 定义 JSON 字符串的开始和结束标记
            String jsBegin = "_setup(";
            String jsEnd = ")}</script>";
            // 查找 JSON 字符串的开始和结束位置
            int begin = resString.indexOf(jsBegin);
            int end = resString.lastIndexOf(jsEnd);
            String jsString = null;
            // 如果找到开始和结束位置，提取 JSON 字符串
            if (begin != -1 && end != -1 && begin < end) {
                jsString = resString.substring(begin + jsBegin.length(), end);
            }
            // 将 JSON 字符串转换为 JSONObject
            JSONObject js = new JSONObject(jsString);
            // 获取客户端版本号
            mClientVersion = js.getLong("v");
        } catch (JSONException e) {
            // 如果处理 JSON 数据时发生异常，打印错误日志并返回 false
            Log.e(TAG, e.toString());
            e.printStackTrace();
            return false;
        } catch (Exception e) {
            // 如果执行 HTTP 请求时发生异常，打印错误日志并返回 false
            Log.e(TAG, "httpget gtask_url failed");
            return false;
        }

        // 登录成功，返回 true
        return true;
    }

    // 获取操作 ID 的方法，每次调用操作 ID 加 1
    private int getActionId() {
        return mActionId++;
    }

    // 创建 HTTP POST 请求的方法
    private HttpPost createHttpPost() {
        // 创建 HTTP POST 请求对象
        HttpPost httpPost = new HttpPost(mPostUrl);
        // 设置请求头的 Content-Type
        httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
        // 设置请求头的 AT 字段
        httpPost.setHeader("AT", "1");
        // 返回 HTTP POST 请求对象
        return httpPost;
    }

    // 获取 HTTP 响应内容的方法
    private String getResponseContent(HttpEntity entity) throws IOException {
        String contentEncoding = null;
        // 获取响应内容的编码
        if (entity.getContentEncoding() != null) {
            contentEncoding = entity.getContentEncoding().getValue();
            Log.d(TAG, "encoding: " + contentEncoding);
        }

        // 获取响应内容的输入流
        InputStream input = entity.getContent();
        // 如果响应内容使用 gzip 压缩，使用 GZIPInputStream 解压
        if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
            input = new GZIPInputStream(entity.getContent());
        } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
            // 如果响应内容使用 deflate 压缩，使用 InflaterInputStream 解压
            Inflater inflater = new Inflater(true);
            input = new InflaterInputStream(entity.getContent(), inflater);
        }

        try {
            // 创建输入流读取器
            InputStreamReader isr = new InputStreamReader(input);
            // 创建缓冲读取器
            BufferedReader br = new BufferedReader(isr);
            // 创建字符串构建器
            StringBuilder sb = new StringBuilder();

            while (true) {
                // 读取一行内容
                String buff = br.readLine();
                // 如果读取到文件末尾，返回字符串构建器的内容
                if (buff == null) {
                    return sb.toString();
                }
                // 将读取的内容追加到字符串构建器中
                sb = sb.append(buff);
            }
        } finally {
            // 关闭输入流
            input.close();
        }
    }

    // 发送 POST 请求的方法
    private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
        // 如果未登录，抛出异常并打印错误日志
        if (!mLoggedin) {
            Log.e(TAG, "please login first");
            throw new ActionFailureException("not logged in");
        }

        // 创建 HTTP POST 请求
        HttpPost httpPost = createHttpPost();
        try {
            // 创建参数列表
            LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
            // 将 JSON 对象转换为字符串并添加到参数列表中
            list.add(new BasicNameValuePair("r", js.toString()));
            // 创建 URL 编码的表单实体
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
            // 设置请求实体
            httpPost.setEntity(entity);

            // 执行 HTTP POST 请求
            HttpResponse response = mHttpClient.execute(httpPost);
            // 获取响应内容
            String jsString = getResponseContent(response.getEntity());
            // 将响应内容转换为 JSONObject 并返回
            return new JSONObject(jsString);

        } catch (ClientProtocolException e) {
            // 如果发生客户端协议异常，抛出网络失败异常并打印错误日志
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new NetworkFailureException("postRequest failed");
        } catch (IOException e) {
            // 如果发生 IO 异常，抛出网络失败异常并打印错误日志
            Log.e(TAG,