const { __glob, __web } = require("./global-variables");
const { LogType } = require("loguix");
const log = require("loguix");
const auth = require("./sub-auth");
const cook = require("cookie")
const wlog = new LogType("Web")
const subplayer = require('./sub-player');
const cors = require("cors")

const { List } = require("./sub-list")
const subplaylist = require("./sub-playlist")
const nodesfinder = require("./nodes-finder")
const { Server } = require("socket.io")
var fs = require("fs")
var path = require("path")
const { Metric } = require("webmetrik");
const { type } = require("os");
const markdownit = require("markdown-it")({
    html: true,
    linkify: true,
    typographer: true
})

/// <reference path="./types.d.ts" />

module.exports.WebServer = class {

    constructor() {

        wlog.step.init("start_server", "Démarrage du serveur Express (Web)")
        init()
      
    }

}

function init() {
    const createError = require('http-errors');
    const express = require('express');
    const cookieParser = require('cookie-parser');
    const favicon = require('express-favicon');

    const app = express();

    const port = normalizePort(process.env.PORT || '4000');


    const server = require('http').createServer(app);
    const io = require('socket.io')(server)
    

    const indexRouter = require(__web.ROUTER + "index.js");
    const loginRouter = require(__web.ROUTER + "login.js");
    const internalRouter = require(__web.ROUTER + "internal.js")

    IOConnection(io)
    
    
    app.set('views', __web.TEMPLATES); // general config
    app.set('view engine', 'ejs');
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(__web.PUBLIC));
    app.use(cors({
        origin: 'chrome-extension://fplckpgomdhnfhhokenalnpkmifabbja'
    }));    
    app.set('port', port);
    app.use('/', indexRouter);
    app.use('/login', loginRouter);
    app.use("/internal", internalRouter)
    app.use("/userspictures", express.static(__glob.PICTURE_DIR))
    app.use(favicon(__web.ICON));

    app.use(function (req, res, next) {
        next(createError(404));
    });

    app.use(function (err, req, res, next) {
 
        // Set locals, only providing error
        // in development
        res.locals.message = err.message;
        res.locals.error = req.app.get('env')
            === 'development' ? err : {};
     
        // render the error page
        res.status(err.status || 500);
        res.render('error');
    });

    server.listen(port);
    server.on('error', (error) => {
        if (error.syscall !== 'listen') {
            throw error;
        }
     
        let bind = typeof port === 'string'
            ? 'Pipe ' + port
            : 'Port ' + port;
     
        // Handle specific listen errors with
        // friendly messages
        switch (error.code) {
            case 'EACCES':
                console.error(bind
                    + ' requires elevated privileges');
                process.exit(1);
                break;
            case 'EADDRINUSE':
                console.error(bind + ' is already in use');
                process.exit(1);
                break;
            default:
                throw error;
        }

    });
    server.on('listening', () => {

        let addr = server.address();
        let bind = typeof addr === 'string'
            ? 'pipe ' + addr
            : 'port ' + addr.port;
        wlog.log("Serveur démarré sur le port : " + bind )
    });

    wlog.step.end("start_server")

}

// EXPRESS FUNCTIONS

function normalizePort(val) {
    let port = parseInt(val, 10);
 
    if (isNaN(port)) {
 
        // Named pipe
        return val;
    }
 
    if (port >= 0) {
 
        // Port number
        return port;
    }
 
    return false;
}
 

/**
 * 
 * @param {Server} io 
 */
