const fileSystem = require("fs");
const mongoose = require("mongoose");
const ServerStatus = require("../ServerStatus");

const COUT = process.stdout;

/** 集合所有的資料庫模板資料。 */
const DBModels = {
    "User": require("./mongooseSchemas/User"),
    "Painting": require("./mongooseSchemas/Painting"),
    "Season": require("./mongooseSchemas/Season"),
    "Theme": require("./mongooseSchemas/Theme"),
    "Participation": require("./mongooseSchemas/Participation"),
    "ParticipantInfo": require("./mongooseSchemas/ParticipantInfo"),
    "SiteMail": require("./mongooseSchemas/SiteMail"),
    "SiteMessage": require("./mongooseSchemas/SiteMessage"),
    "Rating": require("./mongooseSchemas/Rating"),
    "Comment": require("./mongooseSchemas/Comment"),
    "NewTheme": require("./mongooseSchemas/NewTheme"),
    "PaintingSpotlight": require("./mongooseSchemas/PaintingSpotlight"),
    "ServerMessage": require("./mongooseSchemas/ServerMessage")
};

/** 指令字串切割方式 */
const cmdSpliter = / +/g;

/** 目前正用於處理指令輸入操作的函式。 */
let CurrentOperationFunc = CommandRoutes;

/** 承接頂層的LineReader之指令處理函式。 */
function MiddleHandler(line) { CurrentOperationFunc(line); }

/** 關閉、離開伺服器運行的方法函式。 */
let ExitServerFunction;

/**
 * 初始化「指令路由」的初始化函式。
 * @param {Function} ExitServFunc 停止伺服器運作的函式。此方法由外部傳入。
 */
function Init(ExitServFunc) {
    ExitServerFunction = ExitServFunc;
    return MiddleHandler;
}

/**
 * 指令路由函式。
 * @param {string} line 從標準文字輸入所接收到的一行輸入字串。
 */
function CommandRoutes(line) {
    let query = line.split(cmdSpliter);     // 以空格來切割每一個指令
    switch(query[0]) {
        case ".exit":       ExitServerFunction(); break;
        case "showstatus":  ShowServerStatus(); break;
        case "act":         ActivityFunction(query); break;
        case "servmsg":     ServMessageFunction(query); break;
        case "db":          DatabaseFunctions(line, query); break;
        case "restore":     RestoreServer(); break;
        case "help":        HelpInformation(); break;
        default:
            console.log("\n錯誤: 找不到指令或巨集。\n");
    }
}

//#region ==================== Show Status Operation Command =========================
/**
 * 顯示目前伺服器的狀態。
 */
function ShowServerStatus() {
    let status = ServerStatus.status;
    if (status) {
        let info =  "目前舉行的活動為第 " + status.currentSeason + " 季\n" +
                    "主題投稿活動： " + ( status.submitThemeEvent ? "運行中\n" : "未運行\n" ) +
                    "主題投票活動： " + ( status.voteThemeEvent ? "運行中\n" : "未運行\n" ) +
                    "在主題投票活動中，每位使用者能夠投選" + status.voteCount + "個候選主題\n" + 
                    "在主題投票活動完成之後，會選出前" + status.promotionCount + "高票個候選主題作為新一季的主題\n" +
                    "自動活動排程系統目前" + ( status.onSchedule ? "正在運行中\n" : "停止使用中\n" ) +
                    "伺服狀態自動儲存: " + ( status.autoSaveStatus ? "開啟\n" : "關閉\n" );
        console.log("\n==================== 伺服器狀態 ====================");
        console.log(info);
    }
    else {
        console.log("\nPlease wait for loading server status file.\n");
    }
}
//#endregion =========================================================================

//#region ==================== Activity Operation Commands ====================
/**
 * 與藝廊活動有關的相關操作函式。
 * @param {string[]} query 解析後的指令片段。
 */
function ActivityFunction(query) {
    let operation = query[1];
    switch(operation) {
        case "status":  Act_Status(); break;
        case "push":    Act_Push(); break;
        case "help":    Act_Help(); break;
        default:
            console.log("\n錯誤: 找不到指令或巨集。\n");
    }
}

/**
 * 顯示目前活動的狀態。
 */
function Act_Status() {
    let status = ServerStatus.status;
    let info = "\n目前舉行的活動為第 " + status.currentSeason + " 季\n";
    // 若「投稿主題」正在進行中
    if (status.submitThemeEvent) {
        info += "當前正為下一季的活動做準備，正舉行著「投稿主題」中。\n";
    }
    // 若「票選主題」正在進行中
    else if (status.voteThemeEvent) {
        info += "當前正為下一季的活動做準備，正舉行著「投稿主題」中。\n";
    }
    info += "目前的活動規劃" + (status.onSchedule ? "由排程系統自動進行中。\n" : "為手動操作。\n");
    console.log(info);
}

/**
 * 以手動的方式推進活動狀態。
 * 若活動排程系統為啟用的狀態，則這個操作將會失效。
 * @param {string[]} query 解析後的指令片段。
 */
