"use strict";
this.name = "IR-mfd.js";
this.author = "phkb";
this.copyright = "2025 phkb";
this.description = "Mission comms channel for IR mission";
this.license = "CC BY-NC-SA 4.0";

/*
    Quite a few ship interactions in this mission take place in space, using communication broadcasts to pass potentially mission critical info.
    However, comms can easily be lost when in flight, as distractions or emergencies take place.

    This item attempts to alleviate the issue. All on the ship-to-ship comms will take place via this MFD, the content of which is saved and 
    can be viewed later when docked.
*/

this._mfdName = expandMissionText("IR_mission_comms_name");
this._lineLength = 14;
this._mfdID = -1;
this._timeoutCounter = 0;
this._currentMFDText = expandMissionText("IR_mission_comms_header");
this._displayPoint = 0;
this._holdData = {};
this._subMessageTimer = null;
this._messageStore = [];
this._msRows = 23;
this._msCols = 31;
this._headerHold = "";

//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function () {
    // tell HUD selector about MFD
    var h = worldScripts.hudselector;
    if (h) h.$HUDSelectorAddMFD(this._mfdName, "IRMFD", this._mfdName);

    if (missionVariables.IR_commLog) {
        this._messageStore = JSON.parse(missionVariables.IR_commLog);
    }
    // initialise the MFD text
    player.ship.setMultiFunctionText(this._mfdName, this._currentMFDText, false);
    this.$initInterface(player.ship.dockedStation);
}

//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function () {
    missionVariables.IR_commLog = JSON.stringify(this._messageStore);
}

//-------------------------------------------------------------------------------------------------------------
this.missionScreenEnded = function () {
    if (player.ship.hudHidden == true) player.ship.hudHidden = false;
}

//-------------------------------------------------------------------------------------------------------------
this.shipDockedWithStation = function (station) {
    this._currentMFDText = expandMissionText("IR_mission_comms_header");
    this._headerHold = "";
    player.ship.setMultiFunctionText(this._mfdName, this._currentMFDText, false);
    this.$initInterface(station);
}

//-------------------------------------------------------------------------------------------------------------
this.shipWillLaunchFromStation = function () {
    this._headerHold += (this._headerHold == "" ? "" : "\n") + expandMissionText("IR_mfd_launch", { time: clock.clockStringForTime(clock.seconds) });
    this._currentMFDText = expandMissionText("IR_mission_comms_header");
    player.ship.setMultiFunctionText(this._mfdName, this._currentMFDText, false);
}

//-------------------------------------------------------------------------------------------------------------
this.shipExitedWitchspace = function() {
    this._headerHold += (this._headerHold == "" ? "" : "\n") + expandMissionText("IR_mfd_arrive", { time: clock.clockStringForTime(clock.adjustedSeconds) });
}

//-------------------------------------------------------------------------------------------------------------
this.$initInterface = function (station) {
    if (this._messageStore.length == 0) return;
    station.setInterface(this.name, {
        title: expandMissionText("IR_commslog_title"),
        category: expandMissionText("IR_mission_log_category"),
        summary: expandMissionText("IR_commslog_summary"),
        callback: this.$showCommLog.bind(this)
    });
}

