package com.kangacult.octogone;

import java.util.HashMap;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.bullet.collision.Collision;
import com.badlogic.gdx.physics.bullet.collision.btBroadphaseProxy;
import com.badlogic.gdx.physics.bullet.collision.btCapsuleShapeZ;
import com.badlogic.gdx.physics.bullet.collision.btCollisionObject;
import com.badlogic.gdx.physics.bullet.dynamics.btDynamicsWorld;
import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody;
import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody.btRigidBodyConstructionInfo;
import com.kangacult.octogone.Audio.SoundType;

//TODO: implement max number of dead corpses
public class Enemies {
    public static final int MAX_ENEMIES = 128;
    public enum State { ALIVE, DEAD, FREE };
    public Animation idle;
    public Animation frozen;
    public btCapsuleShapeZ shape;
    public Model quad;
    public float radius = 0.35f;
    public float height = 1.0f;
    public float timeDead = 15f;


    private int numAlive = 0;
    private int head = 0;
    HashMap<btRigidBody, Integer> collisionLookup = new HashMap<btRigidBody, Integer>();
    //Per-entity data
    public btRigidBody[] body = new btRigidBody[MAX_ENEMIES];
    public ModelInstance[] model = new ModelInstance[MAX_ENEMIES];
    public MotionState[] motion = new MotionState[MAX_ENEMIES];
    public State[] state = new State[MAX_ENEMIES];
    public Vector3[] direction = new Vector3[MAX_ENEMIES];
    public Vector3[] position = new Vector3[MAX_ENEMIES];
    public Vector3[] velocity = new Vector3[MAX_ENEMIES];
    public Animation[] anim = new Animation[MAX_ENEMIES];
    public float[] animTime = new float[MAX_ENEMIES];
    public Texture[] frame = new Texture[MAX_ENEMIES];
    public float[] speed = new float[MAX_ENEMIES];
    public float[] attack = new float[MAX_ENEMIES];
    /**ID of wave this enemy was last hit by (to prevent multiple hits)*/
    public long[] hitBy = new long[MAX_ENEMIES];
    /** Allow dead enemies to be annoying for a certain time*/
    public float[] timeToCrumble = new float[MAX_ENEMIES];

    public void init(Model quadModel) {
        {//Idle
            Texture[] textures = new Texture[] {
                    new Texture(Gdx.files.internal("Enemy/Idle/Enemy00.png")),
                    new Texture(Gdx.files.internal("Enemy/Idle/Enemy01.png")),
                    new Texture(Gdx.files.internal("Enemy/Idle/Enemy02.png")),
                    new Texture(Gdx.files.internal("Enemy/Idle/Enemy03.png")),
                    new Texture(Gdx.files.internal("Enemy/Idle/Enemy04.png")),
                    new Texture(Gdx.files.internal("Enemy/Idle/Enemy05.png")),
                    new Texture(Gdx.files.internal("Enemy/Idle/Enemy06.png")),
                    new Texture(Gdx.files.internal("Enemy/Idle/Enemy07.png")),
            };
            float[] frames = new float[] {
                    0.06f,
                    0.06f,
                    0.06f,
                    0.06f,
                    0.06f,
                    0.06f,
                    0.06f,
                    0.06f,
            };
            idle = new Animation(textures, frames);
        }
        {
            //Frozen
            Texture[] textures = new Texture[] {
                    new Texture(Gdx.files.internal("Enemy/Frozen/Frozen00.png")),
            };
            float[] frames = new float[] {
                    1.0f,
            };
            frozen = new Animation(textures, frames);
        }

        shape = new btCapsuleShapeZ(radius, height - radius * 2f);

        for (int i = 0; i < MAX_ENEMIES; i++) {
            motion[i] = new MotionState(new Matrix4());
            model[i] = new ModelInstance(quadModel);
            direction[i] = new Vector3();
            position[i] = new Vector3();
            velocity[i] = new Vector3();
            animTime[i] = 0f;
            anim[i] = idle;
            frame[i] = idle.textures[0];
            state[i] = State.FREE;
            speed[i] = MathUtils.random(1f, 3f);
            timeToCrumble[i] = 0f;

            btRigidBodyConstructionInfo info =
                    new btRigidBodyConstructionInfo(0f, motion[i], shape, new Vector3());
            body[i] = new btRigidBody(info);
            info.dispose();
            body[i].setCollisionFlags(body[i].getCollisionFlags() | 
                    btCollisionObject.CollisionFlags.CF_KINEMATIC_OBJECT);
            body[i].setActivationState(Collision.DISABLE_DEACTIVATION);
        }
    }
    
