package modelling;

import java.util.Vector;
import java.awt.Color;
import java.util.Iterator;
import java.util.TreeMap;

import sema.Element;
import sema.Map;
import sema.Drawing;
import sema.Area;
import sema.Box;

/**
 * Implements circular area.
 * The boxes coverage is a square containing the area
 */
public class CircularArea implements Area {
    
    public CircularArea(Map map,float x, float y, float radius, float angle) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.map = map;
        this.lastX = x;
        this.lastY = y;
        this.lastRadius = radius;
        refreshBoxes();
        this.angle = 0;
        this.lastAngle = angle;
        this.lastV = intersectedBoxes;
    }
    
    private Map map;
    private Element element;
    
    private float radius;
    
    private float lastX;
    private float lastY;
    private float lastRadius;
    private Vector lastV;
    private float lastAngle;
    
    public final float sqr2 = (float)Math.sqrt(2);
    public final float sqr2s2 = sqr2/2;
    
    //cases occupées par le rectangle
    private Vector intersectedBoxes = new Vector();
    
    public Vector getIntersectedBoxes() {
        return intersectedBoxes;
    }
    
    private void registerBoxes() { //enregistre l'élément auprès des cases occupées par la zone
        if (element!=null)
            for (int i=0; i<intersectedBoxes.size(); i++) {
                {
                    ((Box) intersectedBoxes.get(i)).register(element);
                }
            }
    }
    
    private void unregisterBoxes() { //désenregistre l'élément auprès des cases occupées par la zone
        if (element!=null)
            for (int i=0; i<intersectedBoxes.size(); i++) {
                {
                    ((Box) intersectedBoxes.get(i)).unregister(element);
                }
            }
        
    }
    
    
    private Vector computeBoxes(){
        return map.getCoveringBoxes(x-radius, y-radius, 2*radius, 2*radius);
    }
    private void refreshBoxes(){
        unregisterBoxes();
        intersectedBoxes = computeBoxes();
        registerBoxes();
    }
    
    public void setElement(Element el) {
        this.element = el;
        refreshBoxes();
    }
    
    private float x;
    private float y;
    
    private float angle;
    
    public float getX() {
        return(x);
    }
    
    public float getY() {
        return(y);
    }
    
    public float getAngle() {
        return(angle);
    }
    
    
    public boolean isIn(float px, float py){
        return(Math.sqrt((px-x)*(px-x)+(py-y)*(py-y))<=radius);
    }
    
    /**
     * Rounded here, because of the approximate covering boxes (there would be undue collisions!).
     * This is because this area is designed to be fast.
     * The elementsCollision should be used for this kind of area.
     * However, the box-base collision is always an approximation!
     */
    public boolean isCrashingElements() {
        return isCrashingElements(getIntersectedBoxes());
    }
    
    public boolean isCrashingElements(Vector v) {
        if (v==null) return true;
        for (int i = 0; i<v.size(); i++){
            Box b = (Box) v.get(i);
            if (!b.isFreeFor(element)) return true;
            int s =b.getContents().size();
            if (s>1 || (s==1 & !(b.getContents().contains(element)))) {
                return(true);
            }
        }
        return(false);
    }
    
    public boolean isPolygon(){
        return false;
    }
    
    public Vector getPolygon(){ //on retourne 8 sommets équirépartis
        Vector v = new Vector();
        float x1 = x+radius;
        float y1 = y;
        float x2 = x+radius*sqr2s2;
        float y2 = y +radius*sqr2s2;
        float x3 = x;
        float y3 = y+radius;
        float x4 = x - radius * sqr2s2;
        float y4 = y + radius*sqr2s2;
        float x5 = x-radius;
        float y5 = y;
        float x6 = x-radius*sqr2s2;
        float y6 = y-radius*sqr2s2;
        float x7 = x;
        float y7 = y-radius;
        float x8 = x+radius*sqr2s2;
        float y8 = y-radius*sqr2s2;
        float[] p1 = new float[2];
        p1[0]=x1; p1[1]=y1;
        float[] p2 = new float[2];
        p2[0]=x2; p2[1]=y2;
        float[] p3 = new float[2];
        p3[0]=x3; p3[1]=y3;
        float[] p4 = new float[2];
        p4[0]=x4; p4[1]=y4;
        float[] p5 = new float[2];
        p5[0]=x5; p5[1]=y5;
        float[] p6 = new float[2];
        p6[0]=x6; p6[1]=y6;
        float[] p7 = new float[2];
        p7[0]=x7; p7[1]=y7;
        float[] p8 = new float[2];
        p8[0]=x8; p8[1]=y8;
        v.add(p1);
        v.add(p2);
        v.add(p3);
        v.add(p4);
        v.add(p5);
        v.add(p6);
        v.add(p7);
        v.add(p8);
        return v;
    }
    
    
    private float vectorial(float px, float py, float ax, float ay, float bx, float by) { //composante suivant z du produit vectoriel PA^PB)
        return((ax-px)*(by-py)-(ay-py)*(bx-px));
    }
    
    public boolean intersect(float ax, float ay, float bx, float by){
        float d = (float)Math.sqrt((ax-bx)*(ax-bx)+(ay-by)*(ay-by));
        if (isIn(ax,ay)||isIn(bx,by)) return true;
        if (d>0)
            if (Math.abs(vectorial(ax,ay,bx,by,x,y))<=radius*d) {
            float px = x+ay-by;
            float py = y+bx-ax;
            return (vectorial(ax,ay,x,y,px,py)*vectorial(bx,by,x,y,px,py)<=0);
            } else return false;
        else return false;
    }
    
    
    public boolean intersect(Vector p1){
        int n = p1.size();
        for (int i = 0; i<n-1;i++){
            float[] temp = (float[])p1.get(i);
            float ax = temp[0];
            float ay = temp[1];
            float[] temp2 = (float[])p1.get(i+1);
            float bx = temp2[0];
            float by = temp2[1];
            if (intersect(ax,ay,bx,by)) return true;
        }
        if (n>1) {
            float[] temp = (float[])p1.get(n-1);
            float ax = temp[0];
            float ay = temp[1];
            float[] temp2 = (float[])p1.get(0);
            float bx = temp2[0];
            float by = temp2[1];
            return (intersect(ax,ay,bx,by));
        }
        return false;
        
    }
    
    public boolean intersect(Area a){
        return this.intersect(a.getPolygon());
    }
    
    
    
    public boolean exactIsCrashingElements() { //peut servir de fonction de gestion des collisions entre éléments
        return exactIsCrashingElements(getIntersectedBoxes());
    }
    
    public boolean exactIsCrashingElements(Vector v) { //peut servir de fonction de gestion des collisions entre éléments
        if (v==null) return true;
        for (int i = 0; i<v.size(); i++){
            Box b = (Box) v.get(i);
            if (!b.isFreeFor(element)) return true;
            Iterator it = b.getContents().iterator();
            while (it.hasNext()) {
                Element el = (Element)it.next();
                if (el!=element)
                    if (this.intersect(el.getArea()))
                        return true;
            }
        }
        return(false);
    }
    
    
    public boolean translate(float x, float y){
        float backX = this.x;
        float backY = this.y;
        this.x = this.x + x;
        this.y = this.y + y;
        if (inMap()) {
            refreshBoxes();
            return true;} else {
            this.x = backX;
            this.y = backY;
            return false;}
        
    }
    
    public boolean setRadius(float r){
        if (r<0) return false;
        float backradius = radius;
        radius = r;
        if (inMap()) {refreshBoxes(); return true;}
        radius = backradius;
        return false;
    }
    
    public boolean setX(float x){
        float backX = this.x;
        this.x = x;
        if (inMap()) {
            refreshBoxes();
            return true;} else {
            this.x = backX;
            return false;}
        
    }
    
    public boolean setY(float y){
        float backY = this.y;
        this.y = y;
        if (inMap()) {
            refreshBoxes();
            return true;} else {
            this.y = backY;
            return false;}
    }
    
    
    public Vector simuleTranslate(float x, float y){
        float backX = this.x;
        float backY = this.y;
        this.x = this.x + x;
        this.y = this.y + y;
        if (!inMap()) {
            this.x = backX;
            this.y = backY;
            this.lastX = this.x;
            this.lastY = this.y;
            this.lastRadius = radius;
            this.lastAngle = angle;
            this.lastV = intersectedBoxes;
            return null;
        }
        Vector v = computeBoxes();
        this.lastX = this.x;
        this.lastY = this.y;
        this.lastRadius = radius;
        this.lastAngle = angle;
        this.lastV = v;
        this.x = backX;
        this.y = backY;
        return v;
    }
    
    public void confirmSimuled(){
        x = lastX;
        y = lastY;
        radius = lastRadius;
        lastAngle = angle;
        intersectedBoxes = lastV;
        this.x = this.x + x;
        this.y = this.y + y;
        unregisterBoxes();
        intersectedBoxes = lastV;
        registerBoxes();
    }
    
    
    private float toZero2PI(float f){ //retourne la valeur module 2Pi de f dans [0;2*Pi[.
        double t = Math.IEEEremainder(f, 2.*Math.PI);
        if (t<0) return (float)(t+2*Math.PI); else return (float)t;
    }
    
    public boolean rotate(float angle){ //angle de rotation dans [-2Pi;2Pi]
        float backAngle = this.angle;
        this.angle = toZero2PI(this.angle + angle);
        if (inMap()) {
            refreshBoxes();
            return true;} else {
            this.angle = backAngle;
            return false;}
    }
    
    
    public boolean setAngle(float a){
        float backA = this.angle;
        this.angle = toZero2PI(a);
        if (inMap()) {
            refreshBoxes();
            return true;} else {
            this.angle = backA;
            return false;}
        
    }
    
    
    public Vector simuleRotate(float angle){ //always remains in the map!
        float backAngle = this.angle;
        this.angle = toZero2PI(this.angle + angle);
        Vector v = computeBoxes();
        this.lastX = x;
        this.lastY = y;
        this.lastAngle = this.angle;
        this.lastRadius = radius;
        this.lastV = v;
        this.angle = backAngle;
        return v;
    }
    
    
    public boolean rescale(float x, float y){
        float backRadius = radius;
        radius = radius * (x+y)/2;
        if (inMap()) {
            refreshBoxes();
            return true;} else {
            this.radius = backRadius;
            return false;}
    }
    
    public Vector simuleRescale(float xS, float yS){
        float backRadius = radius;
        radius = radius * (xS+yS)/2;
        if (!inMap()) return null;
        Vector v = computeBoxes();
        if (!inMap()) {
            this.radius = backRadius;
            this.lastX = x;
            this.lastY = y;
            this.lastRadius = radius;
            this.lastAngle = angle;
            this.lastV = intersectedBoxes;
            return null;
        }
        this.lastX = x;
        this.lastY = y;
        this.lastRadius = radius;
        this.lastV = v;
        this.lastAngle = angle;
        this.radius = backRadius;
        return v;
    }
    
    
    public boolean inMap() {
        return (x>=radius && x<=(map.xLength-radius) & y>=radius && y<=(map.yLength-radius));
    }
    
    public void draw(Drawing g) {
        g.setColor(Color.WHITE);
        g.fillCircle(x,y,radius);
        g.setColor(Color.BLACK);
        if (element!=null)
            g.drawImage(element.getImage(),x-radius*sqr2s2,y-radius*sqr2s2,radius*sqr2,radius*sqr2,angle,x,y);
        g.drawCircle(x,y,radius);
    }
    
    public void fill(Drawing g, Color c) {
        g.setColor(c);
        g.fillCircle(x,y,radius);
    }
    
    public void drawBorder(Drawing g, Color c) {
        g.setColor(c);
        g.drawCircle(x,y,radius);
    }
    
    
    public void highlight(Drawing g, Color c) {
        g.setColor(c);
        g.fillCircle(x,y,radius);
        g.setColor(Color.BLACK);
        g.drawCircle(x,y,radius);    }
    
    
    public String[][] getProperties() {
        String[][] ret = new String[5][2];
        String[] p1 = {"x",new Float(x).toString()};
        String[] p2 = {"y",new Float(y).toString()};
        String[] p4 = {"radius",new Float(radius).toString()};
        String[] p5 = {"angle (in degrees)",new Float(180*angle/Math.PI).toString()};
        String[] p6 = {"intersected boxes",new Integer(intersectedBoxes.size()).toString()};
        ret[0] = p1; ret[1] = p2; ret[2] = p4; ret[3] = p5; ret[4] = p6;
        return(ret);
    }
    
    public void setProperties(String s1, String s2) {
        try {
            if (s1.equals("x")) {
                setX(Float.parseFloat(s2));
            } else
                if (s1.equals("y")) {
                setY(Float.parseFloat(s2));
                } else
                    if (s1.equals("radius")) {
                setRadius(Float.parseFloat(s2));
                    } else
                        if (s1.equals("angle (in degrees)")) {
                setAngle(Float.parseFloat(s2)/180f*(float)Math.PI);
                        };
        } catch (Exception e) {}
    }
    
    public Object clone(){
        return new CircularArea(map, x, y, radius,angle);
    }
    
}