//-------------------------------------------------------------------------------------------------------------
// breaks text up into appropriate lengths for an MFD
this.$processText = function $processText(msg) {
    var line = "";
    // if we've got a header ready to go, add it now before the message
    if (this._headerHold != "") {
        var lines = this._headerHold.split("\n");
        this._headerHold = "";
        for (var i = 0; i < lines.length; i++) {
            this._messageStore.push({ source: "", text: lines[i], params: {} });
            this.$processText(lines[i]);
        }
    }
    // the replace here should ensure all newline commands are at the end of a word, and not in the middle of one
    var words = msg.replace(new RegExp("\n", 'g'), "\n ").split(" ");
    var word = "";
    var output = [];

    for (var i = 0; i < words.length; i++) {
        word = words[i].trim();
        // make sure we have a word to add before trying to add it
        if (word !== "") {
            if (defaultFont.measureString(line + " " + word) <= this._lineLength) {
                line += (line === "" ? "" : " ") + word;
                // if the word ended in a newline command, add the line to the output and reset
                if (word.indexOf("\n", word.length - 2) !== -1) {
                    output += line;
                    line = "";
                }
            } else {
                output.push(line);
                line = "  " + word;
                // if the word ended in a newline command, add the line to the output and reset
                if (word.indexOf("\n", word.length - 2) !== -1) {
                    output.push(line);
                    line = "";
                }
            }
        }
    }
    if (line != "") output.push(line);

    // update MFD so that new text goes on the bottom.
    var lines = this._currentMFDText.split("\n");
    // drop the first line (which will say "MISSION COMMS")
    lines.shift();
    lines = lines.concat(output);

    var final = "";
    if (lines.length == 0 || (lines.length == 1 && lines[0] == "")) {
    } else {
        if (lines.length <= 9) {
            final += lines.join("\n");
        } else {
            for (var i = lines.length - 9; i < lines.length; i++) {
                final += (final == "" ? "" : "\n") + lines[i];
            }
        }
    }
    final = expandMissionText("IR_mission_comms_header") + "\n" + final;
    this._currentMFDText = final;

    return final;
}

//-------------------------------------------------------------------------------------------------------------
// updates the text of the MFD
this.$updateMFD = function $updateMFD(msgkey, params, src, srcname) {
    if (!params) params = {};
    var p = player.ship;
    var adding = {};
    var currmsg = expandMissionText(msgkey, params);
    if (src && (!srcname || srcname == "")) {
        currmsg = src.displayName + ": " + currmsg;
        adding.source = src.displayName;
    }
    if (srcname && srcname != "") {
        currmsg = srcname + ": " + currmsg;
        adding.source = srcname;
    }
    if (!src && !srcname) {
        currmsg = expandMissionText("IR_commander_name") + ": " + currmsg;
        adding.source = expandMissionText("IR_commander_name");
    }
    // store the message key for later
    adding.key = msgkey;
    adding.params = params;
    this._messageStore.push(adding);

    // if we're receiving this when docked, just make it a console message
    if (p.docked) {
        player.consoleMessage(currmsg);
        return;
    }

    // if the hud is hidden don't try an update - it will get confusing as to which mfd slot is open or not.
    if (p.hudHidden === false) {
        // find the slot currently set for this MFD
        this.$findMFDID();
        // if we haven't got a set slot (this._mfdID === -1) or the set slot we had is now unavailable...
        if (this._mfdID === -1 ||
            (p.multiFunctionDisplayList[this._mfdID] && p.multiFunctionDisplayList[this._mfdID] !== "" && p.multiFunctionDisplayList[this._mfdID] !== this._mfdName)) {
            // find a free slot
            // first, make sure we reset our slot id marker (in the case where the previous one is in use)
            this._mfdID = -1;
            // search for a free slot
            for (var i = 0; i < p.multiFunctionDisplayList.length; i++) {
                if (!p.multiFunctionDisplayList[i] || p.multiFunctionDisplayList[i] === "") {
                    this._mfdID = i;
                    break;
                }
            }
        }
    }
    // also send this as a comms message if the src is in range
    if (src && src.isInSpace) {
        if (p.position.distanceTo(src) < p.scannerRange) {
            // yay! comms is working!
            src.commsMessage(currmsg, p);
        }
    } else {
        // oh well, make it a console message
        if (srcname && srcname != "") {
            worldScripts["IR-main-script.js"].$sendSystemComms(srcname, currmsg);
        } else {
            player.commsMessage(currmsg);
        }
    }
    // set the text in the MFD
    var output = this.$processText(currmsg);
    p.setMultiFunctionText(this._mfdName, output, false);
    // we have a free slot, so force the mfd to display
    if (this._mfdID !== -1) {
        p.setMultiFunctionDisplay(this._mfdID, this._mfdName);
    }
}

//-------------------------------------------------------------------------------------------------------------
// converts the log array to a single string
this.$convertDataToString = function () {
    var log = "";
    for (var i = 0; i < this._messageStore.length; i++) {
        log += (this._messageStore[i].source != "" ? this._messageStore[i].source + ": " : "") + 
            (this._messageStore[i].key && this._messageStore[i].key != "" ? expandMissionText(this._messageStore[i].key, this._messageStore[i].params) : "") + 
            (this._messageStore[i].text && this._messageStore[i].text != "" ? this._messageStore[i].text : "") + "\n";
    }
    return log;
}

