/*
 * 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.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;

import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
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.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;

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

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

/**
 * GTaskManager 类负责管理 Google 任务的同步操作。
 * 它使用单例模式确保只有一个实例存在，并提供同步方法来处理本地和远程任务数据的同步。
 */
public class GTaskManager {
    // 日志标签，用于标识日志信息来源
    private static final String TAG = GTaskManager.class.getSimpleName();

    // 同步成功状态码
    public static final int STATE_SUCCESS = 0;
    // 网络错误状态码
    public static final int STATE_NETWORK_ERROR = 1;
    // 内部错误状态码
    public static final int STATE_INTERNAL_ERROR = 2;
    // 同步进行中状态码
    public static final int STATE_SYNC_IN_PROGRESS = 3;
    // 同步取消状态码
    public static final int STATE_SYNC_CANCELLED = 4;

    // 单例实例
    private static GTaskManager mInstance = null;

    // 用于获取认证令牌的 Activity
    private Activity mActivity;
    // 应用上下文
    private Context mContext;
    // 内容解析器，用于与内容提供者交互
    private ContentResolver mContentResolver;
    // 标识同步是否正在进行
    private boolean mSyncing;
    // 标识同步是否被取消
    private boolean mCancelled;
    // 存储从 Google 获取的任务列表，键为任务列表的全局 ID
    private HashMap<String, TaskList> mGTaskListHashMap;
    // 存储从 Google 获取的所有任务节点，键为任务的全局 ID
    private HashMap<String, Node> mGTaskHashMap;
    // 存储元数据，键为相关任务的全局 ID
    private HashMap<String, MetaData> mMetaHashMap;
    // 元数据任务列表
    private TaskList mMetaList;
    // 存储本地已删除的笔记 ID
    private HashSet<Long> mLocalDeleteIdMap;
    // 用于将 Google 任务 ID 映射到本地笔记 ID
    private HashMap<String, Long> mGidToNid;
    // 用于将本地笔记 ID 映射到 Google 任务 ID
    private HashMap<Long, String> mNidToGid;

    /**
     * 私有构造函数，确保单例模式
     */
    private GTaskManager() {
        // 初始化同步状态
        mSyncing = false;
        mCancelled = false;
        // 初始化存储数据的集合
        mGTaskListHashMap = new HashMap<String, TaskList>();
        mGTaskHashMap = new HashMap<String, Node>();
        mMetaHashMap = new HashMap<String, MetaData>();
        mMetaList = null;
        mLocalDeleteIdMap = new HashSet<Long>();
        mGidToNid = new HashMap<String, Long>();
        mNidToGid = new HashMap<Long, String>();
    }

    /**
     * 获取 GTaskManager 的单例实例
     * @return GTaskManager 的单例实例
     */
    public static synchronized GTaskManager getInstance() {
        if (mInstance == null) {
            // 如果实例不存在，则创建一个新的实例
            mInstance = new GTaskManager();
        }
        return mInstance;
    }

    /**
     * 设置用于获取认证令牌的 Activity 上下文
     * @param activity 用于获取认证令牌的 Activity
     */
    public synchronized void setActivityContext(Activity activity) {
        mActivity = activity;
    }

