package main;

import java.util.Iterator;
import java.awt.geom.Point2D;
import java.awt.Point;
import java.util.ArrayList;

public class Player extends Creature
{
    private NPC npcTarget;
    int level;
    int experience;
    Weapon weapon;
    Armor[] armor;
    ArrayList<Item> inventory;
    public Item[][] invSpace;
    public int statPoints;
    private int baseAttackSpeed;
    private int baseMoveSpeed;
    private int basePixelSpeed;
    public double attackSpeedMod;
    public double moveSpeedMod;
    public int damageMod;
    public int hpMod;
    public int mpMod;
    
    public Player(final String name, final int xOffset, final int yOffset, final int level, final int experience) {
        super(name, xOffset, yOffset);
        this.invSpace = new Item[10][8];
        this.npcTarget = null;
        this.level = level;
        this.experience = experience;
        this.weapon = null;
        this.armor = new Armor[4];
        for (int x = 0; x < this.armor.length; ++x) {
            this.armor[x] = null;
        }
        this.inventory = new ArrayList<Item>();
        this.statPoints = 0;
        final double n = 1.0;
        this.moveSpeedMod = n;
        this.attackSpeedMod = n;
        final boolean damageMod = false;
        this.mpMod = (damageMod ? 1 : 0);
        this.hpMod = (damageMod ? 1 : 0);
        this.damageMod = (damageMod ? 1 : 0);
        this.basePixelSpeed = this.speed;
        for (int i = 0; i < this.invSpace.length; ++i) {
            for (int j = 0; j < this.invSpace[0].length; ++j) {
                this.invSpace[i][j] = null;
            }
        }
    }
    
    @Override
    public int getDamage() {
        if (this.weapon == null) {
            return super.getDamage() + this.stats[StatType.Strength.ordinal()] / 2 + this.damageMod;
        }
        return this.weapon.getDamage() + this.stats[StatType.Strength.ordinal()] / 2 + this.damageMod;
    }
    
    @Override
    public void takeDamage(final int damage) {
        this.hitpoints -= (int)Math.round(damage * Math.pow(0.5, this.stats[StatType.Armor.ordinal()] / 100.0));
    }
    
    public int getBaseAttackSpeed() {
        return this.baseAttackSpeed;
    }
    
    public int getBaseMoveSpeed() {
        return this.baseMoveSpeed;
    }
    
    public int getBasePixelSpeed() {
        return this.basePixelSpeed;
    }
    
    public int getArmorRating() {
        int total = 0;
        for (int x = 0; x < this.armor.length; ++x) {
            total += this.armor[x].getArmorRating();
        }
        return total;
    }
    
    public double getAttackSpeed() {
        final double dexMod = 1.0 + this.stats[StatType.Dexterity.ordinal()] / 50.0;
        double weaponAttackSpeed = 1.0;
        if (this.weapon != null) {
            weaponAttackSpeed = this.weapon.getAttackSpeed();
        }
        return weaponAttackSpeed / this.attackSpeedMod / dexMod;
    }
    
    public double getMoveSpeed() {
        final double dexMod = 1.0 + this.stats[StatType.Dexterity.ordinal()] / 50.0;
        double weaponAttackSpeed = 1.0;
        if (this.weapon != null) {
            weaponAttackSpeed = this.weapon.getAttackSpeed();
        }
        return weaponAttackSpeed / this.attackSpeedMod / dexMod;
    }
    
    @Override
    public void propagateStatChanges() {
        this.maxHitpoints = this.stats[StatType.Constitution.ordinal()] * 5 + this.hpMod;
        this.maxManapoints = this.stats[StatType.Intelligence.ordinal()] * 5 + this.mpMod;
    }
    
    @Override
    public void setModel(final Model model) {
        super.setModel(model);
        this.baseAttackSpeed = this.getModel().getAnimation(Direction.North, Action.Attacking).drawInterval;
        this.baseMoveSpeed = this.getModel().getAnimation(Direction.North, Action.Walking).drawInterval;
    }
    
    @Override
    public void AI_move(final Player player, final Map map) {
        if (this.enemyTarget != null && this.path != null && (this.path.getLast().distance(this.enemyTarget.loc) > this.loc.distance(this.path.getLast()) || this.loc.distance(this.enemyTarget.loc) * 2.0 < this.enemyTarget.loc.distance(this.path.getLast()))) {
            this.setEnemyTarget(this.enemyTarget, map);
        }
    }
    
    @Override
    public void AI_react(final Map map) {
        if (this.enemyTarget == null) {
            return;
        }
        if (this.enemyTarget.getClass() == NPC.class) {
            if (this.enemyTarget.intersects(this)) {
                this.startDialog((NPC)this.enemyTarget);
                this.enemyTarget = null;
                this.path = null;
            }
        }
        else {
            final double distance = this.loc.distance(this.enemyTarget.loc);
            if (this.getModel().action != Action.Attacking && this.getModel().action != Action.BeenHit && distance <= this.startRange) {
                this.startAttack(this.enemyTarget);
            }
            if (this.readyToAttack() && this.minRange <= distance && distance <= this.maxRange) {
                this.attack(this.enemyTarget, map);
            }
        }
    }
    
    public boolean isTalking() {
        return this.npcTarget != null;
    }
    
    public NPC getNPCTarget() {
        return this.npcTarget;
    }
    
    public void startDialog(final NPC npc) {
        this.npcTarget = npc;
    }
    
    public void endDialog() {
        this.npcTarget = null;
    }
    