//-------------------------------------------------------------------------------------------------------------
this.$convertDataToArray = function () {
    var log = [];
    for (var i = 0; i < this._messageStore.length; i++) {
        log.push((this._messageStore[i].source != "" ? this._messageStore[i].source + ": " : "") + 
            (this._messageStore[i].key && this._messageStore[i].key != "" ? expandMissionText(this._messageStore[i].key, this._messageStore[i].params) : "") + 
            (this._messageStore[i].text && this._messageStore[i].text != "" ? this._messageStore[i].text : ""));
    }
    return log;
}

//-------------------------------------------------------------------------------------------------------------
// records the index of the MFD that currently holds the mfd
this.$findMFDID = function $findMFDID() {
    var p = player.ship;
    for (var i = 0; i < p.multiFunctionDisplayList.length; i++) {
        if (p.multiFunctionDisplayList[i] === this._mfdName) this._mfdID = i;
    }
}

//-------------------------------------------------------------------------------------------------------------
// hides all instances of the MFD
this.$autoHideMFD = function $autoHideMFD() {
    var p = player.ship;
    if (p && p.multiFunctionDisplayList) {
        for (var i = 0; i < p.multiFunctionDisplayList.length; i++) {
            if (p.multiFunctionDisplayList[i] === this._mfdName) {
                p.setMultiFunctionDisplay(i, "");
            }
        }
    }
}

//-------------------------------------------------------------------------------------------------------------
this.$turnOffMFD = function $turnOffMFD() {
    // turn off the MFD (if it was used)
    this._currentMFDText = "";
    this._final = false;
    this._displayPoint = 0;
    this._target = null;
    if (this._mfdID !== -1) {
        if (this._subMessageTimer && this._subMessageTimer.isRunning) this._subMessageTimer.stop();
        this._subMessageTimer = new Timer(this, this.$autoHideMFD, 3, 0);
    }
}

//-------------------------------------------------------------------------------------------------------------
this.$showCommLog = function $showCommLog() {
    this._syslines = [];
    this.$reformatData(this.$convertDataToArray(), this._syslines, this._msCols);

    this._maxpage = Math.ceil(this._syslines.length / this._msRows);
    this._curpage = 0;
    this.$showPage();
}

//-------------------------------------------------------------------------------------------------------------
this.$showPage = function $showPage() {
    var p = player.ship;

    if (this.$isBigGuiActive() === false) p.hudHidden = true;

    var text = this.$commLogPage(this._curpage, this._msRows);
    var opts;
    var curChoices = {};
    var def = "09_NEXT";

    if (this._maxpage <= 1) {
        def = "99_EXIT";
        curChoices["01_PREV"] = { text: expandMissionText("IR_prev_page"), color: "darkGrayColor", unselectable: true };
        curChoices["09_NEXT"] = { text: expandMissionText("IR_next_page"), color: "darkGrayColor", unselectable: true };
    } else {
        if (this._curpage === 0) {
            curChoices["01_PREV"] = { text: expandMissionText("IR_prev_page"), color: "darkGrayColor", unselectable: true };
            curChoices["09_NEXT"] = { text: expandMissionText("IR_next_page"), color: this._menuColor };
        } else {
            curChoices["01_PREV"] = { text: expandMissionText("IR_prev_page"), color: this._menuColor };
            if (this._curpage + 1 === this._maxpage) {
                def = "01_PREV";
                curChoices["09_NEXT"] = { text: expandMissionText("IR_next_page"), color: "darkGrayColor", unselectable: true };
            } else {
                curChoices["09_NEXT"] = { text: expandMissionText("IR_next_page"), color: this._menuColor };
            }
        }
    }

    curChoices["99_EXIT"] = { text: expandMissionText("IR_exit_log"), color: this._exitColor };

    var opts = {
        screenID: "IR_comm_log",
        title: expandMissionText("IR_mission_comms_log_title", { page: (this._curpage + 1).toString(), max: (this._maxpage == 0 ? 1 : this._maxpage.toString()) }),
        allowInterrupt: true,
        choices: curChoices,
        exitScreen: "GUI_SCREEN_INTERFACES",
        overlay: { name: "IR_commlog_radar.png", height: 546 },
        initialChoicesKey: this._lastchoice ? this._lastchoice : def,
        message: text
    };
    mission.runScreen(opts, this.$logHandler.bind(this));
}

