//imports
import * as log from '../util/logging.js'
import * as resp from '../interface/rest/response.js'
import { mkstring } from '../util/token.js'
import * as logic from './UnoRules.js'
import { UnoChat } from './UnoChat.js'
import { UnoSockets } from './UnoSocket.js'
import WebSocket from 'ws';

import jsonCards from '../../config/cards.json'
import jsonConf from '../../config/game.json'
import { shuffle } from '../util/shuffle.js'
import { UIVote } from './UiVote.js'

export interface UnoCard {
    id: number;
    color: string;
    number: string;
    tx: string;
}

export interface UnoPlayer {
    nickname: string;
    lastseen: Date;
    userId: string;
    ready: boolean;
    uno: boolean;
    cardDrawn: boolean;
    score: number;
    rounds: number;
    hand: UnoCard[];
    socket: WebSocket;
}1

export interface UnoExtraRules {
    sw_stacking: boolean;
    sw_jumpin: boolean;
    sw_double: boolean;
    sw_sleep: boolean;
    sw_seveno: boolean;
}

export class UnoSession {
    sessionID: number;      //SitzungsID
    players: UnoPlayer[];   //alle Spielerobjekte
    winners: UnoPlayer[];   //fertige spieler
    cardsPlayed: UnoCard[]; //gespielte Karten Satpel
    cardsMixed: UnoCard[];  //nachziehkarten Stapel
    started: boolean;       //spiel gestartet
    password: string;       //passwort f�r den zutritt
    currentPlayer: string;  //aktueller spieler
    direction: number;      //spielrichtung (1=vor, -1=zur�ck, 0=nicht gestartet)
    startcards: number;     //anzahl der maximalen karten
    maxPlayers: number;     //maximale spieleranzahl
    serverName: string;     //serever Name
    unoChat: UnoChat;
    sockets: UnoSockets;
    lastPlayedBy: UnoPlayer;
    testmode: boolean;
    activeVote: UIVote;

    sessionId: string;
    canDelete: boolean;

    extraRules: UnoExtraRules;

    sleepTimer;
    sleepValue;

    stackSize: number;      //anzahl der Karten die genzpgen werden müssen

    private checkTimer;

    constructor(sessionId,custom: any){
        this.extraRules = {
            sw_stacking: custom.sw_stacking ,
            sw_jumpin: custom.sw_jumpin ,
            sw_double: custom.sw_double ,
            sw_sleep: custom.sw_sleep ,
            sw_seveno: custom.sw_seveno ,
        }
        console.log(custom)
        this.players= [];
        this.winners= [];
        this.unoChat= new UnoChat(this);
        this.sockets= undefined;
        this.sessionId= sessionId;
        //konfigurationsdate laden
        log.logNotice("Spiel Starten. Lade Konfigurationsdatei...");
        let t_this = jsonConf;
        this.password = t_this.password;
        this.startcards = custom.startcards || t_this.startCards;
        this.maxPlayers = t_this.maxPlayers;
        this.serverName = t_this.serverName;
        this.testmode = t_this.testMode;
        this.sockets = new UnoSockets(this);
        this.activeVote = undefined;
        this.canDelete = false;
        this.initLobby();
        //kick inactive
        this.checkTimer = setInterval(()=>{
            [...this.players, ...this.winners].forEach(el =>{
                var dif = ((new Date()).getTime()- el.lastseen.getTime())/1000;
                if(!this.started && dif > 10){
                    this.kickUser(el, "10 Sekunden Inaktiv")
                }else if(this.started && dif > 60){
                    this.kickUser(el, "60 Sekunden Inaktiv")
                }else if(this.started && dif > 29 && dif < 31){
                    this.unoChat.writeMessage(el.nickname + " wird in 30 Sekunden wegen Inaktivität gekickt.")
                }
                else if(this.started && dif > 44 && dif < 46){
                    this.unoChat.writeMessage(el.nickname + " wird in 15 Sekunden wegen Inaktivität gekickt.")
                }
            })
            if ([...this.players, ...this.winners].length == 0){
                log.logInfo('Lobby empty. Closing...');
                this.canDelete = true;
                this.deleteSession();
            }
        },1000);
    }

    public deleteSession(){
        [...this.players, ...this.winners].forEach(el =>  el.socket.close())
        clearInterval(this.sockets.pingTimer)
        clearInterval(this.checkTimer)
        this.stopTimer();
    }