function IOConnection(io) {
    /**
     * @type {LogType}
     */
    const alog = log.getInstance("Authentification")

    const usersOnline = new Array()
    
    process.on("UPDATE_SELF", () => {
            
        if(io) {
    
            AlwaysRequest("UPDATE_SELF")
            AlwaysRequest("ALL_CONNECTED_USER", usersOnline)
        }    
        
    })

    process.on("UPDATE_NODES", () => {

        if(io) {

            const data = nodesfinder.getNodesData()
            AdminRequest("NODES", data)
        }    
    
    })

    process.on("PLAYLIST_REFRESH", () => {
        if(io) {
            AlwaysRequest("PLAYLIST_REFRESH", "OK")
        }
    })


    process.on("MUSIC_UPDATE_STATE", () => {
        if(io) {
            const data = subplayer.updateMusicState()
            AlwaysRequest("MUSIC_STATE", data)
        
        }
    })

    io.on("connection", (socket) => {

        var token = cook.parse(socket.handshake.headers.cookie).token
        var user = auth.getUser(token)
        if(user) {

            if(user.admin == true) {
                socket.join("admin")    

            }

            const online_users_data = auth.getSimpleUser(token)
                
            if(online_users_data) {
                // Check if user is already connected and if not push it
                if(!usersOnline.find(item => item.username == online_users_data.username)) {
                    usersOnline.push(online_users_data)
     
                }
        
                alog.log(online_users_data.username + " - Connexion du serveur avec le socket : " + socket.id + " - " + socket.handshake.address + " - " + socket.handshake.headers["user-agent"])
            }

            AlwaysRequest("ALL_CONNECTED_USER", usersOnline)
     
            socket.on("disconnect", () => {
                alog.log(user.user.username + " - Déconnexion du serveur - Socket : " + socket.id)
                socket.leave("admin")
                var index = usersOnline.indexOf(online_users_data)
                if(index > -1) {
                    usersOnline.splice(index, 1)
                }
                AlwaysRequest("ALL_CONNECTED_USER", usersOnline)
            })

            GetRequest("USER_INFO", () => {

                GetAnswer("USER_INFO", user)
                alog.log("Envoi des informations Discord de '" + user.user.username + "' à '" + socket.id + "'" )
            })

            GetRequest("USER_LIST", () => {
                
                GetAnswer("USER_LIST", auth.getSimpleUsers())
             
            })

            GetRequest('MUSIC_STATE', () => {
                
                const data = subplayer.updateMusicState()
          
                GetAnswer("MUSIC_STATE", "Bienvenue " + user.user.username + " ! " )
                AlwaysRequest("MUSIC_STATE", data)  
            })

            GetRequest("PLAYLIST", () => {
                    
                const playlistResult = subplaylist.getUser(user.user.id)
                GetAnswer("PLAYLIST", playlistResult)
            })

            GetRequest("PAUSE", () => {
                subplayer.pause()
                GetAnswer("PAUSE", "OK")
            })

            GetRequest("BACKWARD", () => {
                subplayer.previous()
                GetAnswer("BACKWARD", "OK")
            })

            GetRequest("FORWARD", () => {
                subplayer.skip()
                GetAnswer("FORWARD", "OK")
            })

            GetRequest("LOOP", () => {
                subplayer.loop()
                GetAnswer("LOOP", "OK")
            })

            GetRequest("SHUFFLE", () => {
                subplayer.changeShuffle()
                GetAnswer("SHUFFLE", "OK")
            })

            GetRequest("DISCONNECT", () => {
                subplayer.leave()
                GetAnswer("DISCONNECT", "OK")
          
            })

            GetRequest("NODES", () => {
                process.emit("UPDATE_NODES")
                GetAnswer("NODES", "OK")
            })

            GetRequest("ALL_CONNECTED_USER", () => {
                GetAnswer("ALL_CONNECTED_USER", "OK")
                AlwaysRequest("ALL_CONNECTED_USER", usersOnline)
            })

            GetRequest("README", () => {   
                var content = markdownit.render(fs.readFileSync(__glob.README).toString()) 
                GetAnswer("README", content)
              
            })

            GetRequest("OOBE_CHECK", () => {
                GetAnswer("OOBE_CHECK", auth.getOOBE(user.user.id))
            })

            GetRequest("OOBE_VALID", () => {
                auth.setOOBE(user.user.id)
                GetAnswer("OOBE_VALID", "OK")
            })

            
            GetRequest("MOVEOUT", () => {
                subplayer.moveOut(user.user.id)
                GetAnswer("MOVEOUT", "OK")
            })
            
            PostRequest("SEEK", (data) => {
                subplayer.seek(data)
                PostAnswer("SEEK", "OK")
            })

            PostRequest("VOLUME", (data) => {
                subplayer.setVol(data)
                PostAnswer("VOLUME", "OK")
            })

            PostRequest("PLAY_QUEUE", (data) => { 
                var sublist = new List()  
                subplayer.addSong(sublist.playQueue(data), user.user.id, true)
                PostAnswer("PLAY_QUEUE", "OK")

            })

            PostRequest("CHANGE_QUEUE", (data) => {
                var sublist = new List()
                sublist.changeQueue(data)
                PostAnswer("CHANGE_QUEUE", "OK")
           
            })

            PostRequest("DELETE_QUEUE", (data) => {
                var sublist = new List()
                sublist.removeByIndex(data)

                PostAnswer("DELETE_QUEUE", "OK")
 
            })

            PostRequest("DELETE_ALL_QUEUE", () => {
                var sublist = new List()
                sublist.removeAll()

                PostAnswer("DELETE_QUEUE", "OK")
        
            })

            PostRequest("MOVE_QUEUE", (data) => {
                var sublist = new List()
                sublist.moveUp(data)

                PostAnswer("MOVE_QUEUE", "OK")
      
            })

            PostRequest("MOVE_QUEUE_BY_ENTIRE", (data) => {
                var sublist = new List()
                sublist.replaceList(data)

                PostAnswer("MOVE_QUEUE_BY_ENTIRE", "OK")
            
            })

            PostRequest("REPORT", (data) => {

                data.username = user
                subplayer.report(null, null, data)
                PostAnswer("REPORT", "OK")
             
            })

            PostRequest("SEARCH", async (data) => {
                const results = await subplayer.search(data)
                PostAnswer("SEARCH", results)
            })

            PostRequest("ADD_SONG", async (data) => {
                subplayer.addSong(data, user.user.id)
                PostAnswer("ADD_SONG", "OK")
             
                playStats(user.user.username)
            })

            PostRequest("ADD_SONG_NOW", async (data) => {
                subplayer.addSong(data, user.user.id, true)
                PostAnswer("ADD_SONG_NOW", "OK")
                
                playStats(user.user.username)
            })

            PostRequest("FP_ADD_SONG", async (data) => {
                subplayer.addSong(data, user.user.id, false, true)
                PostAnswer("FP_ADD_SONG", "OK")
              
                playStats(user.user.username)
            })

            PostRequest("FP_ADD_SONG_NOW", async (data) => {
                subplayer.addSong(data, user.user.id, true, true)
                PostAnswer("FP_ADD_SONG_NOW", "OK")
               
                playStats(user.user.username)
            })

            PostRequest("PLAY_PLAYLIST", async (data) => {
                subplaylist.playPlaylist(user.user.id, data)
                PostAnswer("PLAY_PLAYLIST", "OK")
               
                playStats(user.user.username)
            })

            PostRequest("PLAY_PLAYLIST_NOW", async (data) => {
                subplaylist.playPlaylist(user.user.id, data, true)
                PostAnswer("PLAY_PLAYLIST_NOW", "OK")

                playStats(user.user.username)
            })


            PostRequest("CREATE_PLAYLIST", async (data) => {
                subplaylist.addPlaylist(user.user.id, data)
                socket.emit("DO_UPDATE_PLAYLIST")
                PostAnswer("CREATE_PLAYLIST", "OK")
               
            })

            PostRequest("DELETE_PLAYLIST", async (data) => {
                subplaylist.removePlaylist(user.user.id, data)
                socket.emit("DO_UPDATE_PLAYLIST")
                PostAnswer("DELETE_PLAYLIST", "OK")
            })

            PostRequest("SEND_PLAYLIST", async (data) => {
                subplaylist.copyPlaylist(user.user.id, data.key, data.dest)
                socket.emit("DO_UPDATE_PLAYLIST")
                PostAnswer("SEND_PLAYLIST", "OK")
            })

            PostRequest("RENAME_PLAYLIST", async (data) => {
                subplaylist.renamePlaylist(user.user.id, data.id, data.name)
                socket.emit("DO_UPDATE_PLAYLIST")
                PostAnswer("RENAME_PLAYLIST", "OK")
            })

            PostRequest("ADD_SONG_TO_PLAYLIST", async (data) => {
                subplaylist.addSong(user.user.id, data.id, data.url)
                socket.emit("DO_UPDATE_PLAYLIST")
                PostAnswer("ADD_SONG_TO_PLAYLIST", "OK")
            })

            PostRequest("DELETE_SONG_TO_PLAYLIST", async (data) => {
                subplaylist.removeSong(user.user.id, data.id, data.identifier)
                socket.emit("DO_UPDATE_PLAYLIST")
                PostAnswer("DELETE_SONG_TO_PLAYLIST", "OK")
            })


            PostRequest("FP_PLAY_PLAYLIST", async (data) => {  
                subplayer.addSongsFromPlaylist(data, user.user.id)
                PostAnswer("FP_PLAY_PLAYLIST", "OK")
               
            })

            PostRequest("FP_PLAY_PLAYLIST_NOW", async (data) => {  
                subplayer.addSongsFromPlaylist(data, user.user.id, true)
                PostAnswer("FP_PLAY_PLAYLIST", "OK")
          
            })

            PostRequest("LYRICS", async (data) => {
         
               if(typeof data == "string") {
                   data = {"url": data}
               } else {
                    let player = require("./discord-bot").getClient().manager.players.get("137291455336022018")
                    PostAnswer("LYRICS", player.lyrics()) 
               }
            })

          
            


            if(user.admin == true) {
                GetRequest("LOGS", () => {
                    const logs_data = new Array()
                    const logs_folder = fs.readdirSync(__glob.LOGS)

                    for(var log of logs_folder) {

                        logs_data.push({"name":log, "value": fs.readFileSync(__glob.LOGS + path.sep + log).toString()})
                    }

                    GetAnswer("LOGS", logs_data)
                })

                GetRequest("USERS", () => {
                    const users_data = auth.getUsers()
                    AdminRequest("USERS", users_data)
                })

                PostRequest("NODES/ADD", (data) => {
                    nodesfinder.addNodes(data)
                    PostAnswer("NODES/ADD", "OK")
                    process.emit("UPDATE_NODES")
                })

                PostRequest("NODES/DELETE", (data) => {
                    nodesfinder.deleteNode(data)
                    PostAnswer("NODES/DELETE", "OK")
                    process.emit("UPDATE_NODES")
                })
                
                PostRequest("NODES/RELOAD", (data) => {
                    nodesfinder.reloadNode(data)
                    PostAnswer("NODES/RELOAD", "OK")
                    process.emit("UPDATE_NODES")
                })


                PostRequest("USERS/ADMIN", (data) => {
                    auth.setAdmin(data)
                    PostAnswer("USERS/ADMIN", "OK")
                    const users_data = auth.getUsers()
                    AdminRequest("USERS", users_data)
                })

                PostRequest("USERS/BAN", (data) => {
                    auth.setBan(data)
                    PostAnswer("USERS/BAN", "OK")
                    const users_data = auth.getUsers()
                    AdminRequest("USERS", users_data)
                })

                PostRequest("USERS/DELETE", (data) => {
                    auth.removeUser(data)
                    PostAnswer("USERS/DELETE", "OK")
                    const users_data = auth.getUsers()
                    AdminRequest("USERS", users_data)
                })

                GetRequest("RESTART", () => {
                    const pm2 = require('pm2');
                    const child_process = require('child_process')
                    let req = child_process.exec(`ssh -o StrictHostKeyChecking=no raphix@omega.raphix.fr sudo -S systemctl restart lavalink `)
                    pm2.restart('Subsonics')
                    GetAnswer("RESTART", "OK")
                
                })
    
                



            }
    
        }

        function GetRequest(GQname, GQcallback) {
            socket.on("GET/" + GQname, () => {
            
                wlog.log(user.user.username + " - Socket : " + socket.id  + " - GET/" + GQname + " - [RECIEVED]") 
                GQcallback()
            })
        
        }
        
        function GetAnswer(GRname, GRvalue) {
        
            wlog.log(user.user.username + " - Socket : " + socket.id  + " - GET/" + GRname + " - [ANSWERED]") 
            socket.emit("ANSWER/GET/" + GRname, GRvalue)
        
        }
        
        function PostRequest(GQname, GQcallback) {
            socket.on("POST/" + GQname, (value) => {
                wlog.log(user.user.username + " - Socket : " + socket.id  + " - POST/" + GQname + " - [RECIEVED]") 
                GQcallback(value)
            })
        
        }
        
        function PostAnswer(GRname, GRvalue) {
        
            wlog.log(user.user.username + " - Socket : " + socket.id  + " - POST/" + GRname + " - [ANSWERED]") 
            socket.emit("ANSWER/POST/" + GRname, GRvalue)
            
        }

   

    })


   


    function AlwaysRequest(GRname, GRvalue) {

        io.sockets.emit("ALWAYS/" + GRname, GRvalue)
    
    }

    
    function AdminRequest(GRname, GRvalue) {

        io.to("admin").emit("ALWAYS/" + GRname, GRvalue)
    
    }

  

    function playStats(username) {
        var userMusicPlayed = new Metric("userMusicPlayed_" + username, "Nombre de musiques jouées par l'utilisateur : " + username)
        userMusicPlayed.setValue(userMusicPlayed.getValue() + 1)
        
    }

}