    public void attack(final Creature enemy, final Map map) {
        this.enemyTarget.getModel().getCurrentAnimation().reset();
        this.enemyTarget.getModel().action = Action.BeenHit;
        this.enemyTarget.takeDamage(this.getDamage());
        if (this.enemyTarget.hitpoints <= 0) {
            this.experience += ((Enemy)enemy).xpReward;
            this.checkLevelUp();
            final Iterator<Item> itemDrops = ((Enemy)enemy).generateDrops().iterator();
            while (itemDrops.hasNext()) {
                this.pickUp(itemDrops.next());
            }
            this.enemyTarget.die();
            this.enemyTarget = null;
            this.path = null;
        }
    }
    
    public void pickUp(final Item item) {
        for (int i = 0; i < this.invSpace.length; ++i) {
            for (int j = 0; j < this.invSpace[0].length; ++j) {
                if (this.itemFits(item, j, i)) {
                    for (int a = 0; a < item.getImgHeight(); ++a) {
                        for (int b = 0; b < item.getImgWidth(); ++b) {
                            this.invSpace[i + a][j + b] = item;
                        }
                    }
                    item.invX = j;
                    item.invY = i;
                    this.inventory.add(item);
                    return;
                }
            }
        }
    }
    
    public void pickUp(final Item item, final int x, final int y) {
        if (this.itemFits(item, x, y)) {
            for (int a = 0; a < item.getImgHeight(); ++a) {
                for (int b = 0; b < item.getImgWidth(); ++b) {
                    this.invSpace[y + a][x + b] = item;
                }
            }
            item.invX = x;
            item.invY = y;
            this.inventory.add(item);
        }
    }
    
    public void drop(final Item item) {
        for (int a = 0; a < item.getImgHeight(); ++a) {
            for (int b = 0; b < item.getImgWidth(); ++b) {
                this.invSpace[item.invY + a][item.invX + b] = null;
            }
        }
        this.inventory.remove(item);
    }
    
    public void moveItem(final Item item, final int x, final int y) {
        if (this.itemFits(item, x, y)) {
            for (int a = 0; a < item.getImgHeight(); ++a) {
                for (int b = 0; b < item.getImgWidth(); ++b) {
                    this.invSpace[item.invY + a][item.invX + b] = null;
                }
            }
            for (int a = 0; a < item.getImgHeight(); ++a) {
                for (int b = 0; b < item.getImgWidth(); ++b) {
                    this.invSpace[y + a][x + b] = item;
                }
            }
            item.invX = x;
            item.invY = y;
        }
    }
    
    public boolean itemFits(final Item item, final int x, final int y) {
        if (x < 0 || x + item.imgWidth > this.invSpace[0].length) {
            return false;
        }
        if (y < 0 || y + item.imgHeight > this.invSpace.length) {
            return false;
        }
        for (int i = 0; i < item.getImgHeight(); ++i) {
            for (int j = 0; j < item.getImgWidth(); ++j) {
                if (this.invSpace[i + y][j + x] != null && this.invSpace[i + y][j + x] != item) {
                    return false;
                }
            }
        }
        return true;
    }
    
    public int getMaxExperience() {
        return this.level * 1000;
    }
    
    public void checkLevelUp() {
        if (this.experience >= this.getMaxExperience()) {
            this.experience -= this.getMaxExperience();
            ++this.level;
            this.statPoints += 5;
        }
    }
    
    public void equipWeapon(final Weapon i) {
        this.equipItem(i);
        if (this.weapon != null) {
            this.unequipItem(this.weapon);
            this.pickUp(this.weapon);
        }
        this.weapon = i;
        Direction[] values;
        for (int length = (values = Direction.values()).length, j = 0; j < length; ++j) {
            final Direction dir = values[j];
            this.getModel().getAnimation(dir, Action.Attacking).drawInterval = (int)(this.getBaseAttackSpeed() * this.getAttackSpeed());
        }
    }
    
    public void unequipWeapon(final Weapon i) {
        this.unequipItem(i);
        this.weapon = null;
        Direction[] values;
        for (int length = (values = Direction.values()).length, j = 0; j < length; ++j) {
            final Direction dir = values[j];
            this.getModel().getAnimation(dir, Action.Attacking).drawInterval = (int)(this.getBaseAttackSpeed() * this.getAttackSpeed());
        }
    }
    
    public void equipArmor(final int pos, final Armor i) {
        final int[] stats = this.stats;
        final int ordinal = StatType.Armor.ordinal();
        stats[ordinal] += i.getArmorRating();
        this.equipItem(i);
        if (this.armor[pos] != null) {
            final int[] stats2 = this.stats;
            final int ordinal2 = StatType.Armor.ordinal();
            stats2[ordinal2] -= this.armor[pos].getArmorRating();
            this.unequipItem(this.armor[pos]);
            this.pickUp(this.armor[pos]);
        }
        this.armor[pos] = i;
    }
    
    public void unequipArmor(final int pos, final Armor i) {
        final int[] stats = this.stats;
        final int ordinal = StatType.Armor.ordinal();
        stats[ordinal] -= i.getArmorRating();
        this.unequipItem(i);
        this.armor[pos] = null;
    }
    
    private void unequipItem(final Item i) {
        final Iterator<Effect> iter = i.getEffects().iterator();
        while (iter.hasNext()) {
            iter.next().unapply(this);
        }
    }
    
    private void equipItem(final Item i) {
        final Iterator<Effect> iter = i.getEffects().iterator();
        while (iter.hasNext()) {
            iter.next().apply(this);
        }
    }
}