function Act_Push(query) {
    let status = ServerStatus.status;
    // 若排程系統是啟用的，則印出訊息。
    if (status.onSchedule) {
        console.log("\n目前的活動規劃由排程系統管理中，無法做手動操作的部分。");
        console.log("若想要進行「活動階段推進」的動作，請先將排程系統關閉。\n");
        return;
    }

    // 若排程系統是關閉的，則先確認是否要執行活動狀態推進的動作。
    function AskPush(line) {
        if (line == "Y" || line == "y") {       // 若使用者確認，則將處理文字輸入的主函式改回，並進行推進的動作
            CurrentOperationFunc = CommandRoutes;
            PushActivity();
        }
        else if (line == "N" || line == "n") {  // 若使用者確認，則將處理文字輸入的主函式改回，並進行推進的動作
            CurrentOperationFunc = CommandRoutes;
            console.log();
        }
        else {
            console.log("\n請輸入Y或N來決定是否要推進狀態: \n");
        }
    }

    let curSeason = status.currentSeason;   // 取得目前的季數
    COUT.write("\n目前的活動狀態為");

    // 依不同的狀態印出訊息
    switch(true) {
        case status.submitThemeEvent:       // 若目前「投稿主題」活動正在進行
            COUT.write("「投稿主題」。\n");
            console.log("若要推進，則活動狀態會到第" + curSeason + "季與第" + (curSeason + 1) + "季的「主題票選」活動。\n");
            break;
        case status.voteThemeEvent:         // 若目前「主題票選」活動正在進行
            COUT.write("「主題票選」。\n");
            console.log("若要推進，則活動狀態會到第" + (curSeason + 1) + "季，也就是更新到全新的一季。\n");
            break;
        default:                            // 若目前僅進行一般的季活動
            COUT.write("「一般活動」。\n");
            console.log("若要推進，則活動狀態會到第" + curSeason + "季與第" + (curSeason + 1) + "季的「投稿主題」活動。\n");
    }
    console.log("\n請問是否要推進? (Y/N) \n");
    CurrentOperationFunc = AskPush;     // 將處理文字輸入的主函式改為AskPush，也就是詢問是否要推進。
}
//#region Functions that Act_Push use.
/**
 * 實作「活動階段推進」的程式動作。 (順序 1)
 */
function PushActivity() {
    let status = ServerStatus.status;   // 取得伺服器狀態物件
    // 若目前「投稿主題」活動正在進行
    if (status.submitThemeEvent) {
        status.submitThemeEvent = false;
        status.voteThemeEvent = true;

        // 刷新使用者資料中的 hasVotedNewTheme 欄位，將其轉變為false。
        DBModels.User.Refresh_HasVotedNewTheme((err) => {
            if (err) return console.log("\n設定使用者資料的時候發生了錯誤。請確認是否有與MongoDB連線。\n");

            // 詢問並設定每一位使用者投票的票數
            AskPromotionTheme(status.submitThemeEvent, status.voteThemeEvent, status.currentSeason);
        });
    }
    // 若目前「主題票選」活動正在進行
    else if (status.voteThemeEvent) {
        status.voteThemeEvent = false;
        status.currentSeason += 1;

        // 將新的一季活動做推進
        DBModels.Season.PushNewSeason((err, result) => {
            if (err) return console.log(result);
            AskBroadcastServerMessage(status.submitThemeEvent, status.voteThemeEvent, status.currentSeason);
        });
    }
    // 若目前僅一般主題在進行
    else {
        status.submitThemeEvent = true;
        // 刷新使用者資料中的 hasPostNewTheme 欄位，將其轉變為false。
        DBModels.User.Refresh_HasPostNewTheme((err) => {
            if (err) return console.log("\n設定使用者資料的時候發生了錯誤。請確認是否有與MongoDB連線。\n");

            // 詢問是否進行伺服訊息廣播
            AskBroadcastServerMessage(status.submitThemeEvent, status.voteThemeEvent, status.currentSeason);
        });
    }
}

/** 
 * 詢問使用者要選取NewTheme中前幾個主題做為最新一季的主題。
 * @param {boolean} submitTheme 「投稿主題」活動是否為進行中。
 * @param {boolean} voteTheme 「主題票選」活動是否為進行中。
 * @param {number} curSeason 目前最新的季。
 */
function AskPromotionTheme(submitTheme, voteTheme, curSeason) {
    let validator = str => /^([1-9]\d*)$/.test(str);
    let ask = "\n在此次主題票選當中，在最後要選取多少候選主題出來作為新一季的主題: ";

    /** 處理使用者的回應 */
    function HandleResponse(line) {
        // 檢查使用者輸入的數值是否為大於0的正整數
        if (!validator(line)) {
            COUT.write("請輸入大於0的正整數、小於等於總候選主題數的數字: ");
            return;
        }
        // 檢查使用者輸入的數字是否小於等於候選主題的總數
        let count = parseInt(line);
        DBModels.NewTheme.count({}, (err, number) => {
            if (err) {
                console.log("\n檢查候選主題總數時發生了錯誤。請檢查MongoDB是否開啟或稍後重新操作。\n");
                CurrentOperationFunc = CommandRoutes;
                return;
            }
            // 若有小於等於候選主題總數，則進行下一步「詢問是否要對所有使用者廣播訊息」
            if (count <= number) {
                ServerStatus.status.promotionCount = count;                     // 將數量記在伺服狀態的promotionCount中
                AskBroadcastServerMessage(submitTheme, voteTheme, curSeason);
            }
            else {
                COUT.write("請輸入大於0的正整數、小於等於總候選主題數的數字: ");
            }
        }); 
    }

    COUT.write(ask);
    CurrentOperationFunc = HandleResponse;
}

