const fileSystem = require("fs");

const User = require("../models/mongooseSchemas/User");
const Painting = require("../models/mongooseSchemas/Painting");

const router = require("express").Router();
const Jimp = require("jimp");
const uuid = require("uuid/v4");
const dataRender = require("../models/DataRender");

/** 畫作儲存時，通用的伺服器錯誤回應訊息。 */
const GeneralSavingErrorMessage = { isOK: false, field: "SERVER", message: "伺服器內部錯誤，請稍後再嘗試儲存。" };

/**
 * 頁面「繪圖創作」的路由處理。
 */
router.get(["/drawing", "/drawing/:users_painting_id"], (req, res) => {
    dataRender.DataRender("drawing", req.url, req.session, (err, dataObj) => {
        // 若找不到相對應的圖畫，則跳轉到指定的訊息頁面。
        if (Painting.IsError_PaintingNotExist(err)) {
            req.session.painting_not_exist = true;
            res.redirect("/painting_not_exist");
        }
        else if (err) {
            res.setHeader("Content-Type", "text/plain; charset=utf-8");
            res.status(500);
            res.end("Server side error 500 : " + err);
        }
        else {
            res.render("drawing", dataObj);
        }
    });
});


/** 確認字串資料流中的格式是否為png圖像(image/png)，且為base64格式。 */
const regexCheckType = /^data:image\/png;base64,/;

/**
 * 檢查使用者是否有登入。若有登入，則進行下一步處理；若無，則回送尚未登入的訊息至客戶端。
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 * @param {Function} next 導向函式。
 */
function CheckLogin(req, res, next) {
    res.setHeader("Content-Type", "application/json");

    // 先檢驗使用者是否登入，若有登入則再進行下一步。
    if (req.session.passport && req.session.passport.user) {
        next();
    }
    // 若沒有登入，則回送未登入的訊息。
    else {
        res.send({isOK: false, field: "SERVER", message: "您尚未登入，請先登入後再執行操作。"});
    }
}

/**
 * 檢查畫作作品的「畫作名稱」、「敘述」與「訪問權限」的內容是否正確。若正確則進行下一步；若無，則回送錯誤訊息至客戶端。
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 * @param {Function} next 導向函式。
 */
