package de.miaurizius.jgame2d.entity;

import de.miaurizius.jgame2d.core.Boot;
import de.miaurizius.jgame2d.core.enums.Direction;
import de.miaurizius.jgame2d.core.GamePanel;
import de.miaurizius.jgame2d.core.Utility;
import de.miaurizius.jgame2d.core.enums.EntityType;
import de.miaurizius.jgame2d.core.enums.GameState;
import de.miaurizius.jgame2d.entity.particle.Particle;
import de.miaurizius.jgame2d.entity.projectile.Projectile;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;

public class Entity {

    protected GamePanel panel;
    public BufferedImage up1, up2, down1, down2, left1, left2, right1, right2;
    public BufferedImage attackUp1, attackUp2, attackDown1, attackDown2, attackLeft1, attackLeft2, attackRight1, attackRight2;
    public BufferedImage guardUp, guardDown, guardLeft, guardRight;
    public BufferedImage image, image2, image3;
    public Rectangle solidArea = new Rectangle(0, 0, 48, 48);
    public Rectangle attackArea = new Rectangle(0, 0, 0, 0);
    public Entity attacker;
    public int solidAreaDefaultX,  solidAreaDefaultY;
    public boolean collision;
    public String[][] dialogue = new String[20][20];

    // STATE
    public int worldX, worldY;
    public Direction direction = Direction.DOWN;
    public int spriteNum = 1;
    public int dialogueSet;
    public int dialogueIndex;
    public boolean collisionOn;
    public boolean invincible;
    public boolean transparent;
    public boolean attacking;
    public boolean alive = true;
    public boolean dying;
    public boolean hpBarOn;
    public boolean consumable;
    public boolean onPath;
    public boolean knockback;
    public boolean guarding;
    public Direction knockbackDirection;

    // COUNTER
    public int spriteCount;
    public int actionLock;
    public int invincibleCount;
    public int shotAvailableCount;
    int dyingCount;
    int hpBarCount;
    int knockbackCount;

    // CHARACTER ATTRIBUTES
    public EntityType type;
    public int defaultSpeed;
    public String name;
    public int speed;
    public int maxLife;
    public int life;
    public int level;
    public int strength;
    public int dexterity;
    public int attack;
    public int defense;
    public int exp;
    public int nextLevelExp;
    public int coins;
    public int maxMana;
    public int mana;
    public Entity currentWeapon;
    public Entity currentShield;
    public Entity currentLight;
    public Projectile projectile;
    public List<Entity> inventory = new ArrayList<>();
    public final int maxInvSize = 20;

    // ITEM ATTRIBUTES
    public EntityType.WeaponType weaponType;
    public int attackValue;
    public int defenseValue;
    public String description;
    public int useCost;
    public int value;
    public int price;
    public int knockbackVal;
    public boolean stackable;
    public int amt = 1;
    public float lightRadius;
    public boolean opened;
    public Entity loot;

    public Entity(GamePanel panel) {
        this.panel = panel;
    }