    private initLobby() {
        this.cardsPlayed = [];       //gespielte Karten Satpel
        this.cardsMixed = [];        //nachziehkarten Stapel
        this.started = false;      //spiel gestartet           
        this.currentPlayer = "";
        this.direction = 0;
        this.stackSize = 0;
        //Karten Laden
        log.logNotice("Spiel Starten. Lade Karten...");
        let cards = jsonCards.cards;
        let counter = 0;
        cards.forEach(el => {
            for (let i = 0; i < el.count; i++) {
                this.cardsMixed.push({
                    id: counter,
                    color: el.color,
                    number: el.number,
                    tx: el.tx
                });
                counter++;
            }
        });
        shuffle(this.cardsMixed); //Karten mischen
        shuffle(this.cardsMixed); //Karten mischen
        shuffle(this.cardsMixed); //Karten mischen
        shuffle(this.cardsMixed); //Karten mischen
        log.logNotice(this.cardsMixed.length.toString() + " Karten geladen.");
        this.clientUpdateStatus();
    }

    public restartGame() {
        this.players = [...this.players, ...this.winners];     //alle Spielerobjekte
        this.winners = [];
        shuffle(this.players);
        this.players.forEach(player => {
            player.hand = []; //übrige karten löschen
            player.ready = false;
            player.uno = false;
            player.cardDrawn = false;
            player.rounds++;
            
        })
        this.initLobby();
        this.clientUpdateHands();
        this.clientUpdateAllPlayers();
        
    }

    public playerJoin(nickname: string):any[] {
        if (this.started) {
            return [false,"Das Spiel wurde bereits gestartet."]
        } else {
            if (!nickname) {
                log.logError("User tried to connect without Nickname")
                return [false,"Du musst einen benutzernamen angeben."]

            }
            //wenn sonst soweit alles passt
            const playerObj: UnoPlayer = {
                nickname: nickname,
                lastseen: new Date(),
                userId: mkstring(20),
                ready: false,
                uno: false,
                cardDrawn: false,
                score: 0,
                rounds: 1,
                hand: [],
                socket: undefined,
            };
            
            this.players.push(playerObj);
            this.clientUpdatePlayer(playerObj);
            log.logNotice("Player " + nickname + " joined with id " + playerObj.userId);
            this.unoChat.writeMessage(nickname + " ist dem Spiel beigetreten");
            
            return [true,{
                userId: playerObj.userId
            }]
        }
    }

    public checkReady() {
        let all = 0;
        let ready = 0;
        this.players.forEach(function (obj, ix) {
            all++;
            if (obj.ready) { ready++; }
        });
        log.logNotice(ready + "/" + all + " Players are ready");
        this.unoChat.writeMessage(ready + "/" + all + " spieler sind Bereit");
        if (all == ready && all > 1) {
            log.logNotice("Start Game");
            this.startGame();
            logic.onStart(this);
        }
    }
    
    public startGame() {
        this.started = true;
        log.logNotice("Starting Game. Dealing Cards");
        for (let player_ix = 0; player_ix < this.players.length; player_ix++) {
            let pObj = this.players[player_ix];
            pObj.hand = [];
            for (let i = 0; i < this.startcards; i++) {
                pObj.hand.push(this.pullCardFromStack());
            }
            this.clientUpdatePlayer(pObj);
        }
        this.cardsPlayed.push(this.pullCardFromStack());

        log.logNotice("Cards dealt");
        
        this.unoChat.writeMessage("Die Runde wurde gestartet");
        this.currentPlayer = this.players[0].userId;
        this.direction = 1;
        this.clientUpdatePlayedCard()
        this.clientUpdateCurrentPlayer();
        this.clientUpdateStatus();
        this.clientUpdateHands();
    }
    
    private pullCardFromStack() {
        let card = this.cardsMixed[this.cardsMixed.length - 1];
        this.cardsMixed.pop();
        log.logNotice("Pulled Card form Stack. Remaining: " + this.cardsMixed.length);
        return card;
    }

    public register_socket(socket: WebSocket, id: string){
        this.sockets.register_socket(socket,id);
    }


    public initClient(player: UnoPlayer) {
        let respObj: any = {};
        //add hands
        let players_public = []
        let players_winners = []
        this.players.forEach((obj) => {
            players_public.push(this.getPlayerMeta(obj));
        });
        this.winners.forEach((obj) => {
            players_winners.push(this.getPlayerMeta(obj));
        });

        respObj.hand = player ? player.hand : [];
        respObj.serverName = this.serverName;
        respObj.cardsMixed = this.cardsMixed.length;
        respObj.cardsPlayed = this.cardsPlayed;
        respObj.started = this.started;
        respObj.direction = this.direction;
        respObj.currentPlayer = this.currentPlayer;
        respObj.players = players_public;
        respObj.winners = players_winners;
        respObj.extraRules = this.extraRules;
        respObj.extraRules.cards = this.startcards;
        respObj.stack = this.stackSize;
        player.socket.send(JSON.stringify({
            initClientData: respObj
        }))
    }

    public clientHearbeat(player: UnoPlayer){
        player.lastseen = new Date();
    }

    public findPlayerIndex(id) {
        let index = 0;
        this.players.forEach((obj, ix) => {
            if (obj.userId == id) {
                index = ix;
            }
        });
        return index;
    }

