package inf101.v18.rogue101.enemies;

import inf101.v18.grid.GridDirection;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.shared.NPC;
import inf101.v18.rogue101.states.Age;
import inf101.v18.rogue101.states.Attack;
import inf101.v18.rogue101.states.Occupation;
import inf101.v18.rogue101.states.Personality;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Girl implements INonPlayer {
    private final String name = randomName();
    private final Age age = Age.getRandom();
    private final Personality personality = Personality.getRandom();
    private final Occupation occupation = Occupation.getRandom();
    private int maxhp;
    private int hp;
    private int attack;
    private int defence;
    private int visibility;
    private int damage;
    private final Backpack backpack = new Backpack();
    private static final Random random = new Random();
    private List<Class<?>> validItems;
    private static final String[] namelist = {"Milk", "Salad", "Mikan", "Rima", "Suki", "Miki", "Hinata", "Haruna", "Asuna"};

    public Girl() {
        setStats();
        setValidItems();
    }

    private void setStats() {
        switch (age) {
            case TODDLER:
                maxhp = 30;
                attack = 10;
                defence = 50;
                damage = 1 + random.nextInt(5);
                visibility = 1;
                break;
            case CHILD:
                maxhp = 50;
                attack = 20;
                defence = 40;
                damage = 2 + random.nextInt(5);
                visibility = 2;
                break;
            case TEEN:
                maxhp = 70;
                attack = 25;
                defence = 30;
                damage = 3 + random.nextInt(5);
                visibility = 3;
                break;
            case ADULT:
                maxhp = 100;
                attack = 30;
                defence = 20;
                damage = 4 + random.nextInt(5);
                visibility = 4;
                break;
            case ELDER:
                maxhp = 70;
                attack = 15;
                defence = 35;
                damage = 3 + random.nextInt(5);
                visibility = 2;
                break;
        }
        if (occupation == Occupation.KNIGHT) {
            attack += 10; //Knights are quite powerful.
        } else if (occupation == Occupation.MAGE) {
            attack += 5; // Mages have lesser range than bowsmen, but more damage.
        }
        maxhp += (int)(random.nextGaussian() * 10);
        hp = maxhp;
        attack += (int)(random.nextGaussian() * 5);
        defence += (int)(random.nextGaussian() * 5);
    }

    /**
     * Has a chance of giving a weapon to a girl.
     *
     * @param lvl   The current map level
     */
    public void giveWeapon(int lvl) {
        if (random.nextInt(5) < lvl) {
            switch (occupation) {
                case KNIGHT:
                    backpack.add(new Sword());
                    break;
                case MAGE:
                    backpack.add(new Staff());
                    break;
                case BOWSMAN:
                    backpack.add(new Bow());
            }
        }
    }

    /**
     * Specified which items the current Girl should be able to pick up.
     */
    private void setValidItems() {
        validItems = new ArrayList<>();
        switch (occupation) {
            case BOWSMAN:
                validItems.add(IRangedWeapon.class);
                break;
            case MAGE:
                validItems.add(IMagicWeapon.class);
                break;
            case KNIGHT:
                validItems.add(IMeleeWeapon.class);
        }
        validItems.add(IBuffItem.class);
    }

    /**
     * Picks a "random" name
     *
     * @return  A random name
     */
    private String randomName() {
        return namelist[random.nextInt(namelist.length)] + "-chan";
    }

    @Override
    public void doTurn(IGame game) {
        if (backpack.hasSpace()) {
            IItem item = NPC.pickUp(game, validItems);
            if (item != null) {
                backpack.add(item);
                return;
            }
            if (NPC.trackItem(game, validItems)) {
                return;
            }
        }

        if (personality == Personality.AFRAID && NPC.flee(game)) {
            return;
        }

        if (willAttack(game) && attack(game)) {
            return;
        }

        List<GridDirection> possibleMoves = game.getPossibleMoves();
        if (!possibleMoves.isEmpty()) {
            Collections.shuffle(possibleMoves);
            game.move(possibleMoves.get(0));
        }
    }

    /**
     * Tries to attack with an attack specified by the Girl's occupation
     *
     * @param game  An IGame object
     * @return      True if an attack occurred. False otherwise
     */
    private boolean attack(IGame game) {
        switch (occupation) {
            case KNIGHT:
                if (NPC.tryAttack(game, 1, Attack.MELEE)) {
                    return true;
                }
                break;
            case MAGE:
                if (NPC.tryAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, Attack.MAGIC)) {
                    return true;
                }
            case BOWSMAN:
                if (NPC.tryAttack(game, getVision(), Attack.RANGED)) {
                    return true;
                }
        }
        return false;
    }

    /**
     * Checks if the current girl will try to attack.
     *
     * @param game  An IGame object
     * @return      True if the girl will attack. False otherwise
     */
    private boolean willAttack(IGame game) {
        boolean attack = false;
        switch (personality) {
            case CALM:
                if (hp < maxhp) {
                    attack = true;
                }
                break;
            case AFRAID:
                if (game.getPossibleMoves().isEmpty()) {
                    attack = true;
                }
                break;
            case AGRESSIVE:
                attack = true;
                break;
        }
        return attack;
    }

    @Override
    public int getAttack() {
        return attack;
    }

    @Override
    public int getDamage() {
        return damage;
    }

    @Override
    public int getCurrentHealth() {
        return hp;
    }

    @Override
    public int getDefence() {
        return defence;
    }

    @Override
    public int getMaxHealth() {
        return maxhp;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return 30;
    }

    @Override
    public String getPrintSymbol() {
        return "\u001b[95m" + "\uD83D\uDEB6" + "\u001b[0m";
    }

    @Override
    public String getSymbol() {
        return "G";
    }

    @Override
    public int getVision() {
        return visibility;
    }

    @Override
    public IItem getItem(Class<?> type) {
        for (IItem item : backpack.getContent()) {
            if (type.isInstance(item)) {
                return item;
            }
        }
        return null;
    }

    @Override
    public int handleDamage(IGame game, IItem source, int amount) {
        hp -= amount;
        return amount;
    }
}
