/* 

Copyright 2006 Rene Grothmann, modified by Eric Hakenholz

This file is part of C.a.R. software.

C.a.R. is a free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.

C.a.R. is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */
package rene.zirkel.objects;

import eric.bar.JPropertiesBar;
import java.util.Enumeration;
import java.util.Vector;

import rene.gui.Global;
import rene.util.xml.XmlWriter;
import rene.zirkel.ZirkelCanvas;
import rene.zirkel.construction.Construction;
import rene.zirkel.construction.ConstructionException;
import rene.zirkel.expression.Expression;
import rene.zirkel.expression.InvalidException;
import rene.zirkel.graphics.MyGraphics;
import rene.zirkel.structures.Coordinates;

/**
 * 
 * @author erichake
 */
public class EquationXYObject extends ConstructionObject implements
        PointonObject {

    Vector V=new Vector();
    // Number of horizontal divisions for the full zero search algorithm :
    int Dhor=100;
    // canvas left,top,width,height :
    double Wl=0, Wt=0, Ww=0, Wh=0;
    Construction C;
    Expression EY=null;
    double X[]={0, 0};
    String Var[]={"x", "y"};

    public EquationXYObject(final Construction c, final String eq) {
        super(c);
        EY=new Expression(eq, c, this, Var);
        updateText();
        C=c;
    }

    public EquationXYObject(final Construction c, final String eq, final int d) {
        this(c, eq);
        Dhor=d;
    }

    public int getDhor() {
        return Dhor;
    }

    public void setDhor(final int newDHor) {
        Dhor=newDHor;
        compute();
        updateText();
    }

    @Override
    public void setDefaults() {
        setShowName(Global.getParameter("options.locus.shownames", false));
        setShowValue(Global.getParameter("options.locus.showvalues", false));
        setColor(Global.getParameter("options.locus.color", 0));
        setColorType(Global.getParameter("options.locus.colortype", 0));
        setFilled(Global.getParameter("options.locus.filled", false));
        setHidden(Cn.Hidden);
        setObtuse(Cn.Obtuse);
        setSolid(Cn.Solid);
        setLarge(Cn.LargeFont);
        setBold(Cn.BoldFont);
    }

    @Override
    public void setTargetDefaults() {
        setShowName(Global.getParameter("options.locus.shownames", false));
        setShowValue(Global.getParameter("options.locus.showvalues", false));
        setColor(Global.getParameter("options.locus.color", 0));
        setColorType(Global.getParameter("options.locus.colortype", 0));
        setFilled(Global.getParameter("options.locus.filled", false));
    }

    public void setEquation(final String eq, final ZirkelCanvas zc) {
        EY=new Expression(eq, C, this, Var);
        compute();
        updateText();

    }

    public double getValue(final String var) throws ConstructionException {
        if (!Valid) {
            throw new InvalidException("exception.invalid");
        }

        return (var.equals("x"))?X[0]:X[1];

    }

    public double evaluateF(final double x, final double y)
            throws ConstructionException {
        X[0]=x;
        X[1]=y;
        try {
            return EY.getValue();
        } catch (final Exception e) {
            throw new ConstructionException("");
        }
    }

    public void compute() {
        Wl=C.getX()-C.getW();
        Wt=C.getY()-C.getH()/2;
        Ww=2*C.getW();
        Wh=C.getH();
        searchZerosSegments();
    }

    public void searchZerosSegments() {
        V.clear();
        final double dx=Ww/Dhor;
        final double dy=dx*Math.sqrt(3)/2;
        final int xn=Dhor+2;
        final int yn=(int) (Wh/dy)+1;

        final double[] x=new double[xn+1];
        final double[] y=new double[yn+1];
        final double[][] z=new double[xn+1][yn+1];

        for (int i=0; i<=xn; i++) {
            x[i]=Wl-dx+i*dx;
        }
        for (int j=0; j<=yn; j++) {
            y[j]=Wt+j*dy;
        }
        for (int i=0; i<=xn; i++) {
            for (int j=0; j<=yn; j++) {
                xv[0]=x[i];
                xv[1]=y[j];
                if (j%2==0) {
                    xv[0]+=dx/2;
                }
                try {
                    z[i][j]=evaluateF(xv[0], xv[1]);
                } catch (final Exception e) {
                    z[i][j]=0;
                }
            }
        }
        for (int i=0; i<=xn-1; i++) {
            for (int j=0; j<=yn-1; j++) {
                if (j%2==0) {
                    searchOneZerosSegment(V, x[i]+dx/2, y[j], z[i][j],
                            x[i], y[j+1], z[i][j+1], x[i+1], y[j+1],
                            z[i+1][j+1]);
                    searchOneZerosSegment(V, x[i]+dx/2, y[j], z[i][j],
                            x[i+1]+dx/2, y[j], z[i+1][j], x[i+1],
                            y[j+1], z[i+1][j+1]);
                } else {
                    searchOneZerosSegment(V, x[i], y[j], z[i][j], x[i+1],
                            y[j], z[i+1][j], x[i]+dx/2, y[j+1],
                            z[i][j+1]);
                    searchOneZerosSegment(V, x[i+1], y[j], z[i+1][j],
                            x[i+1]-dx/2, y[j+1], z[i][j+1], x[i+1]+dx/2, y[j+1], z[i+1][j+1]);
                }
            }
        }
    }

    public void searchOneZerosSegment(final Vector v, final double x1,
            final double y1, final double z1, final double x2, final double y2,
            final double z2, final double x3, final double y3, final double z3) {
        if (z1*z2<0) {
            double l1=z2/(z2-z1);
            l1=fix(x1, y1, z1, x2, y2, z2, l1);
            final double m1=1-l1;
            if (z1*z3<0) {
                double l2=z3/(z3-z1);
                l2=fix(x1, y1, z1, x3, y3, z3, l2);
                final double m2=1-l2;
                v.add(new Coordinates(l1*x1+m1*x2, l1*y1+m1*y2, l2*x1+m2*x3, l2*y1+m2*y3));
            } else {
                double l2=z3/(z3-z2);
                l2=fix(x2, y2, z2, x3, y3, z3, l2);
                final double m2=1-l2;
                v.add(new Coordinates(l1*x1+m1*x2, l1*y1+m1*y2, l2*x2+m2*x3, l2*y2+m2*y3));
            }
        } else if (z1*z3<0) {
            double l1=z3/(z3-z1);
            l1=fix(x1, y1, z1, x3, y3, z3, l1);
            final double m1=1-l1;
            double l2=z3/(z3-z2);
            l2=fix(x2, y2, z2, x3, y3, z3, l2);
            final double m2=1-l2;
            v.add(new Coordinates(l1*x1+m1*x3, l1*y1+m1*y3, l2*x2+m2*x3, l2*y2+m2*y3));
        }
    }
    double xv[]=new double[2];

    public double fix(final double x1, final double y1, final double z1,
            final double x2, final double y2, final double z2, double l1) {
        try {
            final double z=evaluateF(x1*l1+x2*(1-l1), y1*l1+y2*(1-l1));
            if (Math.abs(z)>(Math.abs(z1)+Math.abs(z2))*1e-5) {
                final double mu=1-l1,  mu2=mu*mu,  mu3=mu2*mu,  mu4=mu3*mu;
                final double h=Math.sqrt(mu4*z2*z2+((-2*mu4+4*mu3-2*mu2)*z1-2*mu2*z)*z2+(mu4-4*mu3+6*mu2-4*mu+1)*z1*z1+(-2*mu2+4*mu-2)*z*z1+z*z);
                final double h1=(mu2*z2-mu2*z1+z1-z+h)/(2*(mu*z2-mu*z1+z1-z));
                final double h2=(mu2*z2-mu2*z1+z1-z-h)/(2*(mu*z2-mu*z1+z1-z));
                if (h1>=0&&h1<=1) {
                    l1=1-h1;
                } else if (h2>=0&&h2<=1) {
                    l1=1-h2;
                }
            }
        } catch (final Exception e) {
        }
        return l1;
    }

    @Override
    public void paint(final MyGraphics g, final ZirkelCanvas zc) {
        if (!Valid||mustHide(zc)) {
            return;
        }
        Coordinates c1;
        g.setColor(this);
        final Enumeration e=V.elements();
        while (e.hasMoreElements()) {
            c1=(Coordinates) e.nextElement();
            g.drawLine(zc.col(c1.X), zc.row(c1.Y), zc.col(c1.X1),
                    zc.row(c1.Y1), this);
        }
    }

    @Override
    public boolean nearto(final int cc, final int rr, final ZirkelCanvas zc) {

        final Enumeration e=V.elements();
        Coordinates c1;
        while (e.hasMoreElements()) {
            c1=(Coordinates) e.nextElement();
            if (((Math.abs(zc.col(c1.X)-cc)+Math.abs(zc.row(c1.Y)-rr))<5)||((Math.abs(zc.col(c1.X1)-cc)+Math.abs(zc.row(c1.Y1)-rr))<5)) {
                return true;
            }
        }

        return false;
    }

    public int getDistance(final PointObject P) {
        xx=P.getX();
        yy=P.getY();
        Coordinates c;
        double dd, dm=0, xm1=0, ym1=0, zm1=0, xm2=0, ym2=0, zm2=0;

        // search the closer segment [M1M2] :
        final Enumeration e=V.elements();
        if (e.hasMoreElements()) {
            c=(Coordinates) e.nextElement();
            xm1=c.X;
            ym1=c.Y;
            xm2=c.X1;
            ym2=c.Y1;
            dm=Math.sqrt((c.X-xx)*(c.X-xx)+(c.Y-yy)*(c.Y-yy))+Math.sqrt((c.X1-xx)*(c.X1-xx)+(c.Y1-yy)*(c.Y1-yy))-Math.sqrt((c.X-c.X1)*(c.X-c.X1)+(c.Y-c.Y1)*(c.Y-c.Y1));
        } else {
            return Integer.MAX_VALUE;
        }
        while (e.hasMoreElements()) {
            c=(Coordinates) e.nextElement();
            dd=Math.sqrt((c.X-xx)*(c.X-xx)+(c.Y-yy)*(c.Y-yy))+Math.sqrt((c.X1-xx)*(c.X1-xx)+(c.Y1-yy)*(c.Y1-yy))-Math.sqrt((c.X-c.X1)*(c.X-c.X1)+(c.Y-c.Y1)*(c.Y-c.Y1));
            if (dd<dm) {
                xm1=c.X;
                ym1=c.Y;
                xm2=c.X1;
                ym2=c.Y1;
                dm=dd;
            }
        }

        // coords du vecteur unitaire directeur de (M1M2) :
        double lg=Math.sqrt((xm2-xm1)*(xm2-xm1)+(ym2-ym1)*(ym2-ym1));
        double dx=(xm2-xm1)/lg;
        double dy=(ym2-ym1)/lg;

        // coords du point H projeté ortho de P sur (M1M2) :
        final double h=(xx-xm1)*dx+(yy-ym1)*dy;
        final double xh=xm1+h*dx;
        final double yh=ym1+h*dy;

        // coords du vecteur unitaire directeur de (HP) :
        lg=Math.sqrt((xx-xh)*(xx-xh)+(yy-yh)*(yy-yh));
        dx=(xx-xh)/lg;
        dy=(yy-yh)/lg;

        // ten pixel length :
        final double d10=10/C.getPixel();

        // Try to have a very accurate precision :
        try {
            xm1=xh+d10*dx;
            ym1=yh+d10*dy;
            zm1=evaluateF(xm1, ym1);
            xm2=xh-d10*dx;
            ym2=yh-d10*dy;
            zm2=evaluateF(xm2, ym2);
            if (zm1*zm2<0) {
                findRootBetween(xm1, ym1, zm1, xm2, ym2, zm2);
                dd=Math.sqrt((P.getX()-xx)*(P.getX()-xx)+(P.getY()-yy)*(P.getY()-yy));
                return (int) Math.round(dd*Cn.getPixel());
            }
        } catch (final Exception ex) {
        }

        // If nothing found, then sorry... :
        return Integer.MAX_VALUE;
    }

    public void project(final PointObject P) {

        xx=P.getX();
        yy=P.getY();
        Coordinates c;
        double dd, dm=0, xm1=0, ym1=0, zm1=0, xm2=0, ym2=0, zm2=0;

        // search the closer segment [M1M2] :
        final Enumeration e=V.elements();
        if (e.hasMoreElements()) {
            c=(Coordinates) e.nextElement();
            xm1=c.X;
            ym1=c.Y;
            xm2=c.X1;
            ym2=c.Y1;
            dm=Math.sqrt((c.X-xx)*(c.X-xx)+(c.Y-yy)*(c.Y-yy))+Math.sqrt((c.X1-xx)*(c.X1-xx)+(c.Y1-yy)*(c.Y1-yy))-Math.sqrt((c.X-c.X1)*(c.X-c.X1)+(c.Y-c.Y1)*(c.Y-c.Y1));
        } else {
            return;
        }
        while (e.hasMoreElements()) {
            c=(Coordinates) e.nextElement();
            dd=Math.sqrt((c.X-xx)*(c.X-xx)+(c.Y-yy)*(c.Y-yy))+Math.sqrt((c.X1-xx)*(c.X1-xx)+(c.Y1-yy)*(c.Y1-yy))-Math.sqrt((c.X-c.X1)*(c.X-c.X1)+(c.Y-c.Y1)*(c.Y-c.Y1));
            if (dd<dm) {
                xm1=c.X;
                ym1=c.Y;
                xm2=c.X1;
                ym2=c.Y1;
                dm=dd;
            }
        }

        // coords du vecteur unitaire directeur de (M1M2) :
        double lg=Math.sqrt((xm2-xm1)*(xm2-xm1)+(ym2-ym1)*(ym2-ym1));
        double dx=(xm2-xm1)/lg;
        double dy=(ym2-ym1)/lg;

        // coords du point H projeté ortho de P sur (M1M2) :
        final double h=(xx-xm1)*dx+(yy-ym1)*dy;
        final double xh=xm1+h*dx;
        final double yh=ym1+h*dy;

        // coords du vecteur unitaire directeur de (HP) :
        lg=Math.sqrt((xx-xh)*(xx-xh)+(yy-yh)*(yy-yh));
        dx=(xx-xh)/lg;
        dy=(yy-yh)/lg;

        // ten pixel length :
        final double d10=10/C.getPixel();

        // Try to have a very accurate precision :
        try {
            xm1=xh+d10*dx;
            ym1=yh+d10*dy;
            zm1=evaluateF(xm1, ym1);
            xm2=xh-d10*dx;
            ym2=yh-d10*dy;
            zm2=evaluateF(xm2, ym2);
            if (zm1*zm2<0) {
                findRootBetween(xm1, ym1, zm1, xm2, ym2, zm2);
                P.move(xx, yy);
                return;
            }
        } catch (final Exception ex) {
        }

        // If nothing found, then project on the segment (low precision) :
        P.move(xh, yh);
    }
    double xx, yy, zz;

    public void findRootBetween(final double x1, final double y1,
            final double z1, final double x2, final double y2, final double z2) {
        try {
            xx=(x1+x2)/2;
            yy=(y1+y2)/2;
            zz=evaluateF(xx, yy);
            if (Math.abs(zz)<1e-10) {
                return;
            }
            if (zz*z1<0) {
                findRootBetween(x1, y1, z1, xx, yy, zz);
            } else {
                findRootBetween(xx, yy, zz, x2, y2, z2);
            }
        } catch (final Exception e) {
        }
    }

    public void project(final PointObject P, final double alpha) {
        project(P);
    }

    public boolean canInteresectWith(final ConstructionObject o) {
        return false;
    }

    @Override
    public String getEY() {
        if (EY!=null) {
            return EY.toString();
        } else {
            return "0";
        }
    }

    @Override
    public String getDisplayValue() {
        return EY.toString();
    }

    @Override
    public String getTag() {
        return "EqXY";
    }

    @Override
    public void printArgs(final XmlWriter xml) {
        xml.printArg("f", EY.toString());
        xml.printArg("Dhor", String.valueOf(Dhor));
    }

    @Override
    public void updateText() {
        setText(getDisplayValue()+"=0");
    }



    public void repulse(final PointObject P) {
        project(P);
    }
}