    public clientUpdatePlayer(player: UnoPlayer){ //modify or add player
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    player: "modify",
                    data: this.getPlayerMeta(player)
                }))
            }
        })
    }

    public clientRemovePlayer(player: UnoPlayer){ //remove player
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    player: "remove",
                    data: this.getPlayerMeta(player)
                }))
            }
        })
    }

    private getPlayerMeta(player: UnoPlayer){
        return {
            nickname: player.nickname,
            lastseen: player.lastseen,
            userId: player.userId,
            ready: player.ready,
            cards: player.hand ? player.hand.length : 0,
            uno: player.uno,
            score: player.score,
            rounds: player.rounds,
        }
    }

    public clientUpdateWinner(){ //modify or add player
        let players_winners = []
        this.winners.forEach((obj) => {
            players_winners.push(this.getPlayerMeta(obj));
        });
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){

                el.socket.send(JSON.stringify({
                    player: "winners",
                    data: players_winners
                }))
            }
        })
    }

    public clientUpdateAllPlayers(){
        let players_public = []
        let players_winners = []
        this.players.forEach((obj) => {
            players_public.push(this.getPlayerMeta(obj));
        });
        this.winners.forEach((obj) => {
            players_winners.push(this.getPlayerMeta(obj));
        });
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    player: "all",
                    players: players_public,
                    winners: players_winners,
                }))
            }
        })
    }

    public clientUpdatePlayedCard(){
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    gameUpdate: "playedStack",
                    card: this.cardsPlayed,
                }))
            }
        })
    }

    public clientUpdateCurrentPlayer(){
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    gameUpdate: "currentPlayer",
                    currentplayer: this.currentPlayer,
                    direction: this.direction,
                }))
            }
        })
    }

    public clientUpdateStatus(){
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    gameUpdate: "status",
                    started: this.started
                }))
            }
        })
    }

    public clientUpdateHands(){
        this.players.forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    hand: "all",
                    cards: el.hand
                }))
            }
        })
    }

    public clientAddCard(player: UnoPlayer, card: UnoCard){ //modify or add player
        if (player.socket && player.socket?.OPEN){
            player.socket.send(JSON.stringify({
                hand: "add",
                card: card
            }))
        }
    }

    public clientRemoveCard(player: UnoPlayer, card: UnoCard){ //modify or add player
        if (player.socket && player.socket?.OPEN){
            player.socket.send(JSON.stringify({
                hand: "remove",
                card: card
            }))
        }
    }

    public kickUser(player: UnoPlayer, reason: string){
        log.logNotice("Player " + player.nickname + " (" + player.userId + ") kicked for reason : " + reason);
        this.unoChat.writeMessage(player.nickname + " wurde gekickt. Grund: " + reason);
        if (player.socket && player.socket?.OPEN){
            player.socket.send(JSON.stringify({kick:reason}));
        }
        if(this.started){
            this.cardsMixed = [...player.hand, ...this.cardsMixed];
            if (player.userId == this.currentPlayer) {
                logic.advancePlayer(this, false, false);
            }
        }
        this.winners = this.winners.filter(el2 => el2.userId != player.userId);
        this.players = this.players.filter(el2 => el2.userId != player.userId);
        this.clientUpdateAllPlayers();
    }

    public startTimer(duration: number, event: Function){
        this.stopTimer();
        this.sendTimer(duration);
        this.sleepValue = duration;
        this.sleepTimer = setInterval(()=>{
            this.sleepValue --;
            this.sendTimer(this.sleepValue);
            if (this.sleepValue == -1){
                this.stopTimer();
                event();
            }
        },1000)
    }

    public stopTimer(){
        if (this.sleepTimer){
            clearInterval(this.sleepTimer)
        }
    }

    private sendTimer(value: number){
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    timer: value,
                }))
            }
        })
    }

    public clientPlaySound(player: UnoPlayer, sound: string){
        if (player.socket && player.socket?.OPEN){
            player.socket.send(JSON.stringify({
                sound: sound,
            }))
        }
    }

    public clientNotify(player: UnoPlayer, message: string, failed: boolean){
        if (player.socket && player.socket?.OPEN){
            player.socket.send(JSON.stringify({
                notify: message,
                warning: !failed,
            }))
        }
    }

    public addStackSize(value: number){
        this.stackSize += value;
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    stack: this.stackSize 
                }))
            }
        })
        this.sendStackSize();
    }

    public resetStackSize(){
        this.stackSize = 0;
        this.sendStackSize();
    }

    private sendStackSize(){
        [...this.players, ...this.winners].forEach(el =>{
            if (el.socket && el.socket?.OPEN){
                el.socket.send(JSON.stringify({
                    stack: this.stackSize 
                }))
            }
        })
    }
   
}
