/*
 * 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.tool;

import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
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.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;


public class BackupUtils {
    private static final String TAG = "BackupUtils";
    // Singleton stuff
    private static BackupUtils sInstance;

    public static synchronized BackupUtils getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new BackupUtils(context);
        }
        return sInstance;
    }
/* `TAG` 是一个 String 常量，用于标识日志输出的 tag。
            `sInstance` 是一个静态变量，用于存储单例实例。
            `getInstance()` 是一个静态方法，通过传入一个 `Context` 参数获取 `BackupUtils` 的单例实例。
 这里使用了双重检查锁定来确保线程安全。
*/

    /**
     * Following states are signs to represents backup or restore（以下是表示备份或恢复的符号）
     * status
     */
    // Currently, the sdcard is not mounted
    public static final int STATE_SD_CARD_UNMOUONTED           = 0;
    // The backup file not exist(备份文件不存在）
    public static final int STATE_BACKUP_FILE_NOT_EXIST        = 1;
    // The data is not well formated, may be changed by other programs（数据格式不正确可能被其他程序更改）
    public static final int STATE_DATA_DESTROIED               = 2;
    // Some run-time exception which causes restore or backup fails（导致备份或还原失败的默写运行异常）
    public static final int STATE_SYSTEM_ERROR                 = 3;
    // Backup or restore success（备份或还原成功）
    public static final int STATE_SUCCESS                      = 4;

    private TextExport mTextExport;//实例化对象

    private BackupUtils(Context context) {
        mTextExport = new TextExport(context);
    }

    private static boolean externalStorageAvailable() {
        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    }

    public int exportToText() {
        return mTextExport.exportToText();
    }//这是一个 public 方法，用于将数据导出为文本，并返回一个整数值。

    public String getExportedTextFileName() {
        return mTextExport.mFileName;
    }//这是一个 public 方法，用于获取导出的文本文件名。

    public String getExportedTextFileDir() {
        return mTextExport.mFileDirectory;
    }//这是一个 public 方法，用于获取导出的文本文件所在的目录。

    private static class TextExport {
        private static final String[] NOTE_PROJECTION = {
                NoteColumns.ID,
                NoteColumns.MODIFIED_DATE,
                NoteColumns.SNIPPET,
                NoteColumns.TYPE
        };//这是一个私有的静态内部类，用于完成数据导出的操作。


        private static final int NOTE_COLUMN_ID = 0;

        private static final int NOTE_COLUMN_MODIFIED_DATE = 1;

        private static final int NOTE_COLUMN_SNIPPET = 2;

        private static final String[] DATA_PROJECTION = {
                DataColumns.CONTENT,
                DataColumns.MIME_TYPE,
                DataColumns.DATA1,
                DataColumns.DATA2,
                DataColumns.DATA3,
                DataColumns.DATA4,
        };

        private static final int DATA_COLUMN_CONTENT = 0;

        private static final int DATA_COLUMN_MIME_TYPE = 1;

        private static final int DATA_COLUMN_CALL_DATE = 2;

        private static final int DATA_COLUMN_PHONE_NUMBER = 4;
        // 定义了多个常量，用于操作笔记和数据的列索引

        private final String [] TEXT_FORMAT;
        private static final int FORMAT_FOLDER_NAME          = 0;
        private static final int FORMAT_NOTE_DATE            = 1;
        private static final int FORMAT_NOTE_CONTENT         = 2;
        // 定义了一个字符串数组 TEXT_FORMAT，其中存储了导出笔记时的文件格式

        private Context mContext;
        private String mFileName;
        private String mFileDirectory;
        // 定义了 Context mContext、String mFileName、String mFileDirectory 三个变量
        // mContext 存储了当前上下文，mFileName 存储了文件名，mFileDirectory 存储了文件夹路径

        public TextExport(Context context) {
            TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
            mContext = context;
            mFileName = "";
            mFileDirectory = "";// 构造函数，初始化了 TEXT_FORMAT、mContext、mFileName 和 mFileDirectory
        }

        private String getFormat(int id) {
            return TEXT_FORMAT[id];
        }// getFormat 方法返回 TEXT_FORMAT 数组中指定 id 的字符串

        /**
         * Export the folder identified by folder id to text
         * (exportFolderToText 方法用于将指定文件夹下的笔记导出到文本中)
         */
        private void exportFolderToText(String folderId, PrintStream ps) {
            // Query notes belong to this folder// 查询属于该文件夹的笔记
            Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
                    NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
                        folderId
                    }, null);

            if (notesCursor != null) {
                if (notesCursor.moveToFirst()) {
                    do {
                        // Print note's last modified date // 输出笔记的最后修改日期
                        ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
                                mContext.getString(R.string.format_datetime_mdhm),
                                notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
                        // Query data belong to this note // 查询属于该笔记的数据
                        String noteId = notesCursor.getString(NOTE_COLUMN_ID);
                        exportNoteToText(noteId, ps);
                    } while (notesCursor.moveToNext());
                }
                notesCursor.close();
            }
        }

        /**
         * Export note identified by id to a print stream
         */
        private void exportNoteToText(String noteId, PrintStream ps) {
            Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
                    DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
                        noteId
                    }, null);// exportNoteToText 方法用于将指定笔记的数据导出到文本中

            if (dataCursor != null) {
                if (dataCursor.moveToFirst()) {
                    do {
                        String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
                        if (DataConstants.CALL_NOTE.equals(mimeType)) {
                            // Print phone number // 输出电话号码
                            String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
                            long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
                            String location = dataCursor.getString(DATA_COLUMN_CONTENT);

                            if (!TextUtils.isEmpty(phoneNumber)) {
                                ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
                                        phoneNumber));
                            }
                            // Print call date// 输出通话日期
                            ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
                                    .format(mContext.getString(R.string.format_datetime_mdhm),
                                            callDate)));
                            // Print call attachment location // 输出通话附件位置
                            if (!TextUtils.isEmpty(location)) {
                                ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
                                        location));
                            }
                        } else if (DataConstants.NOTE.equals(mimeType)) {
                            String content = dataCursor.getString(DATA_COLUMN_CONTENT);
                            if (!TextUtils.isEmpty(content)) {
                                ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
                                        content));
                            }
                        }
                    } while (dataCursor.moveToNext());
                }
                dataCursor.close();
            }
            // print a line separator between note// 在导出每个笔记后输出一个分隔符
            try {
                ps.write(new byte[] {
                        Character.LINE_SEPARATOR, Character.LETTER_NUMBER
                });
            } catch (IOException e) {
                Log.e(TAG, e.toString());
            }
        }

        /**
         * Note will be exported as text which is user readable
         */
        public int exportToText() {
            if (!externalStorageAvailable()) {
                Log.d(TAG, "Media was not mounted");
                return STATE_SD_CARD_UNMOUONTED;
            }/*这段代码的作用是向指定的PrintStream对象中写入一个分隔符。它使用了Java中的byte数组，其中包含了两个特殊的字符：Character.LINE_SEPARATOR和Character.LETTER_NUMBER。

            Character.LINE_SEPARATOR表示平台的行分隔符，其值因平台而异。例如，在Windows中，它的值是"\r\n"，在Unix/Linux中，它的值是"\n"。

            Character.LETTER_NUMBER是一个没有实际意义的字符，它只是被用来作为分隔符的一部分。

            当这个byte数组被写入PrintStream对象时，它会在文本中插入一个分隔符，以便在文本中区分不同的笔记。如果写入过程中发生IOException，那么会在Logcat中输出相应的错误信息。*/

            PrintStream ps = getExportToTextPrintStream();
            if (ps == null) {
                Log.e(TAG, "get print stream error");
                return STATE_SYSTEM_ERROR;
            }//这段代码中的 getExportToTextPrintStream() 是一个自定义方法，它返回一个 PrintStream 对象，该对象用于将笔记数据导出到文本文件中
            // First export folder and its notes//“首先导出文件夹及其包含的笔记”
            Cursor folderCursor = mContext.getContentResolver().query(
                    Notes.CONTENT_NOTE_URI,
                    NOTE_PROJECTION,
                    "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
                            + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
                            + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);

            if (folderCursor != null) {
                if (folderCursor.moveToFirst()) {
                    do {
                        // Print folder's name
                        String folderName = "";
                        if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
                            folderName = mContext.getString(R.string.call_record_folder_name);
                        } else {
                            folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
                        }
                        if (!TextUtils.isEmpty(folderName)) {
                            ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
                        }
                        String folderId = folderCursor.getString(NOTE_COLUMN_ID);
                        exportFolderToText(folderId, ps);
                    } while (folderCursor.moveToNext());
                }
                folderCursor.close();
            }/*这段代码的作用是导出所有文件夹及其包含的笔记。

            首先，通过调用 ContentResolver 的 query() 方法查询所有的文件夹和 Call Record 文件夹，同时排除回收站中的笔记，将结果保存在 Cursor 对象 folderCursor 中。

            接着，通过遍历 folderCursor 中的所有记录，获取每个文件夹的名称和ID，并将其写入到输出流 ps 中。如果当前文件夹是 Call Record 文件夹，则使用字符串资源文件中的值作为文件夹的名称*/

            // Export notes in root's folder
            Cursor noteCursor = mContext.getContentResolver().query(
                    Notes.CONTENT_NOTE_URI,
                    NOTE_PROJECTION,
                    NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
                            + "=0", null, null);

            if (noteCursor != null) {
                if (noteCursor.moveToFirst()) {
                    do {
                        ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
                                mContext.getString(R.string.format_datetime_mdhm),
                                noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
                        // Query data belong to this note
                        String noteId = noteCursor.getString(NOTE_COLUMN_ID);
                        exportNoteToText(noteId, ps);
                    } while (noteCursor.moveToNext());
                }
                noteCursor.close();
            }
            ps.close();

            return STATE_SUCCESS;
        }/*这段代码的作用是导出根文件夹中的所有笔记。

        首先，通过调用 ContentResolver 的 query() 方法查询根文件夹中的所有笔记，并将结果保存在 Cursor 对象 noteCursor 中。

        接着，通过遍历 noteCursor 中的所有记录，获取每个笔记的修改日期，并将其写入到输出流 ps 中。然后，调用 exportNoteToText() 方法，将当前笔记的内容导出到输出流 ps 中。

        最后，关闭输出流 ps，并返回 STATE_SUCCESS 表示导出笔记数据成功。如果 noteCursor 为空，则不会做任何处理，直接关闭输出流并返回成功状态。*/

        /**
         * Get a print stream pointed to the file {@generateExportedTextFile}
         */
        private PrintStream getExportToTextPrintStream() {
            File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
                    R.string.file_name_txt_format);
            if (file == null) {
                Log.e(TAG, "create file to exported failed");
                return null;
            }
            mFileName = file.getName();
            mFileDirectory = mContext.getString(R.string.file_path);
            PrintStream ps = null;
            try {
                FileOutputStream fos = new FileOutputStream(file);
                ps = new PrintStream(fos);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return null;
            } catch (NullPointerException e) {
                e.printStackTrace();
                return null;
            }
            return ps;
        }
    }/*这段代码的作用是创建一个输出流 PrintStream 对象，用于将笔记数据导出到文本文件中。

首先，通过调用 generateFileMountedOnSDcard() 方法获取导出文件的路径和名称，并将结果保存在 File 对象 file 中。

接着，检查 file 是否为 null。如果是，则在Logcat中输出 "create file to exported failed" 的错误信息，并返回 null。

然后，获取 file 的名称和路径，并创建一个 FileOutputStream 对象 fos，将其作为参数传递给 PrintStream 构造函数，创建一个 PrintStream 对象 ps。

最后，返回 ps 对象，如果在创建 PrintStream 对象时出现 FileNotFoundException 或 NullPointerException 异常，则返回 null*/

    /**
     * Generate the text file to store imported data
     */
    private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
        StringBuilder sb = new StringBuilder();
        sb.append(Environment.getExternalStorageDirectory());
        sb.append(context.getString(filePathResId));
        File filedir = new File(sb.toString());
        sb.append(context.getString(
                fileNameFormatResId,
                DateFormat.format(context.getString(R.string.format_date_ymd),
                        System.currentTimeMillis())));
        File file = new File(sb.toString());

        try {
            if (!filedir.exists()) {
                filedir.mkdir();
            }
            if (!file.exists()) {
                file.createNewFile();
            }
            return file;
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }
}/*这段代码的作用是生成一个文件对象，并返回该对象的引用。

首先，获取外部存储设备的根目录，并将其与 filePathResId 参数所指定的路径拼接成一个完整的路径，并将其保存在 StringBuilder 对象 sb 中。

接着，创建一个 File 对象 filedir，用于表示存储导出文件的目录。如果该目录不存在，则调用 mkdir() 方法创建该目录。

然后，使用 fileNameFormatResId 参数所指定的文件名格式，将当前日期和时间添加到 sb 中，形成完整的文件路径。最后，创建一个 File 对象 file，用于表示导出的文件。

最后，检查 file 和 filedir 是否存在。如果它们都存在，则直接返回 file 对象的引用。否则，通过捕捉 SecurityException 和 IOException 异常，输出异常信息，并返回 null。*/


