package me.hammerle.supersnuvi.entity;

import java.util.LinkedList;
import me.hammerle.supersnuvi.gamelogic.Level;
import me.hammerle.supersnuvi.javafx.Image;
import me.hammerle.supersnuvi.rendering.Game;
import me.hammerle.supersnuvi.tiles.Location;
import me.hammerle.supersnuvi.util.CollisionBox;
import me.hammerle.supersnuvi.util.Face;
import me.hammerle.supersnuvi.util.Utils;

public class Entity
{
    // 1 Tile = 1 m
    // Pixel    m       s      Pixel
    // ----- * --- * ------ = -------
    //   m      s     Tick     Tick
    // added correction factor, real life gravity seems to strong
    public static double GRAVITY = Game.TILE_SIZE * 9.81 / 60 * 0.1;
    
    private double prevPosX;
    private double prevPrevY;
    private double posX;
    private double posY;
    
    private double prevMotionX;
    private double prevMotionY;
    private double motionX;
    private double motionY;
    private boolean onGround;
    private boolean inWater;
    
    private final CollisionBox collisionBox;
    private final double width;
    private final double height;
    
    private double friction;
    
    private final double maxHealth;
    private double health;
    
    private Image image;
    
    private final Level level;
    
    public Entity(Level level, double x, double y, double width, double height, double health)
    {
        this.level = level;
        this.posX = x;
        this.posY = y;
        this.prevPosX = x;
        this.prevPrevY = y;
        this.motionX = 0;
        this.motionY = 0;
        this.onGround = false;
        this.inWater = false;
        this.collisionBox = new CollisionBox(0, 0, width, height);
        this.width = width;
        this.height = height;
        this.maxHealth = health;
        this.health = health;
        
        image = Utils.getColoredImage(0, 0, 0, 255, (int) width, (int) height);
    }

    public Level getLevel()
    {
        return level;
    }
    
    //--------------------------------------------------------------------------
    // ticking
    //--------------------------------------------------------------------------
    
    public void tick() 
    {
        inWater = false;
        
        if(isAffectedByGravity())
        {
            motionY -= GRAVITY;
        }
        
        prevMotionX = motionX;
        prevMotionY = motionY;
        
        prevPosX = posX;
        prevPrevY = posY;
        setAllowedMotion();
        posX += motionX;
        posY += motionY;   
        
        motionX = Utils.round(motionX);
        motionY = Utils.round(motionY);
        
        if(motionY != 0)
        {
            onGround = false;
        }
        
        posX = Utils.round(posX);
        posY = Utils.round(posY);
        
        LinkedList<CollisionBox> list = level.getMovementBoxesAt(this, getBox());
        if(!list.isEmpty())
        {
            System.out.println(getBox());
            list.forEach(s -> System.out.println(s));
            System.out.println("WELL");
            posX = prevPosX;
            posY = prevPrevY;
        }
        
        friction = 0.7f;
        doCollision();
        
        if(motionX < 0.0)
        {
            motionX += friction;
            if(motionX > 0.0)
            {
                motionX = 0.0;
            }
        }
        else if(motionX > 0.0)
        {
            motionX -= friction;
            if(motionX < 0.0)
            {
                motionX = 0.0;
            }
        }
    }
    
    //--------------------------------------------------------------------------
    // gravity, friction
    //--------------------------------------------------------------------------
    
    public final boolean jump()
    {
        if(onGround)
        {
            onGround = false;
            motionY += getJumpPower();
            return true;
        }
        return false;
    }
    
    public boolean isAffectedByGravity()
    {
        return true;
    }
    
    public double getJumpPower()
    {
        return 0;
    }
    
    public boolean isOnGround()
    {
        return onGround;
    }
    
    public final double getMaxSpeedModifier()
    {
        double max = 1;
        if(inWater)
        {
            max *= 0.65;
        }
        return max;
    }
    
    public final void setInWater(boolean b)
    {
        inWater = b;
    }
    
    public final boolean isInWater()
    {
        return inWater;
    }
    
    public void setFriction(double friction)
    {
        this.friction = friction;
    }
    