//-------------------------------------------------------------------------------------------------------------
this.$commLogPage = function (cpage, lines) {
    var output = "";

    var iStart = 0;
    var iEnd = 0;
    var textCount = 0;

    // set out initial end point
    iEnd = iStart + lines;
    if (cpage != 0) {
        iStart = cpage * lines;
        iEnd = iStart + lines;
    }
    if (iEnd > this._syslines.length) iEnd = this._syslines.length;

    for (var i = iStart; i < iEnd; i++) {
        textCount += this._syslines[i].trim().length;
        output += this._syslines[i] + "\n";
    }

    if (cpage === 0 && iStart === 0 && iEnd === 0 && (this._syslines[0] == null || textCount == 0)) output = "(empty)";

    return output;
}

//-------------------------------------------------------------------------------------------------------------
this.$logHandler = function $logHandler(choice) {
    delete this._lastchoice;

    var newChoice = null;

    if (!choice) {
        if (this._debug) log(this.name, "no choice");
        return; // launched while reading?
    } else if (choice === "01_PREV") {
        if (this._curpage > 0) this._curpage -= 1;
    } else if (choice === "09_NEXT") {
        if (this._curpage < this._maxpage - 1) this._curpage += 1;
    }
    this._lastchoice = choice;
    if (newChoice) this._lastchoice = newChoice;

    if (choice != "99_EXIT") {
        this.$showPage();
    }
}

//-------------------------------------------------------------------------------------------------------------
this.$reformatData = function (srcArray, destArray, lineWidth) {
    //rebuild comms into wide screen format
    var msg = "";
    for (var i = 0; i < srcArray.length; i++) {
        if (srcArray[i].substring(0, 1) != " " && msg != "") {
            // this is a new message, so output the current one and reset
            this.$addMsgToArray(msg, destArray, lineWidth);
            msg = "";
        }
        // add the message line to the current message
        // we'll trim off any leading and trailing spaces and add them back manually
        msg += srcArray[i].trim() + " ";
    }
    if (msg != "") {
        // output the remaining message
        //output = output + "> " + msg;
        this.$addMsgToArray(msg, destArray, lineWidth);
    }
}

//-------------------------------------------------------------------------------------------------------------
// adds a message to an array, splitting the lines based on a line width
this.$addMsgToArray = function (msg, ary, linewidth) {
    var prt = "";
    if (defaultFont.measureString(msg) > linewidth) {
        var words = msg.split(" ");
        var iPoint = 0
        var nextWord = "";
        do {
            // add the space in (after the first word)
            if (prt != "") {
                prt += " ";
            }
            // add the word on
            prt = prt + words[iPoint];
            // get the next word in the array
            if (iPoint < words.length - 1) {
                nextWord = " " + words[iPoint + 1];
            } else {
                nextWord = ""
            }
            // check the width of the next with the nextword added on
            if (defaultFont.measureString(prt + nextWord) > linewidth) {
                // hit the max width - add the line and start again
                ary.push(prt);
                // subsequent lines of a message will be indented slightly
                prt = " ";
            }
            iPoint += 1;
            // check for the end of the array, and output remaining text
            if ((iPoint >= words.length) && (prt.trim() != "")) {
                ary.push(prt);
            }
        } while (iPoint < words.length);
        words = [];
    } else {
        ary.push(msg);
    }
}

//-------------------------------------------------------------------------------------------------------------
// returns true if a HUD with allowBigGUI is enabled, otherwise false
this.$isBigGuiActive = function $isBigGuiActive() {
    if (oolite.compareVersion("1.83") <= 0) {
        return player.ship.hudAllowsBigGui;
    } else {
        var bigGuiHUD = ["XenonHUD.plist", "coluber_hud_ch01-dock.plist"]; // until there is a property we can check, I'll be listing HUD's that have the allow_big_gui property set here
        if (bigGuiHUD.indexOf(player.ship.hud) >= 0) {
            return true;
        } else {
            return false;
        }
    }
}