    // DEFAULT
    public void update() {
        if(knockback) {
            checkCollision();
            if(collisionOn) {
                knockbackCount = 0;
                knockback = false;
                speed = defaultSpeed;
                invincibleCounting();
                return;
            }

            switch(knockbackDirection) {
                case UP -> worldY -= speed;
                case DOWN -> worldY += speed;
                case LEFT ->worldX -= speed;
                case RIGHT -> worldX += speed;
            }

            knockbackCount++;
            if(knockbackCount != 10) {
                invincibleCounting();
                return;
            }

            knockback = false;
            knockbackCount = 0;
            speed = defaultSpeed;
        } else if(attacking) attacking();
        else {
            setAction();
            checkCollision();

            if(!collisionOn) {
                switch (direction) {
                    case UP -> worldY -= speed;
                    case DOWN -> worldY += speed;
                    case LEFT ->worldX -= speed;
                    case RIGHT -> worldX += speed;
                }
            }

            spriteCount++;
            if(spriteCount > 24) {
                if(spriteNum == 1) spriteNum = 2;
                else if(spriteNum == 2) spriteNum = 1;
                else spriteNum = 0;
                spriteCount = 0;
            }
        }
        invincibleCounting();
    }
    public void draw(Graphics2D graphics2d) {
        int screenX = worldX - panel.player.worldX + panel.player.screenX;
        int screenY = worldY - panel.player.worldY + panel.player.screenY;

        if(worldX + panel.tileSize > panel.player.worldX - panel.player.screenX &&
                worldX - panel.tileSize < panel.player.worldX + panel.player.screenX &&
                worldY + panel.tileSize > panel.player.worldY - panel.player.screenY &&
                worldY - panel.tileSize < panel.player.worldY + panel.player.screenY
        ) {

            // MONSTER HP-BAR
            if(this.type == EntityType.MONSTER && hpBarOn) {
                graphics2d.setColor(new Color(35, 35, 35));
                graphics2d.fillRect(screenX-1, screenY-6, panel.tileSize+2, 12);

                graphics2d.setColor(new Color(255, 0, 30));
                graphics2d.fillRect(screenX, screenY-5, (int) ((double) panel.tileSize/maxLife)*life, 10);

                hpBarCount++;
                if(hpBarCount > 600) { //bar disappears after 10 seconds
                    hpBarCount = 0;
                    hpBarOn = false;
                }
            }

            // DRAW ENTITY
            if(invincible) {
                hpBarOn = true;
                hpBarCount = 0;
                if(transparent) changeOpacity(graphics2d, 0.4f);
            }
            if(dying) dyingAnimation(graphics2d);
            if(type == EntityType.PLAYER || name.equals("orc")) { // only modify sprite render position for player because I dont know yet how monster attack sprite are gonna look
                if(attacking) graphics2d.drawImage(parseSpriteATK(),
                        (direction == Direction.LEFT) ? screenX - panel.tileSize : screenX,
                        (direction == Direction.UP) ? screenY - panel.tileSize : screenY, null);
                else if(guarding) graphics2d.drawImage(parseSpriteGRD(), screenX, screenY, null);
                else graphics2d.drawImage(parseSprite(), screenX, screenY, null);
            } else graphics2d.drawImage(parseSprite(), screenX, screenY, null);
            changeOpacity(graphics2d, 1f);
        }

        if(panel.keyH.debug) {
            graphics2d.setColor(new Color(255, 0, 0, 70));
            graphics2d.fillRect(worldX - panel.player.worldX + panel.player.screenX, worldY - panel.player.worldY + panel.player.screenY, panel.tileSize, panel.tileSize);
        }
    }