    /**
     * 执行本地和 Google 任务之间的同步操作
     * @param context 应用上下文
     * @param asyncTask 异步任务，用于发布同步进度
     * @return 同步结果的状态码
     */
    public int sync(Context context, GTaskASyncTask asyncTask) {
        if (mSyncing) {
            // 如果同步正在进行，记录日志并返回同步进行中状态码
            Log.d(TAG, "Sync is in progress");
            return STATE_SYNC_IN_PROGRESS;
        }
        // 设置上下文和内容解析器
        mContext = context;
        mContentResolver = mContext.getContentResolver();
        // 标记同步开始
        mSyncing = true;
        mCancelled = false;
        // 清空存储数据的集合
        mGTaskListHashMap.clear();
        mGTaskHashMap.clear();
        mMetaHashMap.clear();
        mLocalDeleteIdMap.clear();
        mGidToNid.clear();
        mNidToGid.clear();

        try {
            // 获取 GTaskClient 实例
            GTaskClient client = GTaskClient.getInstance();
            // 重置更新数组
            client.resetUpdateArray();

            // 登录 Google 任务
            if (!mCancelled) {
                if (!client.login(mActivity)) {
                    // 登录失败，抛出网络异常
                    throw new NetworkFailureException("login google task failed");
                }
            }

            // 从 Google 获取任务列表
            asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
            initGTaskList();

            // 执行内容同步工作
            asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
            syncContent();
        } catch (NetworkFailureException e) {
            // 捕获网络异常，记录日志并返回网络错误状态码
            Log.e(TAG, e.toString());
            return STATE_NETWORK_ERROR;
        } catch (ActionFailureException e) {
            // 捕获操作失败异常，记录日志并返回内部错误状态码
            Log.e(TAG, e.toString());
            return STATE_INTERNAL_ERROR;
        } catch (Exception e) {
            // 捕获其他异常，记录日志并返回内部错误状态码
            Log.e(TAG, e.toString());
            e.printStackTrace();
            return STATE_INTERNAL_ERROR;
        } finally {
            // 清空存储数据的集合
            mGTaskListHashMap.clear();
            mGTaskHashMap.clear();
            mMetaHashMap.clear();
            mLocalDeleteIdMap.clear();
            mGidToNid.clear();
            mNidToGid.clear();
            // 标记同步结束
            mSyncing = false;
        }

        // 根据取消状态返回同步结果状态码
        return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
    }

    /**
     * 初始化从 Google 获取的任务列表
     * @throws NetworkFailureException 网络异常
     */
    private void initGTaskList() throws NetworkFailureException {
        if (mCancelled)
            return;
        // 获取 GTaskClient 实例
        GTaskClient client = GTaskClient.getInstance();
        try {
            // 从 Google 获取任务列表的 JSON 数组
            JSONArray jsTaskLists = client.getTaskLists();

            // 初始化元数据列表
            mMetaList = null;
            for (int i = 0; i < jsTaskLists.length(); i++) {
                // 获取每个任务列表的 JSON 对象
                JSONObject object = jsTaskLists.getJSONObject(i);
                // 获取任务列表的全局 ID
                String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
                // 获取任务列表的名称
                String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);

                if (name
                        .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
                    // 如果是元数据列表
                    mMetaList = new TaskList();
                    // 根据远程 JSON 数据设置任务列表内容
                    mMetaList.setContentByRemoteJSON(object);

                    // 加载元数据
                    JSONArray jsMetas = client.getTaskList(gid);
                    for (int j = 0; j < jsMetas.length(); j++) {
                        // 获取每个元数据的 JSON 对象
                        object = (JSONObject) jsMetas.getJSONObject(j);
                        MetaData metaData = new MetaData();
                        // 根据远程 JSON 数据设置元数据内容
                        metaData.setContentByRemoteJSON(object);
                        if (metaData.isWorthSaving()) {
                            // 如果元数据值得保存，添加到元数据列表
                            mMetaList.addChildTask(metaData);
                            if (metaData.getGid() != null) {
                                // 将元数据添加到元数据哈希表
                                mMetaHashMap.put(metaData.getRelatedGid(), metaData);
                            }
                        }
                    }
                }
            }

            // 如果元数据列表不存在，创建一个新的元数据列表
            if (mMetaList == null) {
                mMetaList = new TaskList();
                mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
                        + GTaskStringUtils.FOLDER_META);
                GTaskClient.getInstance().createTaskList(mMetaList);
            }

            // 初始化任务列表
            for (int i = 0; i < jsTaskLists.length(); i++) {
                // 获取每个任务列表的 JSON 对象
                JSONObject object = jsTaskLists.getJSONObject(i);
                // 获取任务列表的全局 ID
                String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
                // 获取任务列表的名称
                String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);

