/*
 * This file is a part of the SteamWar software.
 *
 * Copyright (C) 2023  SteamWar.de-Serverteam
 *
 * 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/>.
 */

package de.steamwar.sql;

import de.steamwar.sql.internal.Field;
import de.steamwar.sql.internal.SelectStatement;
import de.steamwar.sql.internal.Statement;
import de.steamwar.sql.internal.Table;
import lombok.AllArgsConstructor;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@AllArgsConstructor
public class UserElo {

    private static final int ELO_DEFAULT = 0;

    private static final Map<String, Map<Integer, Optional<Integer>>> gameModeUserEloCache = new ConcurrentHashMap<>();
    private static final Map<Integer, String> emblemCache = new ConcurrentHashMap<>();

    public static void clear() {
        gameModeUserEloCache.clear();
        emblemCache.clear();
    }

    private static final Table<UserElo> table = new Table<>(UserElo.class);
    private static final SelectStatement<UserElo> getElo = table.select(Table.PRIMARY);
    private static final Statement setElo = table.insertAll();

    private static final Statement place = new Statement("SELECT COUNT(*) AS Place FROM UserElo WHERE GameMode = ? AND Elo > ? AND Season = ?");
    private static final Statement fightsOfSeason = new Statement("SELECT COUNT(*) AS Fights FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID WHERE UserID = ? AND GameMode = ? AND UNIX_TIMESTAMP(StartTime) + Duration >= UNIX_TIMESTAMP(?)");

    @Field(keys = {Table.PRIMARY})
    private final int season;
    @Field(keys = {Table.PRIMARY})
    private final String gameMode;
    @Field(keys = {Table.PRIMARY})
    private final int userId;
    @Field
    private final int elo;

    public static int getEloOrDefault(int userID, String gameMode) {
        return getElo(userID, gameMode).orElse(ELO_DEFAULT);
    }

    public static Optional<Integer> getElo(int userID, String gameMode) {
        return gameModeUserEloCache.computeIfAbsent(gameMode, gm -> new HashMap<>()).computeIfAbsent(userID, uid -> Optional.ofNullable(getElo.select(Season.getSeason(), gameMode, userID)).map(userElo -> userElo.elo));
    }

    public static int getFightsOfSeason(int userID, String gameMode) {
        return fightsOfSeason.select(rs -> {
            if (rs.next())
                return rs.getInt("Fights");
            return 0;
        }, userID, gameMode, Season.getSeasonStart());
    }

    public static void setElo(int userId, String gameMode, int elo) {
        emblemCache.remove(userId);

        int oldPlacement = getPlacement(getElo(userId, gameMode).orElse(0), gameMode);
        int newPlacement = getPlacement(elo, gameMode);

        gameModeUserEloCache.getOrDefault(gameMode, Collections.emptyMap()).remove(userId);
        if (oldPlacement <= 3 || newPlacement <= 3) {
            emblemCache.clear();
        }

        setElo.update(Season.getSeason(), gameMode, userId, elo);
    }

    public static int getPlacement(int elo, String gameMode) {
        return place.select(rs -> {
            if (rs.next())
                return rs.getInt("Place") + 1;
            return -1;
        }, gameMode, elo, Season.getSeason());
    }

    public static String getEmblem(SteamwarUser user, List<String> rankedModes) {
        return emblemCache.computeIfAbsent(user.getId(), userId -> {
            int emblemProgression = -1;
            for (String mode : rankedModes) {
                if (UserElo.getFightsOfSeason(userId, mode) == 0) continue;
                int progression = getProgression(userId, mode);
                if (progression > emblemProgression) {
                    emblemProgression = progression;
                }
            }
            return toEmblem(emblemProgression);
        });
    }

    public static String getEmblemProgression(String gameMode, int userId) {
        switch (getProgression(userId, gameMode)) {
            case -1:
                return "§f/ §8∨ ∧ ∨ ∧ ∨ ∧ ❂ III II I";
            case 0:
                return "§8/ §6∨ §8∧ ∨ ∧ ∨ ∧ ❂ III II I";
            case 1:
                return "§8/ ∨ §6∧ §8∨ ∧ ∨ ∧ ❂ III II I";
            case 2:
                return "§8/ ∨ ∧ §7∨ §8∧ ∨ ∧ ❂ III II I";
            case 3:
                return "§8/ ∨ ∧ ∨ §7∧ §8∨ ∧ ❂ III II I";
            case 4:
                return "§8/ ∨ ∧ ∨ ∧ §e∨ §8∧ ❂ III II I";
            case 5:
                return "§8/ ∨ ∧ ∨ ∧ ∨ §e∧ §8❂ III II I";
            case 6:
                return "§8/ ∨ ∧ ∨ ∧ ∨ ∧ §5❂ §8III II I";
            case 7:
                return "§8/ ∨ ∧ ∨ ∧ ∨ ∧ ❂ §5III §8II I";
            case 8:
                return "§8/ ∨ ∧ ∨ ∧ ∨ ∧ ❂ III §5II §8I";
            case 9:
                return "§8/ ∨ ∧ ∨ ∧ ∨ ∧ ❂ III II §5I";
            default:
                throw new SecurityException("Progression is not in range");
        }
    }

    public static int getProgression(int userId, String gameMode) {
        int elo = getElo(userId, gameMode).orElse(-1);
        if (elo < 0) return -1;

        if (elo <= 100) return 0;
        if (elo <= 200) return 1;
        if (elo <= 400) return 2;
        if (elo <= 600) return 3;
        if (elo <= 900) return 4;
        if (elo <= 1200) return 5;

        int placement = getPlacement(elo, gameMode);
        if (placement == 1) return 9;
        if (placement == 2) return 8;
        if (placement == 3) return 7;

        return 6;
    }

    public static String toEmblem(int progression) {
        switch(progression) {
            case -1:
                return "";
            case 0:
                return "§6∨ ";
            case 1:
                return "§6∧ ";
            case 2:
                return "§7∨ ";
            case 3:
                return "§7∧ ";
            case 4:
                return "§e∨ ";
            case 5:
                return "§e∧ ";
            case 6:
                return "§5❂ ";
            case 7:
                return "§5III ";
            case 8:
                return "§5II ";
            case 9:
                return "§5I ";
            default:
                throw new SecurityException("Progression out of range");
        }
    }
}