    // INTERACTION
    public void setAction() {}
    public void damageReaction() {}
    public void attacking() {
        if(panel.player.attackCancel && type == EntityType.PLAYER) return;
        spriteCount++;
        if(spriteCount <= 5) spriteNum = 1;
        if(spriteCount > 5 && spriteCount <= 25) {
            spriteNum = 2;
            int currentWorldX = worldX;
            int currentWorldY = worldY;
            int solidAreaWidth = solidArea.width;
            int solidAreaHeight = solidArea.height;

            switch(direction) {
                case UP -> worldY -= attackArea.height;
                case DOWN -> worldY += attackArea.height;
                case LEFT -> worldX -= attackArea.width;
                case RIGHT -> worldX += attackArea.width;
            }
            solidArea.width = attackArea.width;
            solidArea.height = attackArea.height;

            if(type == EntityType.MONSTER) if(panel.collisionH.checkPlayer(this)) damagePlayer(attack);

            if(type == EntityType.PLAYER) {
                int monsterIndex = panel.collisionH.checkEntity(this, panel.monster[panel.currentMap.getIndex()]);
                panel.player.damageMonster(monsterIndex, this, attack, currentWeapon.knockbackVal);

                int iTileIndex = panel.collisionH.checkEntity(this, panel.iTile[panel.currentMap.getIndex()]);
                panel.player.interactTile(iTileIndex);
            }

            worldX = currentWorldX;
            worldY = currentWorldY;
            solidArea.width = solidAreaWidth;
            solidArea.height = solidAreaHeight;

        }
        if(spriteCount > 25) {
            spriteNum = 1;
            spriteCount = 0;
            attacking = false;
        }
    }
    public void damagePlayer(int attack) {
        if(panel.player.invincible) return;

        boolean block = panel.player.guarding && panel.player.direction == this.direction.getOpposite();
        int damage = attack - panel.player.defense;

        if(block) {
            panel.playSE(15);
            damage = 0;
        } else panel.playSE(6);

        panel.player.life -= Math.max(damage, (block ? 0 : 1));

        if(damage != 0) {
            setKnockback(panel.player, this, knockbackVal);
            panel.player.transparent = true;
        }
        panel.player.invincible = true;
    }
    public void speak() {

    }
    public void facePlayer() {
        switch(panel.player.direction) {
            case UP -> direction = Direction.DOWN;
            case DOWN -> direction = Direction.UP;
            case LEFT -> direction = Direction.RIGHT;
            case RIGHT -> direction = Direction.LEFT;
        }
    }
    public void startDialogue(Entity entity, int setNum) {
        panel.gameState = GameState.DIALOGUE;
        panel.ui.tradingNPC = entity;
        dialogueSet = setNum;
    }
    public void dyingAnimation(Graphics2D graphics2d) {
        dyingCount++;
        int incr = 5;
        if(dyingCount <= incr) changeOpacity(graphics2d, 0f);
        if(dyingCount > incr && dyingCount <= incr*2) changeOpacity(graphics2d, 1f);
        if(dyingCount > incr*2 && dyingCount <= incr*3) changeOpacity(graphics2d, 0f);
        if(dyingCount > incr*3 && dyingCount <= incr*4) changeOpacity(graphics2d, 1f);
        if(dyingCount > incr*4 && dyingCount <= incr*5) changeOpacity(graphics2d, 0f);
        if(dyingCount > incr*5 && dyingCount <= incr*6) changeOpacity(graphics2d, 1f);
        if(dyingCount > incr*6 && dyingCount <= incr*7) changeOpacity(graphics2d, 0f);
        if(dyingCount > incr*7 && dyingCount <= incr*8) changeOpacity(graphics2d, 1f);
        if(dyingCount > incr*8) {
            alive = false;
        }
    }
    public boolean use(Entity entity) {
        return false;
    } //If entity is consumable
    public void checkDrop() {

    }
    public void checkCollision() {
        collisionOn = false;
        panel.collisionH.checkTile(this);
        panel.collisionH.checkObject(this, false);
        panel.collisionH.checkEntity(this, panel.npc[panel.currentMap.getIndex()]);
        panel.collisionH.checkEntity(this, panel.monster[panel.currentMap.getIndex()]);
        panel.collisionH.checkEntity(this, panel.iTile[panel.currentMap.getIndex()]);
        boolean contactPlayer = panel.collisionH.checkPlayer(this);

        if(this.type == EntityType.MONSTER && contactPlayer) damagePlayer(attack);
    }
    public void dropItem(Entity droppedItem) {
        for(int i = 0; i < panel.obj[panel.currentMap.getIndex()].length; i++) {
            if(panel.obj[panel.currentMap.getIndex()][i] == null) {
                panel.obj[panel.currentMap.getIndex()][i] = droppedItem;
                panel.obj[panel.currentMap.getIndex()][i].worldX = worldX;
                panel.obj[panel.currentMap.getIndex()][i].worldY = worldY;
                break;
            }
        }
    }
    public void interact() {

    }
    public int getDetected(Entity user, Entity[][] target, String targetName) {
        int index = 999;
        int nextWorldX = user.getLeftX();
        int nextWorldY = user.getTopY();

        switch(user.direction) {
            case UP -> nextWorldY = user.getTopY()-panel.player.speed;
            case DOWN -> nextWorldY = user.getBottomY()+panel.player.speed;
            case LEFT -> nextWorldX = user.getLeftX()-panel.player.speed;
            case RIGHT -> nextWorldX = user.getRightX()+panel.player.speed;
        }
        int col = nextWorldX / panel.tileSize;
        int row = nextWorldY / panel.tileSize;

        for(int i = 0; i < target[panel.currentMap.getIndex()].length; i++) {
            if(target[panel.currentMap.getIndex()][i] == null) continue;
            if(
               target[panel.currentMap.getIndex()][i].getCol() == col &&
               target[panel.currentMap.getIndex()][i].getRow() == row &&
               target[panel.currentMap.getIndex()][i].name.equalsIgnoreCase(targetName)
            ) {
                index = i;
                break;
            }
        }
        return index;
    }
    public void setKnockback(Entity target, Entity attacker, int knockbackVal) {
        this.attacker = attacker;
        target.knockbackDirection = attacker.direction;
        target.speed += knockbackVal;
        target.knockback = true;
    }
    public void setLoot(Entity loot) {}

    // PARTICLE SETUP
    public Color getParticleColor() {
        return null;
    }
    public int getParticleSize() {
        return -1;
    }
    public int getParticleSpeed() {
        return -1;
    }
    public int getParticleMaxLife() {
        return -1;
    }
    public void generateParticle(Entity generator, Entity target) {
        Color color = generator.getParticleColor();
        int size = generator.getParticleSize();
        int speed = generator.getParticleSpeed();
        int maxLife = generator.getParticleMaxLife();

        Particle p1 = new Particle(panel, target, color, size, speed, maxLife, -2, -1);
        Particle p2 = new Particle(panel, target, color, size, speed, maxLife, 2, -1);
        Particle p3 = new Particle(panel, target, color, size, speed, maxLife, -2, 1);
        Particle p4 = new Particle(panel, target, color, size, speed, maxLife, 2, 1);
        panel.particleList.add(p1);
        panel.particleList.add(p2);
        panel.particleList.add(p3);
        panel.particleList.add(p4);
    }

