/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2026 Rainbownapkin and the TTN Community

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.*/

/**
 * Class for handling incoming Private Messages for the entire client
 */
class pmHandler{
    /**
     * Instantiates a new Private Message Handler object
     * @param {channel} client - Parent client Management Object
     */
    constructor(client){
        /**
         * Parent client management object
         */
        this.client = client;

        /**
         * PM Icon in the main chat bar
         */
        this.pmIcon = document.querySelector('#chat-panel-pm-icon');

        /**
         * List of PM Sessions
         */
        this.seshList = new Map();

        /**
         * List of open PM Panels
         */
        this.panelList = new Map();

        /**
         * PM RX Sound
         */
        this.rxSound = '/nonfree/imrecv.ogg';

        /**
         * Open Sesh Sound
         */
        this.openSeshSound = '/nonfree/opensesh.ogg';

        /**
         * End Sesh Sound
         */
        this.endSeshSound = '/nonfree/closesesh.ogg';

        this.defineListeners();
        this.setupInput();
    }

    /**
     * Defines network related event listeners for PM Handler
     */
    defineListeners(){
        this.client.pmSocket.on("message", this.handlePM.bind(this));
        this.client.pmSocket.on("sent", this.handlePM.bind(this));
    }

    /**
     * Defines inpet related event listeners for PM handler
     */
    setupInput(){
        this.pmIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new pmPanel(client))});
    }

    /**
     * Handles received Private Messages from the PM service on the server, organizing it into the proper session from the sesh list
     * Or creating a new sesh where a matching one does not a exist
     * @param {object} data - Private Message data from the server
     */
    handlePM(data){
        //Store whether or not current message has been consumed by an existing sesh
        let consumed = false;

        //Store whether or not we have this sesh open in an existing PMPanel
        let displayed = false;

        //Pull session name from static generation method
        const nameObj = pmHandler.genSeshName(data);

        //For each existing sesh
        for(const seshEntry of this.seshList){
            //Pull sesh object from map entry
            const sesh = seshEntry[1];

            //If currently checked sesh ID matches calculated message sesh id
            if(sesh.id == nameObj.name){
                //Dump collected message into the matching session
                sesh.messages.push(data);

                //Set sesh to unread
                sesh.unread = true;

                //For each open PM Panel
                for(const panel of this.panelList){
                    //If sesh ID matches an open sesh in an existing panel
                    if(sesh.id == panel[1]){
                        //Set sesh to read
                        sesh.unread = false;
                    }
                }

                //If the message was unread
                if(sesh.unread){
                    //Notify user of new message/sesh
                    this.handlePing();
                }

                //Add sesh to sesh map
                this.seshList.set(sesh.id, sesh);

                //Let the rest of the method know that we've consumed this message
                consumed = true;
            }
        }

        //If we made it through the loop without consuming the message
        if(!consumed){
            //Generate a new sesh
            const sesh = new pmSesh(data, client);


            //Notify user of new message/sesh
            this.handlePing((data.msg == '' || data.msg == null));

            //Add it to the sesh list
            this.seshList.set(sesh.id, sesh);
        }

        //If this isn't an empty message (sesh-starter), and PM's always make noise, and we didn't send the message
        if(data.msg != '' && data.msg != null && localStorage.getItem('rxPMSound') == 'all' && data.user != this.client.user.user){
            //make sum noize!
            utils.ux.playSound(this.rxSound);
        }
    }

    handlePing(newSesh = false){
        //Light up the icon
        this.pmIcon.classList.add('positive-inverse');

        if(newSesh && (localStorage.getItem('newSeshSound') == 'true')){
            utils.ux.playSound(this.openSeshSound);
        }else if(localStorage.getItem('rxPMSound') == 'unread'){
            utils.ux.playSound(this.rxSound);
        }
    }

    //Handles UI updates after reading all messages
    checkAllRead(){
        //For each sesh
        for(const sesh of this.seshList){
            //If a sesh is unread
            if(sesh[1].unread){
                //LOOK OUT BOYS, THIS ONE'S BEEN READ! CHEESE IT!
                return;
            }
        }

        //Unlight the icon
        this.pmIcon.classList.remove('positive-inverse');
    }

    readSesh(panelID, seshID){
        //Set current sesh for panel
        this.panelList.set(panelID, seshID);

        //Get requested session
        const sesh = this.seshList.get(seshID);

        //Set it to unread
        sesh.unread = false;

        //Commit sesh back to sesh list
        this.seshList.set(sesh.id, sesh);

        //Check if all messages are read and handle em' if they are
        this.checkAllRead();
    }

    static genSeshName(message){
        const recipients = [];

        //Manually iterate through recipients
        for(const member of message.recipients){
            //check to make sure we're not adding ourselves
            if(member != client.user.user){
                //Copy relevant array members by value instead of reference
                recipients.push(member);
            }
        }

        //If this wasn't our message
        if(message.user != client.user.user){
            //Push sender onto members list
            recipients.push(message.user);
        }

        //Sort recipients
        recipients.sort();

        return {
            name: recipients.join(', '),
            recipients
        }
    }
}

/**
 * Class which represents an existing Private Messaging session between two or more users
 */
class pmSesh{
    /**
     * Instatiates a new pmSession object
     * @param {Object} message - Initial Private Message object from server that initiated the session
     */
    constructor(message, client){
         /**
         * Parent client management object
         */
        this.client = client;       

        const nameObj = pmHandler.genSeshName(message);

        /**
         * Members of session excluding the currently logged in user
         */
        this.recipients = nameObj.recipients

        /**
         * Name of the chat sesh, named after out-going recipients
         */
        this.id = nameObj.name;

        /**
         * Whether or not we have unread messages within the session
         */
        this.unread = true;

        /**
         * Array containing all session messages
         */
        this.messages = (message.msg == "" || message.msg == null) ? [] : [message];
    }
}