/**
 * 詢問是否要進行伺服器的訊息廣播。 (順序 2)
 * @param {boolean} submitTheme 「投稿主題」活動是否為進行中。
 * @param {boolean} voteTheme 「主題票選」活動是否為進行中。
 * @param {number} curSeason 目前最新的季。
 */
function AskBroadcastServerMessage(submitTheme, voteTheme, curSeason) {
    let ask = "是否要對個使用者做新活動的訊息廣播? (Y/N) ";
    let title, content;
    COUT.write(ask);

    /** 定義處理使用者輸入訊息標題的函式 (順序 3.A.1) */
    function HandleTitleResponse(line) {
        title = line;
        console.log("請輸入訊息內容:");
        CurrentOperationFunc = HandleContentResponse;
    }

    /** 定義處理使用者輸入訊息內容的函式 (順序 3.A.2) */
    function HandleContentResponse(line) {
        content = line;
        COUT.write("確認廣播此伺服訊息? (Y/N) ");
        CurrentOperationFunc = ConfirmServerMessage;
    }

    /** 定義處理使用者是否使用預設的訊息內容 (順序 3.B.1) */
    function DefaultResponse(line) {
        if (line == "Y" || line == "y") {
            // 以資料title、content來對所有使用者進行廣播
            BroadcastServerMessage(title, content, (err, result) => {
                if (err)  return console.log("\n" + result + "\n");
                
                // 依照不同狀態輸出成功訊息
                console.log("\n已成功地將最新的活動訊息廣播給所有使用者！");
                ShowFinishMessage(submitTheme, voteTheme, curSeason);
                
                // 最後將主要處理文字輸入的方法設定回主指令路由函式中
                CurrentOperationFunc = CommandRoutes;
            });
        }
        else if (line == "N" || line == "n") {
            // 如果使用者拒絕使用預設的伺服器廣播訊息，則輸出最終訊息，並將處理文字輸入的變數函式指回主處理函式
            ShowFinishMessage(submitTheme, voteTheme, curSeason);
            CurrentOperationFunc = CommandRoutes;
        }
        else {
            COUT.write("請問是否要使用預設的廣播訊息內容? (Y/N) ");
        }
    }

    /** 確認使用者是否要廣播此伺服訊息 (順序 3.A.3) */
    function ConfirmServerMessage(line) {
        if (line == "Y" || line == "y") {
            // 以資料title、content來對所有使用者進行廣播
            BroadcastServerMessage(title, content, (err, result) => {
                if (err)  return console.log("\n" + result + "\n");
                
                // 依照不同狀態輸出成功訊息
                console.log("\n已成功地將最新的活動訊息廣播給所有使用者！");
                ShowFinishMessage(submitTheme, voteTheme, curSeason);
                
                // 最後將主要處理文字輸入的方法設定回主指令路由函式中
                CurrentOperationFunc = CommandRoutes;
            });
        }
        else if (line == "N" || line == "n") {
            // 如果選擇不廣播伺服訊息，則再一次詢問「是否要做活動訓席廣播」
            AskBroadcastServerMessage(submitTheme, voteTheme, curSeason);
        }
        else {
            COUT.write("確認廣播此伺服訊息? (Y/N) ");
        }
    }

    /** 處理回應 (順序 3) */
    function HandleResponse(line) {
        // 若決定要對使用者進行廣播，則先詢問廣播訊息的標題
        if (line == "Y" || line == "y") {
            console.log("請輸入訊息標題:");
            CurrentOperationFunc = HandleTitleResponse;
        }
        // 若沒有決定要自行廣播，則再詢問是否要進行預設的伺服器訊息廣播
        else if (line == "N" || line == "n") {
            [title, content] = GetDefaultActivityServMsg(submitTheme, voteTheme, curSeason);
            console.log("\n預設的廣播格式為:");
            console.log("標題: %s", title);
            console.log("內容: %s\n", content);
            COUT.write("請問是否要使用預設的廣播訊息內容? (Y/N) ");
            CurrentOperationFunc = HandleDefaultResponse;
        }
        else {
            COUT.write(ask);
        }
    }

    // 變更主要處理文字輸入的函式
    CurrentOperationFunc = HandleResponse;
}

/**
 * 在「活動階段推進」之中，最後完成動作時所顯示的訊息。
 * @param {boolean} submitTheme 「投稿主題」活動是否為進行中。
 * @param {boolean} voteTheme 「主題票選」活動是否為進行中。
 * @param {number} curSeason 目前最新的季。
 */
function ShowFinishMessage(submitTheme, voteTheme, curSeason) {
    if (submitTheme)
        console.log("\n第%d季的「投稿主題」活動已經開始！\n", curSeason + 1);
    else if (voteTheme)
        console.log("\n第%d季的「投稿主題」活動已經結束。活動「主題票選」已經開始！\n", curSeason + 1);
    else
        console.log("\n第%d季的繪圖投稿活動與第%d季的「主題票選」活動已經結束。\n最新第%d季的活動已經開始！\n", curSeason - 1, curSeason, curSeason);
}