function CheckBasicPaintingInfo(req, res, next) {

    // 驗證「畫作名稱」
    req.checkBody("name")
       .notEmpty()
       .withMessage("請在「畫作名稱」上為你的作品命名。")
       .matches(/^([^<>\&"']{1,64})$/)
       .withMessage("請在「畫作名稱」上輸入1~64字的作品名稱，其中不可包含「<>&\"'」非法字元。");
    
    // 驗證「敘述」
    req.checkBody("description")
       .notEmpty()
       .withMessage("請為您的作品寫上1~300字間的敘述。")
       .matches(/^([^<>\&"']{1,300})$/)
       .withMessage("作品敘述中不可包含「<>&\"'」非法字元。");
    
    // 驗證「訪問權限」
    req.checkBody("view_authority")
       .notEmpty()
       .withMessage("在作品「訪問權限」中，您必須選擇其中一種權限，決定其他使用者對此作品的能見度。")
       .isInt({min: 0, max: 2})
       .withMessage("作品「訪問權限」的權限值必須為0~2之間。");
    
    // 取得以上的驗證結果
    req.getValidationResult().then(result => {
        let errors = result.mapped();
        // 若驗證結果為無任何錯誤，則繼續驗證剩下的「標籤」清單與圖畫影像
        if (result.isEmpty()) {
            next();
        }
        // 若有錯誤，則將結果回傳
        else {
            let firstErr = Object.values(errors)[0];
            res.send({isOK: false, field: firstErr.param, message: firstErr.msg});
        }
    });
}

/**
 * 檢查畫作作品的「標籤」清單。若正確則進行下一步；若無，則回送錯誤訊息至客戶端。
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 * @param {Function} next 導向函式。
 */
function CheckPaintingsTags(req, res, next) {
    let user_id = req.session.passport.user;    // 取得使用者的_id

    // 尋找目標使用者
    User.findOne({"_id": user_id})
        .populate({ path: "paintings", select: { "id": 1, "isFinished": 1, "isLocked": 1 } })
        .exec((err, userDocs) => {
            if (err) return res.send(GeneralSavingErrorMessage);
            // 檢查畫作的標籤是否接在使用者的定義之下
            if (userDocs.IsInTagsList(req.body.taglist)) {
                req.session.userDocs = userDocs;
                next();
            }
            // 若否，則傳回錯誤訊息
            else {
                res.send({isOK: false, field: "taglist", message: "有一至多個被選取的標籤在作品「標籤」清單中，並未在您所定義的標籤清單中。"});
            }
        }
    );
}

/**
 * 檢查畫作資訊是否正確。若處理完成正確無誤，則導向下一個處理程序；若有錯誤則回送錯誤訊息至客戶端。
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 * @param {Function} next 導向函式。
 */
function CheckPaintingInfo(req, res, next) {
    let body = req.body;                        // 資料主體
    let userDocs = req.session.userDocs;        // 取得資料庫之使用者資料
    let responseMsg = {isOK: false};            // 定義回送訊息包
    let img_id  = body.id;                      // 取得畫作的id
    let imgData = body.painting_image;          // 取得圖片資料

    delete req.session.userDocs;

    // 若畫作id存在 且 該畫作不為使用者所有的畫作，則回送錯誤訊息
    if (img_id && !userDocs.IsPaintingsOwner(img_id)) {
        res.send({isOK: false, field: "id", message: "此畫作id無法在您的作品集中找到。您目前所繪製的作品並不屬於您的。"});
        return;
    }

    // 接下來檢查圖畫作品是否符合指定的格式
    if (regexCheckType.test(imgData)) {
        let base64Data = imgData.replace(/^data:image\/png;base64,/, "");
        let dataBuffer = new Buffer(base64Data, "base64");

        // 以Buffer的方式讀取Base64的影像檔案
        Jimp.read(dataBuffer, (err, image) => {
            if (err) return res.send({isOK: false, field: "painting_image", message: "無法解析傳送至伺服端的圖畫影像，您是否是以非正當的方式儲存圖畫呢？若有問題請回饋給我們。"});

            // 檢查圖畫影像的長寬比例是否正確
            if (image.bitmap.width === 800 && image.bitmap.height === 450) {
                // 將必要的資料存在Session中之後，在呼叫next()導向下一個處理。
                req.session.userDocs = userDocs;
                req.session.image = image;
                next();
            }
            // 若不正確則回送錯誤訊息
            else {
                res.send({isOK: false, field: "painting_image", message: "傳送至伺服端的圖畫之長寬大小與規定不符。您是否是以非正當的方式儲存圖畫呢？請以正確的方式儲存。"});
            }
        });
    }
    // 若不符合指定格式，則回送錯誤訊息
    else {
        res.send({isOK: false, field: "painting_image", message: "傳送的圖畫影像格式與規定不符。您是否是以非正當的方式儲存圖畫呢？請以正確的方式儲存。"});
    }
}

/**
 * 儲存畫作影像與資料。若成功則，則將成功訊息回送至客戶端；若失敗，則同樣將錯誤訊息回送。
 * 呼叫此處理函式時，req.session中必須包含讀取後的使用者資料(userDocs)、圖畫影像資料物件Jimp(image)，否則會出錯。
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 * @param {Function} next 導向函式。
 */
function SavingPaintingAndResponse(req, res, next) {
    let userDocs = req.session.userDocs;
    let image = req.session.image;

    delete req.session.userDocs;
    delete req.session.image;

    // 檢查使用者的畫作ID，若存在則更新；不存在則新增
    if (req.body.id)
        UpdatePaintingInfo(res, req.body, image, false);    // 若畫作已存在，則更新其資訊
    else
        CreateNewPainting(res, req.body, userDocs, image, false);   // 若為新畫作，則加入新資料至Painting並連結至User。
}

/**
 * 路由「POST: /drawing/save」的一連串處理中，為最後儲存的部分。
 * 對畫作做「Painting資料更新」與「更新圖畫影像檔案」的動作。
 * @param {Express.Response} res Express的Response物件。
 * @param {Object} body Express的資料主體(body)。
 * @param {User} userDocs 使用者資料(Mongoose)。
 * @param {Jimp.Jimp} image 圖畫影像檔案。
 * @param {boolean} isFinish 是否要完成圖畫。
 */
function UpdatePaintingInfo(res, body, image, isFinish) {
    let lastModified = new Date();          // 取得目前的時間日期
    body.lastModified = lastModified;       // 更新「最後修改時間」欄位
    body.isFinished = isFinish;             // 為資料主體添上 isFinished = false 屬性，以符合輸入的要求。

    Painting.UpdateInfoById(body, body.id, image, (err, result) => {
        // 若有錯誤，則檢查錯誤為何種型態。
        if (err) {
            if (Painting.IsError_PaintingHasFinished(err)) {
                res.send({isOK: false, field: "SERVER", message: "此畫作狀態已為「完成」狀態，無法再二次「完成」此畫作。"});
            }
            else if (Painting.IsError_PaintingIsLocked(err)) {
                res.send({isOK: false, field: "SERVER", message: "此畫作已被鎖定，無法對其做任何更改。"});
            }
            else if (Painting.IsError_PaintingNotExist(err)) {
                res.send({isOK: false, field: "SERVER", message: "指定的畫作並不存在。請檢查目前畫作是否正確或是否屬於您的。"});
            }
            else {
                res.send(GeneralSavingErrorMessage);
            }
            return;
        }

        // 更新、儲存成功，將成功訊息回送至客戶端。若為完成畫作，則傳送轉跳頁面網址。
        if (isFinish)
            res.send({isOK: true, url: "/painting_finished"});
        else
            res.send({isOK: true, id: body.id, lastModified: result.lastModified.toLocaleString(), message: "已成功儲存了您的畫作!"});
    });
}

/**
 * 路由「POST: /drawing/save」的一連串處理中，為最後儲存的部分。
 * 對新的畫作做「Painting資料庫新增資料」、「將新增的Painting資料連接User」與「儲存影像檔案」的動作。
 * @param {Express.Response} res Express的Response物件。
 * @param {Object} body Express的資料主體(body)。
 * @param {User} userDocs 使用者資料(Mongoose)。
 * @param {Jimp.Jimp} image 圖畫影像檔案。
 * @param {boolean} isFinish 圖畫是否完成。
 */
function CreateNewPainting(res, body, userDocs, image, isFinish) {
    // 定義新畫作的基本資訊物件
    let newPaintingData = {
        name: body.name,
        description: body.description,
        artist: userDocs.username,
        tags: body.taglist,
        viewAuthority: body.view_authority,
        isFinished: isFinish
    };
    // 以 newPaintingData 來建立新畫作資料
    Painting.createNewPainting(newPaintingData, (err, result) => {
        if (err) return res.send(GeneralSavingErrorMessage);

        let imageFileName = "./db/paintings/" + result.id + ".png"; // 定義儲存的檔案名稱
        userDocs.paintings.push(result._id);                        // 將畫作id增加至使用者資料的paintings中

        // 以定義的檔案名稱 imageFileName 將影像儲存
        image.write(imageFileName, (err) => {
            if (err) return res.send(GeneralSavingErrorMessage);

            // 儲存使用者資料
            userDocs.save((err) => {
                if (err) return res.send(GeneralSavingErrorMessage);

                // 所有資料儲存成功，回送成功訊息。若為完成畫作，則送出跳轉頁面網址。
                if (isFinish)
                    res.send({isOK: true, url: "/painting_finished"});
                else
                    res.send({isOK: true, id: result.id, lastModified: result.lastModified.toLocaleString(), message: "已成功儲存了您的新畫作!"});
            });
        });
    });
}

/**
 * 在頁面「繪圖創作」下，客戶端傳送「畫作儲存」的相關資料至伺服端時的處理。
 */
router.post("/drawing/save", CheckLogin, CheckBasicPaintingInfo, CheckPaintingsTags, CheckPaintingInfo, SavingPaintingAndResponse);

/**
 * 確認畫作是否已經有建立、儲存過一次。若有則繼續接下來的儲存動作；若無則回送訊息。
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 * @param {Function} next 導向函式。
 */
function CheckHaveCreatedPainting(req, res, next) {
    if (req.body.id) {
        next();
    }
    else {
        res.send({isOK: false, field: "SERVER", message: "此為新建立的畫作，請先手動儲存過一次後，再啟用「自動儲存」功能。"});
    }
}

/**
 * 處理「自動儲存」的處理，
 */
router.post("/drawing/autosave", CheckLogin, CheckHaveCreatedPainting, CheckBasicPaintingInfo, CheckPaintingsTags, CheckPaintingInfo, SavingPaintingAndResponse);

/**
 * 路由「POST: /drawing/save」的一連串處理中，為最後儲存的部分。
 * 對畫作做「Painting資料更新」與「更新圖畫影像檔案」的動作。此次
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 * @param {Function} next 導向函式。
 */
function FinishPainting(req, res, next) {
    let userDocs = req.session.userDocs;
    let image = req.session.image;

    delete req.session.userDocs;
    delete req.session.image;

    // 檢查使用者的畫作ID，若存在則更新；不存在則新增
    if (req.body.id)
        UpdatePaintingInfo(res, req.body, image, true);    // 若畫作已存在，則更新其資訊
    else
        CreateNewPainting(res, req.body, userDocs, image, true);   // 若為新畫作，則加入新資料至Painting並連結至User。
}

/**
 * 在頁面「繪圖創作」下，客戶端傳送「完成畫作」的相關資料至伺服端時的處理。
 */
router.post("/drawing/finish", CheckLogin, CheckBasicPaintingInfo, CheckPaintingsTags, CheckPaintingInfo, FinishPainting);

/**
 * 在頁面「繪圖創作」下，成功「完成畫作」動作之後的跳轉頁面。
 */
router.get("/painting_finished", (req, res) => {
    dataRender.DataRender("message_form", req.url, req.session, (err, dataObj) => {
        if (err) {
            res.setHeader("Content-Type", "text/plain; charset=utf-8");
            res.status(500);
            res.end("Server side error 500 : " + err);
        }
        else {
            res.render("message_form", dataObj);
        }
    });
});

/**
 * 找不到指定的畫作作品時的轉跳頁面。
 */
router.get("/painting_not_exist", (req, res) => {
    if (req.session.painting_not_exist) {
        delete req.session.painting_not_exist;  // 刪除標記
        dataRender.DataRender("message_form", req.url, req.session, (err, dataObj) => {
            if (err) {
                res.setHeader("Content-Type", "text/plain; charset=utf-8");
                res.status(500);
                res.end("Server side error 500 : " + err);
            }
            else {
                res.render("message_form", dataObj);
            }
        });
    }
    else {
        res.redirect("/");
    }
});

/**
 * 確認指定的畫作屬於使用者。若要近一步檢查大小，請使用CheckPaintingInfo函式檢查。
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 * @param {Function} next 導向函式。
 */
function CheckPaintingBelongsToUser(req, res, next) {
    let user_id = req.session.passport.user;    // 取得使用者的_id
    let paintingId = req.body.id;               // 取得指定要刪除的圖畫id

    // 若沒有指定畫作Id，則回送錯誤
    if (!paintingId) {
        res.send({isOK: false, field: "id", message: "沒有指定所要刪除的圖畫Id。"});
        return;
    }
    
    User.findOne({"_id": user_id})
        .populate({ path: "paintings", select: { "id": 1 } })
        .exec((err, userDocs) => {
            if (err) return res.send(GeneralSavingErrorMessage);
            if (!userDocs) return callback({isOK: false, field: "SERVER", message: "找不到使用者資料。請重新登入再嘗試，或請聯繫我們。"});

            // 檢查指定的畫作Id是否為使用者擁有。若是，則繼續下一步驟；若否，則回送錯誤訊息。
            if (userDocs.IsPaintingsOwner(paintingId)) {

                // 由於userDocs.paintings中的每一項皆是ObjectId物件，無法直接透過Array提供的indexOf與splice來刪除
                // 因此只能一個個比較後再刪除
                let paintingList = userDocs.paintings;
                let length = paintingList.length;
                for (let i = 0; i < length; i++) {
                    // 若第i個畫作Id與paintingId相同，則刪除其。
                    if (paintingList[i].equals(paintingId)) {
                        paintingList.splice(i, 1);
                        break;
                    }
                }

                // 儲存後進到下一步驟「刪除畫作資料」本身
                userDocs.save((err) => {
                    if (err) return res.send(GeneralSavingErrorMessage);
                    next();
                });
            }
            else {
                res.send({isOK: false, field: "id", message: "指定要刪除的畫作作品並不屬於您的，無法刪除。"});
            }
        }
    );
}

/**
 * 執行刪除畫作的動作，完成後將轉跳頁面網址送回。
 * @param {Express.Request} req Express的Request物件。
 * @param {Express.Response} res Express的Response物件。
 */
function DeletePainting_AndResponse(req, res) {
    let paintingId = req.body.id;               // 取的指定要刪除的圖畫Id

    Painting.findOne({"id": paintingId}, (err, paintingDocs) => {
        if (err) return res.send(GeneralSavingErrorMessage);
        if (!paintingDocs) return res.send({isOK: false, field: "SERVER", message: "找不到指定要刪除的畫作。請聯繫我們來解決此問題。"});
        let isJoinedActivity = paintingDocs.activity;       // 紀錄是否在參加過活動

        // 若畫作已被鎖定，則無法刪除畫作，回送錯誤訊息。
        if (paintingDocs.isLocked) {
            res.send({isOK: false, field: "id", message: "此畫作已被鎖定，無法進行刪除動作。"});
            return;
        }
        
        // 刪除與此畫作相關的「留言」、「評分」與「參與活動」資訊
        paintingDocs.RemoveAllReferenceInfo((err, isOK) => {
            if (err) return res.send(GeneralSavingErrorMessage);
            
            // 刪除圖畫影像
            fileSystem.unlink(global.__dirname + paintingDocs.links, (err) => { if (err) console.log(err); });

            // 刪除此畫作後，回送轉跳網址
            Painting.deleteOne({"_id": paintingDocs._id}, (err) => {
                if (err) return res.send(GeneralSavingErrorMessage);
                
                req.session.paintingDeleted = true;                 // 標記使用者已刪除畫作。有此標記，轉跳頁面才可以顯示
                if (isJoinedActivity)                               // 若使用者刪除的畫作有參加活動的話，在下個轉跳頁面中提醒使用者其畫作仍然會看得到
                    req.session.paintingDeleted_Activity = true;  
                res.send({isOK: true, url: "/painting_deleted"});
            });
        });
    });
}

/**
 * 使用者傳送「刪除畫作」的要求至伺服器。
 */
router.delete("/drawing/delete", CheckLogin, CheckPaintingBelongsToUser, DeletePainting_AndResponse);

/**
 * 使用者成功刪除畫作之後的轉跳頁面。
 */
router.get("/painting_deleted", (req, res) => {
    // 使用者有登入，且有被標記paintingDeleted，回應跳轉訊息頁面
    if (req.session.passport && req.session.passport.user && req.session.paintingDeleted) {
        delete req.session.paintingDeleted;     // 刪除標記
        dataRender.DataRender("message_form", req.url, req.session, (err, dataObj) => {
            if (err) {
                res.setHeader("Content-Type", "text/plain; charset=utf-8");
                res.status(500);
                res.end("Server side error 500 : " + err);
            }
            else {
                res.render("message_form", dataObj);
            }
        });
    }
    // 否則跳轉到首頁
    else {
        res.redirect("/");
    }
});

module.exports = router;