    public void reset(btDynamicsWorld world) {
        for (int i = 0; i < MAX_ENEMIES; i++) {
            if (state[i] != State.FREE) {
                world.removeRigidBody(body[i]);
            }
            state[i] = State.FREE;
            attack[i] = MathUtils.random(2f, 6f);
        }
        collisionLookup.clear();
        numAlive = 0;
    }

    public void dispose(btDynamicsWorld world) {
        for (int i = 0; i < MAX_ENEMIES; i++) {
            if (state[i] != State.FREE) {
                world.removeRigidBody(body[i]);
            }
            body[i].dispose();
        }
        shape.dispose();
        idle.dispose();
        frozen.dispose();
    }

    public boolean spawn(btDynamicsWorld world, Vector3 position) {
        int i = -1;
        for (int j = 0; j < MAX_ENEMIES; j++) {
            if (state[(j + head) % MAX_ENEMIES] == State.FREE) {
                i = head;
                head++;
                head %= MAX_ENEMIES;
                break;
            }
        }
        if (i < 0) return false;
        
        //TODO Fancy particle effect
        state[i] = State.ALIVE;
        this.position[i].set(position);
        anim[i] = idle;
        animTime[i] = MathUtils.random(idle.totalTime);
        velocity[i].setZero();
        world.addRigidBody(body[i], 
                (short) btBroadphaseProxy.CollisionFilterGroups.CharacterFilter, 
                (short) btBroadphaseProxy.CollisionFilterGroups.AllFilter);
        body[i].activate();
        collisionLookup.put(body[i], i);
        numAlive += 1;
        return true;
    }

    public void update(float deltaTime, btDynamicsWorld world, Player player,
            Projectiles projectiles, Camera camera, Audio audio) {
        for (int i = 0; i < MAX_ENEMIES; i++) {
            if (state[i] == State.FREE) {
                position[i].set(0f, 0f, -1000f);
                motion[i].transform.setToTranslation(position[i]);
                continue;
            }
            
            if (position[i].z < -1.0f) {
                hit(i, world, camera, audio);
                hit(i, world, camera, audio);
                //TODO Play Falling sound
                //TODO Show falling particle effect
            }

            if(state[i] == State.DEAD) {
                anim[i] = frozen;
                timeToCrumble[i] -= deltaTime;
                if (timeToCrumble[i] < 0f){
                    hit(i, world, camera, audio);
                }
            }

            if (state[i] == State.ALIVE) {
                if (attack[i] < 0f) {
                    attack[i] = MathUtils.random(2f, 6f);
                    projectiles.spawn(world, position[i], velocity[i]);
                    audio.playSound(SoundType.ENEMY_FIREBALL, camera.position, camera.direction, position[i]);
                }
                attack[i] -= deltaTime;

                direction[i].set(player.position);
                direction[i].sub(position[i]);
                if (direction[i].len2() > 1f)
                    direction[i].nor();
            } else {
                direction[i].setZero();
            }
            Movement.move(deltaTime, world, body[i], shape, position[i], velocity[i], direction[i], speed[i], motion[i]);

            model[i].transform.setToTranslation(position[i]);
            model[i].transform.rotate(Vector3.Z, camera.yaw);
            if (state[i] == State.ALIVE) //FIXME Remove this once there's a death animation
                animTime[i] += deltaTime;
            animTime[i] %= anim[i].totalTime;
            frame[i] = anim[i].getFrame(animTime[i]);
            for (Material m: model[i].materials) {
                TextureAttribute t = (TextureAttribute) m.get(TextureAttribute.Diffuse);
                t.textureDescription.texture = frame[i];
                m.set(t);
            }
        }
    }

    public void render(ModelBatch batch) {
        for (int i = 0; i < MAX_ENEMIES; i++) {
            if (state[i] == State.FREE) continue;

            batch.render(model[i]);
        }
    }
    
    /**Only do damage if hit by a different projectile to one from previous detection. */
    public void hitWave(int i, btDynamicsWorld world, long waveNum, Camera camera, Audio audio) {
        if (waveNum != hitBy[i]) {
            hit(i, world, camera, audio);
            hitBy[i] = waveNum;
        }
    }
    
    public void hit(int i, btDynamicsWorld world, Camera camera, Audio audio) {
        if (state[i] == State.ALIVE) {
            state[i] = State.DEAD;
            numAlive -= 1;
            audio.playSound(SoundType.ENEMY_DEATH, camera.position, camera.direction, position[i]);
            timeToCrumble[i] = timeDead;
            //TODO Particle Effect
        } else if (state[i] == State.DEAD) {
            state[i] = State.FREE;
            world.removeRigidBody(body[i]);
            collisionLookup.remove(body[i]);
            //TODO Particle Effect
        }
    }
    
    public int getNumEnemiesAlive() {
        return numAlive;
    }
}