/**
 * 取得預設的伺服器廣播訊息。
 * @param {boolean} submitTheme 「投稿主題」活動是否為進行中。
 * @param {boolean} voteTheme 「主題票選」活動是否為進行中。
 * @param {number} curSeason 目前最新的季。
 * @return {string[]} 預設的伺服訊息中的 [標題, 內容] 的成對。
 */
function GetDefaultActivityServMsg(submitTheme, voteTheme, curSeason) {
    switch(true) {
        case submitTheme:
            return ["第" + (curSeason + 1) + "季的「投稿主題」活動已經開始！",
                    "最新的「主題投稿」活動已經開始了！無論是有創意、有熱情、有特殊想法的作家們，趕緊來發起你們心中所想要的主題吧！人人都有機會在版面上看到自己所投稿的主題噢！"];
        case voteTheme:
            return ["第" + (curSeason + 1) + "季的「主題票選」活動已經開始！",
                    "最新的「主題票選」活動已經開始了！無論你在先前有投稿主題或沒有投稿主題，趕緊來這個活動看看，有沒有你所想要、中意的主題，有的話就把你手中的票朝那主題投下去吧！"];
        default:
            return ["最新一季繪畫活動，第" + curSeason + "季已經開始了！！",
                    "第" + curSeason + "季的繪畫活動已經開始了！來看看在這最新的一季當中，有沒有你所喜歡的、所愛的主題，有的話就開始動起你手中的創意、發揮你的天份，來開始畫畫吧！此外，第" + ( curSeason - 1 ) + "季的繪畫活動結果已經出爐了，可以去瞧一瞧是哪幾位大師登上旁行榜吧！"];
    }
}
//#endregion

/** 
 * 查詢指令"act"的相關功能與說明。
 */
function Act_Help() {
    console.log("\nact - 與伺服器活動有相關的操作");
    console.log("act status : 檢視目前伺服器活動的狀態訊息。");
    console.log("act push   : 以手動的方式做「活動階段推進」的動作。");
    console.log("act help   : 查詢指令act的相關操作方式。\n")
}

/**
 * 新增、廣播伺服訊息。
 * @param {string} title 訊息的標題。
 * @param {string} content 訊息的內容。
 * @param {CallbackFunction} callback 回呼函式。
 */
function BroadcastServerMessage(title, content, callback) {
    let ServerMessage = DBModels.ServerMessage;
    let User = DBModels.User;
    // 建立一個新的伺服訊息資料
    ServerMessage.createNewServerMessage({ title, content }, (err, _id) => {
        if (err) return callback(err, "在建立新的伺服器訊息時發生了錯誤，請改用一般的廣播方式來進行廣播。");
        // 透過伺服訊息資料的_id，加入至各使用者的siteMsg中
        User.BroadcastServerMessage(_id, (err, result) => {
            if (err) return callback(err, "在對所有使用者進行廣播動作時發生了錯誤。請更改使用單一或多項指定來傳送伺服訊息。");
            callback(null, true);
        });;
    });
}

//#endregion ======================================================================

//#region ==================== Server Message Operation Commands ====================
/**
 * 撰寫伺服器訊息相關功能的函式。
 * @param {string[]} query 解析後的指令片段。
 */
function ServMessageFunction(query) {
    let operation = query[1];
    switch(operation) {
        case "sendone":   WriteServMsg(ServMsg_SendOne, false);  break;
        case "sendmany":  WriteServMsg(ServMsg_SendOne, false);  break;
        case "broadcast": WriteServMsg(ServMsg_Broadcast, true); break;
        case "help":      ServMsg_Help(); break;
        default:
            console.log("\n錯誤: 找不到指令或巨集。\n");
    }
}

/**
 * 實行指令"servmsg sendone"之指令。此函式必須要透過WriteServMsg函式內部來呼叫。
 * @param {string} title 伺服訊息的標題。
 * @param {string} content 伺服訊息的內容。
 * @param {boolean} isPrivate 是否為私密訊息。
 */
function ServMsg_SendOne(title, content, isPrivate) {
    let User = DBModels.User;
    /** 接收輸入的使用者名稱。 */
    function InputUsername(username) {
        // 嘗試取得目標使用者
        User.findOne({ "username": username }, (err, userDocs) => {
            if (err) {
                console.log("\n尋找目標使用者時發生了錯誤。請確認是否有連接上MongoDB。");
                CurrentOperationFunc = CommandRoutes;
            }
            else if (!userDocs) {
                console.log("\n找不到目標使用者，請再輸入一次: (使用者名稱) ");
            }
            else {
                userDocs.AddNewSiteMessage(title, content, isPrivate);
                userDocs.save((err) => {
                    if (err) {
                        console.log("\n使用者資料儲存失敗。請檢查是否與MongoDB連線正常。\n");
                    }
                    else {
                        console.log("\n伺服訊息發送成功!\n");
                    }
                    CurrentOperationFunc = CommandRoutes;
                });
            }
        });
    }

    console.log("\n請問要發送給哪位使用者? (使用者名稱) ")
    CurrentOperationFunc = InputUsername;
}

/**
 * 實行指令"servmsg sendmany"之指令。此函式必須要透過WriteServMsg函式內部來呼叫。
 * @param {string} title 伺服訊息的標題。
 * @param {string} content 伺服訊息的內容。
 * @param {boolean} isPrivate 是否為私密訊息。
 */