                if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
                        && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
                                + GTaskStringUtils.FOLDER_META)) {
                    // 如果是有效的任务列表
                    TaskList tasklist = new TaskList();
                    // 根据远程 JSON 数据设置任务列表内容
                    tasklist.setContentByRemoteJSON(object);
                    // 将任务列表添加到任务列表哈希表
                    mGTaskListHashMap.put(gid, tasklist);
                    // 将任务列表添加到任务哈希表
                    mGTaskHashMap.put(gid, tasklist);

                    // 加载任务
                    JSONArray jsTasks = client.getTaskList(gid);
                    for (int j = 0; j < jsTasks.length(); j++) {
                        // 获取每个任务的 JSON 对象
                        object = (JSONObject) jsTasks.getJSONObject(j);
                        // 获取任务的全局 ID
                        gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
                        Task task = new Task();
                        // 根据远程 JSON 数据设置任务内容
                        task.setContentByRemoteJSON(object);
                        if (task.isWorthSaving()) {
                            // 如果任务值得保存，设置元信息并添加到任务列表
                            task.setMetaInfo(mMetaHashMap.get(gid));
                            tasklist.addChildTask(task);
                            // 将任务添加到任务哈希表
                            mGTaskHashMap.put(gid, task);
                        }
                    }
                }
            }
        } catch (JSONException e) {
            // 捕获 JSON 解析异常，记录日志并抛出操作失败异常
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("initGTaskList: handing JSONObject failed");
        }
    }

    /**
     * 执行本地和远程任务内容的同步操作
     * @throws NetworkFailureException 网络异常
     */
    private void syncContent() throws NetworkFailureException {
        int syncType;
        Cursor c = null;
        String gid;
        Node node;

        // 清空本地已删除笔记 ID 集合
        mLocalDeleteIdMap.clear();

        if (mCancelled) {
            return;
        }

        // 处理本地已删除的笔记
        try {
            // 查询本地已删除的笔记
            c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
                    "(type<>? AND parent_id=?)", new String[] {
                            String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
                    }, null);
            if (c != null) {
                while (c.moveToNext()) {
                    // 获取笔记的全局 ID
                    gid = c.getString(SqlNote.GTASK_ID_COLUMN);
                    // 从任务哈希表中获取对应的任务节点
                    node = mGTaskHashMap.get(gid);
                    if (node != null) {
                        // 如果节点存在，从任务哈希表中移除该节点
                        mGTaskHashMap.remove(gid);
                        // 执行删除远程任务的同步操作
                        doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
                    }

                    // 将本地已删除的笔记 ID 添加到集合中
                    mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
                }
            } else {
                // 查询失败，记录警告日志
                Log.w(TAG, "failed to query trash folder");
            }
        } finally {
            if (c != null) {
                // 关闭游标
                c.close();
                c = null;
            }
        }

        // 先同步文件夹
        syncFolder();

        // 处理数据库中存在的笔记
        try {
            // 查询数据库中存在的笔记
            c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
                    "(type=? AND parent_id<>?)", new String[] {
                            String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
                    }, NoteColumns.TYPE + " DESC");
            if (c != null) {
                while (c.moveToNext()) {
                    // 获取笔记的全局 ID
                    gid = c.getString(SqlNote.GTASK_ID_COLUMN);
                    // 从任务哈希表中获取对应的任务节点
                    node = mGTaskHashMap.get(gid);
                    if (node != null) {
                        // 如果节点存在，从任务哈希表中移除该节点
                        mGTaskHashMap.remove(gid);
                        // 建立全局 ID 到本地笔记 ID 的映射
                        mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
                        // 建立本地笔记 ID 到全局 ID 的映射
                        mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
                        // 获取同步类型
                        syncType = node.getSyncAction(c);
                    } else {
                        if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
                            // 如果全局 ID 为空，说明是本地新增的笔记
                            syncType = Node.SYNC_ACTION_ADD_REMOTE;
                        } else {
                            // 否则，说明是远程已删除的笔记
                            syncType = Node.SYNC_ACTION_DEL_LOCAL;
                        }
                    }
                    // 执行同步操作
                    doContentSync(syncType, node, c);
                }
            } else {
                // 查询失败，记录警告日志
                Log.w(TAG, "failed to query existing note in database");
            }

        } finally {
            if (c != null) {
                // 关闭游标
                c.close();
                c = null;
            }
        }

        // 处理剩余的任务节点
        Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<String, Node> entry = iter.next();
            node = entry.getValue();
            // 执行添加本地任务的同步操作
            doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
        }

        // 检查是否被取消
        if (!mCancelled) {
            if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
                // 批量删除本地已删除的笔记失败，抛出操作失败异常
                throw new ActionFailureException("failed to batch-delete local deleted notes");
            }
        }

        // 检查是否被取消
        if (!mCancelled) {
            // 提交更新
            GTaskClient.getInstance().commitUpdate();