    public boolean isRunning()
    {
        return false;
    }
    
    //--------------------------------------------------------------------------
    // collision stuff
    //--------------------------------------------------------------------------
    
    public final CollisionBox getBox() 
    {
        return collisionBox.reset().offset(posX, posY);
    }
    
    private void setAllowedMotion()
    {
        if(motionX == 0 && motionY == 0)
        {
            return;
        }
        double dirX = motionX;
        double dirY = motionY;
        double startX = posX;
        double startY = posY;
        double endX = startX + dirX;
        double endY = startY + dirY;
        
        
        CollisionBox eBox = getBox();
        double halfWidth = eBox.getWidth() / 2;
        double halfHeight = eBox.getHeight() / 2;
        eBox = eBox.copy();
        
        startX += halfWidth;
        startY += halfHeight;
        endX += halfWidth;
        endY += halfHeight;
        
        // expanding area by the size of the entity
        CollisionBox expandedBox = eBox.copy().expand(motionX, motionY);
        
        LinkedList<CollisionBox> allBoxes = level.getMovementBoxesAt(this, expandedBox);
        if(allBoxes.isEmpty())
        {
            return;
        }        
        
        double x;
        double y;
        char lastWall = 0;
                
        if(dirX >= 0)
        {
            if(dirY <= 0) // Right - Down
            {
                for(CollisionBox box : allBoxes)
                {
                    box.grow(halfWidth, halfHeight);
                    if(endX <= box.getMinX() || endY >= box.getMaxY() ||
                        startX >= box.getMaxX() || startY <= box.getMinY())
                    {
                        continue;
                    }
                    x = Utils.interpolateX(startX, startY, endX, endY, box.getMaxY());
                    y = Utils.interpolateY(startX, startY, endX, endY, box.getMinX());
                    if(y > box.getMinY() && y < box.getMaxY())
                    {
                        endX = box.getMinX();
                        endY = y;
                        lastWall = 'x';
                    }
                    else if(x > box.getMinX() && x < box.getMaxX())
                    {
                        endY = box.getMaxY();
                        endX = x;
                        lastWall = 'y';
                    }
                    else if(x >= box.getMinX() && x <= box.getMaxX())
                    {
                        lastWall = 'y';
                    }
                }
                if(lastWall == 'x')
                {
                    double lineEndY = startY + dirY;
                    for(CollisionBox box : allBoxes)
                    {
                        if(endX <= box.getMinX() || lineEndY >= box.getMaxY() ||
                            endX >= box.getMaxX() || endY <= box.getMinY())
                        {
                            continue;
                        }
                        lineEndY = box.getMaxY();
                    }
                    endY = lineEndY;
                }
                else if(lastWall == 'y')
                {
                    double lineEndX = startX + dirX;
                    for(CollisionBox box : allBoxes)
                    {
                        if(lineEndX <= box.getMinX() || endY >= box.getMaxY() ||
                            endX >= box.getMaxX() || endY <= box.getMinY())
                        {
                            continue;
                        }
                        lineEndX = box.getMinX();
                    }
                    endX = lineEndX;
                }
            }
            else // Right - Up
            {
                for(CollisionBox box : allBoxes)
                {
                    box.grow(halfWidth, halfHeight);
                    if(endX <= box.getMinX() || endY <= box.getMinY() ||
                        startX >= box.getMaxX() || startY >= box.getMaxY())
                    {
                        continue;
                    }
                    x = Utils.interpolateX(startX, startY, endX, endY, box.getMinY());
                    y = Utils.interpolateY(startX, startY, endX, endY, box.getMinX());
                    if(y > box.getMinY() && y < box.getMaxY())
                    {
                        endX = box.getMinX();
                        endY = y;
                        lastWall = 'x';
                    }
                    else if(x > box.getMinX() && x < box.getMaxX())
                    {
                        endY = box.getMinY();
                        endX = x;
                        lastWall = 'y';
                    }
                    else if(x >= box.getMinX() && x <= box.getMaxX())
                    {
                        lastWall = 'y';
                    }
                }
                if(lastWall == 'x')
                {
                    double lineEndY = startY + dirY;
                    for(CollisionBox box : allBoxes)
                    {
                        if(endX <= box.getMinX() || lineEndY <= box.getMinY() ||
                            endX >= box.getMaxX() || endY >= box.getMaxY())
                        {
                            continue;
                        }
                        lineEndY = box.getMinY();
                    }
                    endY = lineEndY;
                }
                else if(lastWall == 'y')
                {
                    double lineEndX = startX + dirX;
                    for(CollisionBox box : allBoxes)
                    {
                        if(lineEndX <= box.getMinX() || endY >= box.getMaxY() ||
                            endX >= box.getMaxX() || endY <= box.getMinY())
                        {
                            continue;
                        }
                        lineEndX = box.getMinX();
                    }
                    endX = lineEndX;
                }
            }
        }
        else
        {
            if(dirY <= 0) // Left - Down
            {
                for(CollisionBox box : allBoxes)
                {
                    box.grow(halfWidth, halfHeight);
                    if(endX >= box.getMaxX() || endY >= box.getMaxY() ||
                        startX <= box.getMinX() || startY <= box.getMinY())
                    {
                        continue;
                    }
                    x = Utils.interpolateX(startX, startY, endX, endY, box.getMaxY());
                    y = Utils.interpolateY(startX, startY, endX, endY, box.getMaxX());
                    if(y > box.getMinY() && y < box.getMaxY())
                    {
                        endX = box.getMaxX();
                        endY = y;
                        lastWall = 'x';
                    }
                    else if(x > box.getMinX() && x < box.getMaxX())
                    {
                        endY = box.getMaxY();
                        endX = x;
                        lastWall = 'y';
                    }
                    else if(x >= box.getMinX() && x <= box.getMaxX())
                    {
                        lastWall = 'y';
                    }
                }
                if(lastWall == 'x')
                {
                    double lineEndY = startY + dirY;
                    for(CollisionBox box : allBoxes)
                    {
                        if(endX <= box.getMinX() || lineEndY >= box.getMaxY() ||
                            endX >= box.getMaxX() || endY <= box.getMinY())
                        {
                            continue;
                        }
                        lineEndY = box.getMaxY();
                    }
                    endY = lineEndY;
                }
                else if(lastWall == 'y')
                {
                    double lineEndX = startX + dirX;
                    for(CollisionBox box : allBoxes)
                    {
                        if(lineEndX >= box.getMaxX() || endY >= box.getMaxY() ||
                            endX <= box.getMinX() || endY <= box.getMinY())
                        {
                            continue;
                        }
                        lineEndX = box.getMaxX();
                    }
                    endX = lineEndX;
                }
            }
            else  // Left - Up
            {
                for(CollisionBox box : allBoxes)
                {
                    box.grow(halfWidth, halfHeight);
                    if(endX >= box.getMaxX() || endY <= box.getMinY() ||
                        startX <= box.getMinX() || startY >= box.getMaxY())
                    {
                        continue;
                    }
                    x = Utils.interpolateX(startX, startY, endX, endY, box.getMinY());
                    y = Utils.interpolateY(startX, startY, endX, endY, box.getMaxX());
                    if(y > box.getMinY() && y < box.getMaxY())
                    {
                        endX = box.getMaxX();
                        endY = y;
                        lastWall = 'x';
                    }
                    else if(x > box.getMinX() && x < box.getMaxX())
                    {
                        endY = box.getMinY();
                        endX = x;
                        lastWall = 'y';
                    }
                    else if(x >= box.getMinX() && x <= box.getMaxX())
                    {
                        lastWall = 'y';
                    }
                }
                if(lastWall == 'x')
                {
                    double lineEndY = startY + dirY;
                    for(CollisionBox box : allBoxes)
                    {
                        if(endX <= box.getMinX() || lineEndY <= box.getMinY() ||
                            endX >= box.getMaxX() || endY >= box.getMaxY())
                        {
                            continue;
                        }
                        lineEndY = box.getMinY();
                    }
                    endY = lineEndY;
                }
                else if(lastWall == 'y')
                {
                    double lineEndX = startX + dirX;
                    for(CollisionBox box : allBoxes)
                    {
                        if(lineEndX >= box.getMaxX() || endY >= box.getMaxY() ||
                            endX <= box.getMinX() || endY <= box.getMinY())
                        {
                            continue;
                        }
                        lineEndX = box.getMaxX();
                    }
                    endX = lineEndX;
                }
            }
        }
        
        motionX = endX - startX;
        motionY = endY - startY;
        
        if(dirY < 0 && motionY == 0)
        {
            onGround = true;
        }
        
        if(Math.abs(motionY) >= 1000 || Math.abs(motionX) >= 1000)
        {
            System.err.println("illegal movement " + motionX + " " + motionY);
            System.exit(0);
        }
        
        /*if(!onGround && Math.abs(dirY) - Math.abs(motionY) > 1)
        {
            System.out.println("\nVORHER " + dirX + " " + dirY);
            System.out.println("NACHHER " + motionX + " " + motionY);
            System.out.println("\nPOS " + posX + " " + posY);
            System.exit(0);
        }*/
        //System.exit(0);
    }
    