function ServMsg_SendMany(title, content, isPrivate) {
    let User = DBModels.User;
    /** 處理使用者輸入的使用者名稱。 */
    function InputUsername(username) {
        // 若有輸入文字則進行以下動作
        if (username.length > 0) {
            // 以使用者輸入的 username 尋找目標使用者是否存在
            User.findOne({ "username": username }, (err, userDocs) => {
                if (err) {
                    console.log("\n尋找目標使用者時發生了錯誤。請確認是否有連接上MongoDB。");
                    CurrentOperationFunc = CommandRoutes;
                }
                else if (!userDocs) {
                    console.log("\n找不到目標使用者，請在輸入一次: (輸入使用者名稱，或直接按下Enter來完成動作)");
                }
                else {
                    // 使用者存在，則以title、content與isPrivate新增主頁訊息
                    userDocs.AddNewSiteMessage(title, content, isPrivate);
                    // 新增完後做儲存動作。
                    userDocs.save((err) => {
                        if (err) {
                            console.log("\n使用者資料儲存失敗。請檢查是否與MongoDB連線正常。\n");
                            CurrentOperationFunc = CommandRoutes;
                        }
                        else {
                            console.log("\n伺服訊息發送成功，請繼續輸入? (輸入使用者名稱，或直接按下Enter來完成動作)");
                        }
                    });
                }
            });
        }
        else {
            console.log("\n已完成訊息發送動作。\n");
            CurrentOperationFunc = CommandRoutes;
        }
    }

    console.log("\n請問要發送給哪位使用者? (輸入使用者名稱，或直接按下Enter來完成動作)");
    CurrentOperationFunc = InputUsername;
}

/**
 * 實行指令"servmsg broadcast"之指令。此函式必須要透過WriteServMsg函式內部來呼叫。
 * @param {string} title 伺服訊息的標題。
 * @param {string} content 伺服訊息的內容。
 */
function ServMsg_Broadcast(title, content) {
    let ServerMessage = DBModels.ServerMessage;
    let User = DBModels.User;
    // 以title、content建立伺服訊息(廣播訊息)
    ServerMessage.createNewServerMessage({ title, content }, (err, _id) => {
        if (err) {
            console.log("\n建立伺服訊息資料時發生了錯誤，請檢查是否有連接上MongoDB。\n");
            CurrentOperationFunc = CommandRoutes;
            return;
        }
        // 定義要加入到各個使用者資料上的主頁訊息資料
        let data = {
            isServerMessage: true,
            refId: _id,
            postTime: new Date(),
            isSeen: false,
            isPrivate: false
        };
        // 更新所有使用者資料，將資料加入至siteMsg之中。
        User.updateMany({}, { $push: { siteMsg: data } }, (err, raw) => {
            if (err) {
                console.log("\n將新的伺服訊息更新至所有使用者的資料之中時發生了錯誤，請檢查是否有連接上MongoDB。\n");
            }
            else {
                console.log("\n您的伺服訊息已成功地廣播給所有使用者了！\n");
            }
            CurrentOperationFunc = CommandRoutes;
        });
    });
}

/**
 * 撰寫完訊息之後所要將標題、內容交給目標函式處理的函式。
 * @typedef {Function} WritenMethod
 * @param {string} title 訊息的標題。
 * @param {string} content 訊息的內容。
 * @param {boolean} isPrivate 是否為私密訊息。
 */
/**
 * 撰寫伺服器訊息。
 * @param {WritenMethod} sendMethod 當伺服訊息完成之後，所要處理標題、內容的函式。
 * @param {boolean} isBroadcast 是否為廣播訊息。
 */
function WriteServMsg(sendMethod, isBroadcast) {
    let title = "", content = "";
    let isPrivate;
    /** 撰寫訊息的標題。 */
    function WriteTitle(line) {
        title = line;
        console.log("\n請在以下轉寫你所要的訊息內容，直到內容中出現「\\0」才會進行下一步驟:");
        CurrentOperationFunc = WriteContent;
    }

    /** 撰寫訊息內容。 */
    function WriteContent(line) {
        let endIndex = line.search(/\\0/);
        if (endIndex < 0) {
            content += line;
        }
        else {
            content += line.substr(0, endIndex);
            // 若是廣播的話，則跳過觀看權限詢問。
            if (isBroadcast) {
                console.log("\n您的伺服訊息已經撰寫完成，內容如下。");
                console.log("標題: %s", title);
                console.log("內容: %s", content);
                console.log("* 由於此訊息為廣播訊息，因此全部使用者皆有觀看權限。 *");
                console.log("\n以上是否已為你所想要的內容呢? (Y/N) ");
                CurrentOperationFunc = ConfirmIsDone;
            }
            else {
                console.log("\n此訊息是否僅限使用者本身能看到: (Y/N) ");
                CurrentOperationFunc = SetIsPrivate;
            }
        }
    }

    /** 設定此訊息的觀看權限。 */
    function SetIsPrivate(line) {
        if (line == "Y" || line == "y" || line == "N" || line == "n") {
            isPrivate = (line == "Y" || line == "y");
            console.log("\n您的伺服訊息已經撰寫完成，內容如下。");
            console.log("標題: %s", title);
            console.log("內容: %s", content);
            console.log("僅限擁有者能觀看: %s", isPrivate ? "Yes" : "No");
            console.log("\n以上是否已為你所想要的內容呢? (Y/N) ");
            CurrentOperationFunc = ConfirmIsDone;
        }
        else {
            console.log("\n此訊息是否僅限使用者本身能看到: (Y/N) ");
        }
    }

    /** 確認訊息是否撰寫完成。 */
    function ConfirmIsDone(line) {
        if (line == "Y" || line == "y") {
            sendMethod(title, content, isPrivate);
        }
        else if (line == "N" || line == "n") {
            console.log("\n是否要再撰寫一份新的伺服訊息呢? (Y/N) ");
            CurrentOperationFunc = ConfirmAgain;
        }
        else {
            console.log("\n以上是否已為你所想要的內容呢? (Y/N) ");
        }
    }

    /** 已經被回拒，但再次確認是否要再撰寫一次。 */
    function ConfirmAgain(line) {
        if (line == "Y" || line == "y") {
            content = "";
            console.log("請寫上您所想要的伺服訊息標題: ");
            CurrentOperationFunc = WriteTitle;
        }
        else if (line == "N" || line == "n") {
            console.log();
            CurrentOperationFunc = CommandRoutes;
        }
        else {
            console.log("\n以上是否已為你所想要的內容呢? (Y/N) ");
        }
    }

    console.log("\n請寫上您所想要的伺服訊息標題: ");
    CurrentOperationFunc = WriteTitle;
}