    // GETTERS
    public int getLeftX() {
        return worldX + solidArea.x;
    }
    public int getRightX() {
        return worldX + solidArea.x + solidArea.width;
    }
    public int getTopY() {
        return worldY + solidArea.y;
    }
    public int getBottomY() {
        return worldY + solidArea.y + solidArea.height;
    }
    public int getCol() {
        return (worldX + solidArea.x) / panel.tileSize;
    }
    public int getRow() {
        return (worldY + solidArea.y) / panel.tileSize;
    }
    public int dX(Entity target) {
        return Math.abs(worldX - target.worldX);
    }
    public int dY(Entity target) {
        return Math.abs(worldY - target.worldY);
    }
    public int dTile(Entity target) {
        //if(Objects.equals(name, "orc")) System.out.println("dX: " + dX(target) + " dY: " + dY(target));
        return (dX(target) + dY(target)) / panel.tileSize;
    }
    public int getGoalCol(Entity target) {
        return (target.worldX+target.solidArea.x)/panel.tileSize;
    }
    public int getGoalRow(Entity target) {
        return (target.worldY+target.solidArea.y)/panel.tileSize;
    }

    // SETTING THINGS UP
    BufferedImage parseSprite() {
        return switch (direction) {
            case UP -> (spriteNum == 1) ? up1 : up2;
            case DOWN -> (spriteNum == 1) ? down1 : down2;
            case LEFT -> (spriteNum == 1) ? left1 : left2;
            case RIGHT -> (spriteNum == 1) ? right1 : right2;
        };
    }
    BufferedImage parseSpriteATK() {
        return switch (direction) {
            case UP -> (spriteNum == 1) ? attackUp1 : attackUp2;
            case DOWN -> (spriteNum == 1) ? attackDown1 : attackDown2;
            case LEFT -> (spriteNum == 1) ? attackLeft1 : attackLeft2;
            case RIGHT -> (spriteNum == 1) ? attackRight1 : attackRight2;
        };
    }
    BufferedImage parseSpriteGRD() {
        return switch (direction) {
            case UP -> guardUp;
            case DOWN -> guardDown;
            case LEFT -> guardLeft;
            case RIGHT -> guardRight;
        };
    }
    public BufferedImage initEntitySprites(String name) {
        try {
            return Utility.scaleImage(ImageIO.read(new FileInputStream("assets/" + name + ".png")), panel.tileSize, panel.tileSize);
        } catch (IOException e) {
            Boot.logger.log(Level.SEVERE, "Could not load entity-image", e);
        }
        return null;
    }
    public BufferedImage initEntitySprites(String name, int width, int height) {
        try {
            return Utility.scaleImage(ImageIO.read(new FileInputStream("assets/" + name + ".png")), width, height);
        } catch (IOException e) {
            Boot.logger.log(Level.SEVERE, "Could not load entity-image", e);
        }
        return null;
    }
    public void changeOpacity(Graphics2D graphics2d, float opacity) {
        graphics2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
    }
    public void searchPath(int goalCol, int goalRow) {
        int startCol = (worldX + solidArea.x) / panel.tileSize;
        int startRow = (worldY + solidArea.y) / panel.tileSize;

        panel.pFinder.setNodes(startCol, startRow, goalCol, goalRow);

        if (panel.pFinder.search()) {
            int nextCol = panel.pFinder.pathList.getFirst().col;
            int nextRow = panel.pFinder.pathList.getFirst().row;

            int enLeftX = worldX + solidArea.x;
            int enTopY = worldY + solidArea.y;

            int targetX = nextCol * panel.tileSize;
            int targetY = nextRow * panel.tileSize;

            // UP
            if (nextRow < startRow) {
                if (enLeftX < targetX) direction = Direction.RIGHT;
                else if (enLeftX > targetX) direction = Direction.LEFT;
                else direction = Direction.UP;
            }
            // DOWN
            else if (nextRow > startRow) {
                if (enLeftX < targetX) direction = Direction.RIGHT;
                else if (enLeftX > targetX) direction = Direction.LEFT;
                else direction = Direction.DOWN;

            }
            else if (nextCol < startCol) { // DOWN LEFT
                if (enTopY < targetY) direction = Direction.DOWN;
                else if (enTopY > targetY) direction = Direction.UP;
                else direction = Direction.LEFT;
            }
            else if (nextCol > startCol) { // RIGHT
                if (enTopY < targetY) direction = Direction.DOWN;
                else if (enTopY > targetY) direction = Direction.UP;
                else direction = Direction.RIGHT;
            }
        } else onPath = false;
    }
    public void followPlayer() {
        int goalCol = panel.player.getCol();
        int goalRow = panel.player.getRow();

        int startCol = (worldX + solidArea.x) / panel.tileSize;
        int startRow = (worldY + solidArea.y) / panel.tileSize;

        panel.pFinder.setNodes(startCol, startRow, goalCol, goalRow);

        if (panel.pFinder.search()) {
            int nextCol = panel.pFinder.pathList.getFirst().col;
            int nextRow = panel.pFinder.pathList.getFirst().row;

            int enLeftX = worldX + solidArea.x;
            int enTopY = worldY + solidArea.y;

            int targetX = nextCol * panel.tileSize;
            int targetY = nextRow * panel.tileSize;

            // UP
            if (nextRow < startRow) {
                if (enLeftX < targetX) direction = Direction.RIGHT;
                else if (enLeftX > targetX) direction = Direction.LEFT;
                else direction = Direction.UP;
            }
            // DOWN
            else if (nextRow > startRow) {
                if (enLeftX < targetX) direction = Direction.RIGHT;
                else if (enLeftX > targetX) direction = Direction.LEFT;
                else direction = Direction.DOWN;

            }
            else if (nextCol < startCol) { // DOWN LEFT
                if (enTopY < targetY) direction = Direction.DOWN;
                else if (enTopY > targetY) direction = Direction.UP;
                else direction = Direction.LEFT;
            }
            else if (nextCol > startCol) { // RIGHT
                if (enTopY < targetY) direction = Direction.DOWN;
                else if (enTopY > targetY) direction = Direction.UP;
                else direction = Direction.RIGHT;
            }
        }
    }
    public void checkStopChasing(Entity target, int distance, int rate) {
        //if(Objects.equals(name, "orc")) System.out.println("dTile: " + dTile(target) + " distance: " + distance);
        if(dTile(target) > distance) onPath = false;
    }
    public void checkStartChasing(Entity target, int distance, int rate) {
        if(dTile(target) < distance) onPath = true;
    }
    public void checkShooting(int rate, int shotInterval) {
        if(new Random().nextInt(rate) == 0 && projectile.alive == false && shotAvailableCount == shotInterval) {
            projectile.set(worldX, worldY, direction, true, this);

            // CHECK VACANCY
            for(int ii = 0; ii < panel.projectileList.size(); ii++) {
                if(panel.projectileList.get(ii) == null) {
                    panel.projectileList.set(ii, projectile);
                    break;
                }
            }
            shotAvailableCount = 0;
        }
    }
    public void checkAttack(int rate, int straight, int horizontal) {
        boolean targetInRange = false;
        int xDist = dX(panel.player);
        int yDist = dY(panel.player);

        switch(direction) {
            case UP -> {
                if(panel.player.worldY < worldY && yDist < straight && xDist < horizontal) targetInRange = true;
            }
            case DOWN -> {
                if(panel.player.worldY > worldY && yDist < straight && xDist < horizontal) targetInRange = true;
            }
            case LEFT -> {
                if(panel.player.worldX < worldX && xDist < straight && yDist < horizontal) targetInRange = true;
            }
            case RIGHT -> {
                if(panel.player.worldX > worldX && xDist < straight && yDist < horizontal) targetInRange = true;
            }
        }

        if(targetInRange)
            if (new Random().nextInt(rate) == 0) {
                attacking = true;
                spriteNum = 1;
                spriteCount = 0;
                shotAvailableCount = 0;
            }
    }
    public void setRandomDirection() {
        actionLock++;
        if(actionLock == 120) { //lock action for x frames
            Random rand = new Random();
            int i = rand.nextInt(100)+1; //Generate number between 1 and 100
            if(i <= 25) direction = Direction.UP;
            if(i > 25 && i <= 50) direction = Direction.DOWN;
            if(i > 50 && i <= 75) direction = Direction.LEFT;
            if(i > 75) direction = Direction.RIGHT;
            actionLock = 0;
        }
    }
    public void invincibleCounting() {
        if(!invincible) return;
        invincibleCount++;
        if(invincibleCount > 40) {
            invincible = false;
            invincibleCount = 0;
        }
    }
    public void resetCounter() {
        spriteCount = 0;
        actionLock = 0;
        invincibleCount = 0;
        shotAvailableCount = 0;
        dyingCount = 0;
        hpBarCount = 0;
        knockbackCount = 0;
    }

}