    private void doCollision()
    {
        CollisionBox box = getBox().copy();
        for(Face f : Face.values())
        {
            box.reset();
            box.expand(f.getCollisionOffsetX(), f.getCollisionOffsetY());
            level.getEntitiesCollidingWith(this, box).forEach(ent -> this.onCollideWithEntity(ent, f));
            level.getTilesCollidingWith(box).forEach(loc -> 
            {
                this.onCollideWithTile(loc, f);
                loc.getTile().onEntityCollide(this, loc.getX(), loc.getY(), f);
            });
        }
    }
    
    public void onCollideWithTile(Location loc, Face face)
    {
    }
    
    public void onCollideWithEntity(Entity ent, Face face)
    {
    }
    
    //--------------------------------------------------------------------------
    // health
    //--------------------------------------------------------------------------

    public double getHealth() 
    {
        return health;
    }

    public double getMaxHealth() 
    {
        return maxHealth;
    }
    
    public void addHealth(double h)
    {
        health += h;
        if(health > maxHealth)
        {
            health = maxHealth;
        }
        else if(health < 0.0)
        {
            onDeath();
        }
    }
    
    public void addHealthPercent(double h)
    {
        addHealth(h * maxHealth);
    }
    
    public void onDeath()
    {
    }
    
    //--------------------------------------------------------------------------
    // basic coord and motion stuff
    //--------------------------------------------------------------------------
    