/** 
 * 查詢伺服器訊息指令"servmsg"的相關操作用法。
 */
function ServMsg_Help() {
    console.log("\nservmsg - 與伺服器傳送訊息有關的操作");
    console.log("servmsg sendone   : 撰寫伺服器訊息，並將訊息傳送給指定的使用者。");
    console.log("servmsg sendmany  : 撰寫伺服器訊息，並將訊息傳給選定的多對使用者。");
    console.log("servmsg broadcast : 撰寫伺服器訊息，並將訊息廣播給全部的使用者。");
    console.log("servmsg help      : 查詢此指令的相關操作方式。\n");
}

//#endregion ======================================================================

//#region ====================== Database Operation Commands ======================

/**
 * 有關於資料庫操作的相關指令動作。
 * @param {string} original 原始完整的指令碼。
 * @param {string[]} query 解析後的指令片段。
 */
function DatabaseFunctions(original, query) {
    let operation = query[1];
    switch(operation) {
        case "find": DBOp_Find(original, query); break;
        case "user": DBOp_User(query); break;
        case "help": DBOp_Help(); break;
        default:
            console.log("\n錯誤: 找不到指令或巨集。\n");
    }
}

/**
 * 資料庫指令操作中的「搜尋」。
 * @param {string} original 原始完整的指令碼。
 * @param {string[]} query 解析後的指令片段。
 */
function DBOp_Find(original, query) {
    let target = query[2];
    let condition = original.substr(original.indexOf(query[3]));

    // 若顯示目標為"schema"，則秀出所有的模板名稱。
    if (target == "schemas") {
        console.log("\n=============== 資料庫中所有的模板 ===============");
        for (let schemaName in DBModels) {
            console.log(schemaName);
        }
        console.log();
        return;
    }

    // 若使用者有輸入條件參數，則嘗試轉換成JSON物件；若無，則設定為空JSON物件。
    if (query[3] && condition.length > 0) {
        try {
            condition = JSON.parse(condition);
        }
        catch (err) {
            return console.log("\n條件參數格式錯誤，請寫下正確的JSON格式。\n");
        }
    }
    else {
        condition = {};
    }    

    // 再透過目標(target)找出指定的資料表。
    let model = DBModels[target];
    let index = 0, length;
    // 若目標的資料集合存在，則繼續下一步
    if (model) {

        // 先計算出目標資料有多少個
        model.count(condition, (err, count) => {
            length = count;
            // 若數量為0，則印出訊息表示找不到並返回
            if (length == 0) {
                console.log("\n指定尋找的資料不存在。\n");
                return;
            }

            /** 詢問使否連續觀看資料後的處理。 */
            function HandleIsContinue(line) {
                if (line == "Y" || line == "y") {
                    SearchAndShows();
                }
                else if (line == "N" || line == "n") {
                    CurrentOperationFunc = CommandRoutes;
                }
                else {
                    console.log("\n以下還有%s筆資料，是否再向下顯示10筆? (Y/N)\n", length - index);
                }
            }

            /** 循環尋找，並且將資料印出。 */
            function SearchAndShows() {
                model.find(condition)
                    .skip(index)
                    .limit(10)
                    .exec((err, docsList) => {
                        if (err) {
                            console.log("\n搜尋資料庫中的指定資料時發生了錯誤。請檢查是否有與MongoDB連線。\n");
                            CurrentOperationFunc = CommandRoutes;
                            return;
                        }
                        console.log();
                        console.log(docsList);      // 將資料印出
                        console.log();
                        index += 10;                // 推移10個量。

                        // 判斷是否全數顯示完成，若全數顯示完成，則將文字處理介面指回原先的處理函式。
                        if (index >= length) {
                            CurrentOperationFunc = CommandRoutes;
                        }
                        else {
                            console.log("\n以下還有%s筆資料，請問是否再向下顯示10筆? (Y/N)\n", length - index);
                            CurrentOperationFunc = HandleIsContinue;
                        }
                    }
                );
            }
            SearchAndShows();
        });
    }
    // 若不存在，則印出
    else {
        console.log("\n找不到指定的模板。\n");
    }
}

/**
 * 與使用者資料操作相關的功能。
 * @param {string[]} query 解析後的指令片段。
 */
function DBOp_User(query) {
    let operation = query[2];
    switch(operation) {
        case "list": DBOp_User_List(); break;
        default:
            console.log("\n錯誤: 找不到指令或巨集。\n");

    }
}
//#region Commands under "db user" operation.
/**
 * 將所有的使用者名稱以清單的方式列出。
 */
function DBOp_User_List() {
    let User = DBModels.User;
    // 先取得所有使用者名稱的數量
    User.count({}, (err, userCount) => {
        if (err) return console.log("\n向資料庫發送請求時發生了錯誤。請檢查是否有與MongoDB連線。\n");
        let index = 0;
        
        /** 是否繼續顯示使用者名稱。 */
        function HandleIsContinue(line) {
            if (line == "Y" || line == "y") {
                ShowUsernames();
            }
            else if (line == "N" || line == "n") {
                CurrentOperationFunc = CommandRoutes;
            }
            else {
                console.log("\n還有剩餘的%s筆資料尚未顯示出來，是否繼續向下顯示80筆? (Y/N)");
            }
        }

        /** 顯示使用者名稱。 */
        function ShowUsernames() {
            User.find({})
                .select("username")
                .skip(index)
                .limit(80)
                .exec((err, docsList) => {
                    if (err) {
                        console.log("\n資料庫搜尋資料時發生了錯誤。請檢查是否有與MongoDB連線。\n");
                        CurrentOperationFunc = CommandRoutes;
                        return;
                    }
                    // 將使用者資料有序的顯示出來
                    let length = docsList.length, spaceChar = ' ', formatSpace;
                    for (let i = 0; i < length; i++) {
                        formatSpace = 17 - docsList[i].username.length;
                        console.log("%s%s%s", docsList[i].username, spaceChar.repeat(formatSpace), i % 4 == 3 ? "\n" : "");
                    }
                    // 檢查是否還有剩餘的資料沒有顯示出來
                    index += 80;
                    if (index >= userCount) {
                        CurrentOperationFunc = CommandRoutes;
                    }
                    else {
                        console.log("\n還有剩餘的%s筆資料尚未顯示出來，是否繼續向下顯示80筆? (Y/N)");
                        CurrentOperationFunc = HandleIsContinue;
                    }
                }
            );
        }

    });
}
//#endregion

/** 
 *  查詢伺服器訊息指令"db"的相關操作用法。
 */
function DBOp_Help() {
    console.log("\ndb - 與資料庫操作有關的指令");
    console.log("db find <SchemaName | 'schemas'> [condition] : 尋找指定資料表中的資料，或列出所有的資料表名稱。");
    console.log("db user <operation> : 與使用者資料相關的操作。\n");
    console.log("db help : 查詢此指令的相關操作方式。\n");
}

//#endregion ======================================================================

//#region ============================= Restore Command =============================
/** 
 * 將整個JMuseum伺服器恢復的最初始、最原本的狀態。
 */
function ServerRestoreCommand() {
    let times = 3;
    /** 再三地確認是否要恢復JMuseum伺服器。 */
    function ConfirmRestore(line) {
        if (line == "Y" || line == "y") {
            time -= 1;
            if (times > 0) {
                console.log("\n是否確定要將JMuseum伺服器恢復到最初始的狀態? (為了確保非為輸入錯誤，因此會詢問%s次) (Y/N)", times);
            }
            else {
                RESTORE_SERVER();
            }
        }
        else if (line == "N" || line == "n") {
            console.log();
            CurrentOperationFunc = CommandRoutes;
        }
        else {
            console.log("\n是否確定要將JMuseum伺服器恢復到最初始的狀態? (為了確保非為輸入錯誤，因此會詢問%s次)", times);
        }
    }

    console.log("\n是否確定要將JMuseum伺服器恢復到最初始的狀態? (為了確保非為輸入錯誤，因此會詢問%s次)", times);
    CurrentOperationFunc = ConfirmRestore;
}

//#endregion ========================================================================

//#region ============================== Help Command ==============================
/**
 * 打印出所有可用的指令種類。
 */
function HelpInformation() {
    console.log("\n所有可用的指令操作種類:");
    console.log("showstatus : 查看目前JMuseum伺服器的狀態。");
    console.log("       act : 繪圖藝廊活動有關的操作，如查看狀態、推進活動等等。");
    console.log("   servmsg : 伺服訊息有關的操作，如對使用者撰寫訊息、發起伺服廣播訊息等等。")
    console.log("        db : 資料庫操作相關的指令群，如搜尋、刪除、加入等等。");
    console.log("   restore : 將JMuseum伺服器回復到最初始的狀態。");
    console.log("      help : 查詢指令操作說明。");
    console.log("     .exit : 關閉JMuseum伺服器。\n");
}
//#endregion =======================================================================

//#region ============================== SERVER RESTORE ==============================

/**
 * 呼叫此函式會還原、回覆整個伺服器狀態與資料庫內的資料。
 */