    public final double getX()
    {
        return posX;
    }
    
    public final double getY()
    {
        return posY;
    }
    
    public final double getPreviousX()
    {
        return prevPosX;
    }
    
    public final double getPreviousY()
    {
        return prevPrevY;
    }
    
    public boolean isMoving()
    {
        return motionX != 0.0 || motionY != 0.0;
    }
    
    public final double getMotionX()
    {
        return motionX;
    }
    
    public final void setMotionX(double motionX)
    {
        this.motionX = motionX;
    }
    
    public final double getMotionY()
    {
        return motionY;
    }
    
    public final void setMotionY(double motionY)
    {
        this.motionY = motionY;
    }
    
    public final double getPreviousMotionX()
    {
        return prevMotionX;
    }
    
    public final double getPreviousMotionY()
    {
        return prevMotionY;
    }
    
    //--------------------------------------------------------------------------
    // rendering stuff
    //--------------------------------------------------------------------------
    
    public final double getWidth()
    {
        return width;
    }
    
    public final double getHeight()
    {
        return height;
    }
    
    public final double getRenderX()
    {
        return posX;
    }
    
    public final double getRenderY()
    {
        return posY + height;
    }
    
    public void setImage(Image image)
    {
        this.image = image;
    }
    
    public boolean drawImageFlipped()
    {
        return false;
    }
    
    public Image getImage()
    {
        return image;
    }
    
    public double getRenderOffsetX()
    {
        return 0;
    }
    
    public double getRenderOffsetY()
    {
        return 0;
    }
}