function RESTORE_SERVER() {
    // 讀取還原預設資料檔案
    fileSystem.readFile(global.__dirname + "/db/restore_datas.json", { encoding: "utf8" }, (err, datas) => {
        if (err) return console.log("\n讀取還原之預設資料時發生了錯誤。請檢查在專案目錄之下的\"/db/restore_datas.json\"是否資料格式正確，或至Github上重新下載一個新的還原預設資料。\n");
        let restoreDatas = JSON.parse(datas);       // 取得原始資料

        // 先將資料庫中所有的資料清除
        CLEAR_COLLECTIONS().then(CREATE_RESTORING_DATAS(restoreDatas))
    });
}

/**
 * 將資料庫中所有資料清除。
 * @return {Promise} 讓外頭的函式可以繼續執行下一步。
 */
function CLEAR_COLLECTIONS() {
    let emptyCondition = {};
    /** 發生錯誤的時候的處理。 */
    function OnError(error) {
        console.log("\n清除資料庫中所有資料時發生了錯誤。請檢查是否有與MongoDB連線。\n");
        return false;
    }

    return DBModels.User.remove(emptyCondition).exec()
        .then(result => result ? DBModels.Painting.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.Season.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.Theme.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.Participation.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.Comment.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.ParticipantInfo.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.Rating.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.SiteMessage.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.SiteMail.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.ServerMessage.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.PaintingSpotlight.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? DBModels.NewTheme.remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? mongoose.connection.collection("sessions").remove(emptyCondition).exec() : false, OnError)
        .then(result => result ? Promise.resolve(true) : false , OnError);
}

/**
 * 建立回復伺服器的基本資料。
 * @param {Object} restoreDatas 回復伺服器時所用的資料。
 * @return {Function => Promise} Promise物件。
 */
function CREATE_RESTORING_DATAS(restoreDatas) {
    /** 發生錯誤時的處理函式。 */
    function OnError(error) {
        console.log("\n在建立回復伺服器之基本資料時發生了錯誤。請檢查是否有連接上MongoDB。\n");
        return false;
    }
    
    return function (result) {
        if (!result) return false;
        return DBModels.User.insertMany(restoreDatas.User)
            .then(result => result ? DBModels.Painting.insertMany(restoreDatas.Painting) : false, OnError)
            .then(result => result ? DBModels.Season.insertMany(restoreDatas.Season) : false, OnError)
            .then(result => result ? DBModels.Theme.insertMany(restoreDatas.Theme) : false, OnError)
            .then(result => result ? DBModels.ParticipantInfo.insertMany(restoreDatas.ParticipantInfo) : false, OnError)
            .then(result => result ? DBModels.Participation.insertMany(restoreDatas.Participation) : false, OnError)
            .then(result => result ? DBModels.PaintingSpotlight.insertMany(restoreDatas.PaintingSpotlight) : false, OnError)
            .then(result => result ? DBModels.ServerMessage.insertMany(restoreDatas.ServerMessage) : false, OnError)
    }
}

/**
 * 將有關的回復資料做連結。
 */
function CONNECT_RELATIVE_DATAS() {
    let User = DBModels.User, Painting = DBModels.Painting, Season = DBModels.Season;
    let Theme = DBModels.Theme, ParticipantInfo = DBModels.ParticipantInfo, Participation = DBModels.Participation;
    let PaintingSpotlight = DBModels.PaintingSpotlight, ServerMessage = DBModels.ServerMessage;

    let userDocs, paintingDocs, seasonDocs, themeDocs;
    let participantInfoDocs, participationDocs, serverMessageDocs;

    /** 當讀取資料庫之資料時發生錯誤的錯誤處理。 */
    function OnLoadError(error) {
        console.log("\n讀取資料庫中的資料時發生錯誤。請檢查是否有連接上MongoDB。\n");
        return false;
    }

    /** 當資料儲存時所發生的錯誤之錯誤處理。 */
    function OnSaveError(error) {
        console.log("\n將資料回存至資料庫時發生了錯誤。請檢查是否有連接上MongoDB。\n");
    }

    // 將與使用者有關的繪圖、主頁訊息加入其中
    DBModels.User.find({}).select("username paintings siteMsg").exec()
        .then(docs => docs ? (userDocs = docs, DBModels.Painting.find({}).select("artist activity").exec()) : false, OnLoadError)
        .then(docs => docs ? (paintingDocs = docs, DBModels.Season.findOne({}).select("themes").exec()) : false, OnLoadError)
        .then(docs => docs ? (seasonDocs = docs, DBModels.Theme.find({}).select("title participants").exec()) : false, OnLoadError)
        .then(docs => docs ? (themeDocs = docs, DBModels.ParticipantInfo.find({}).select("paintingName").exec()) : false, OnLoadError)
        .then(docs => docs ? (participantInfoDocs = docs, DBModels.Participation.find({}).select("themeName activityRank").exec()) : false, OnLoadError)
        .then(docs => docs ? (participationDocs = docs, DBModels.ServerMessage.findOne({}).select("_id").exec()) : false, OnLoadError)
        .then(docs => docs ? (serverMessageDocs = docs, true) : false, OnLoadError)
        .then(result => {
            if (!result) return false;
            // 建立新的主頁訊息資料，並將其主頁資料加入到使用者資料中
            let newSiteMessage = { isServerMessage: true, refId: serverMessageDocs._id, postTime: new Date(), isSeen: true, isPrivate: false };
            userDocs.map((docs) => {
                
            });
        });
}

//#endregion =========================================================================

module.exports = Init;