package algorithmrepository.contouring;

import java.util.ArrayList;
import svg.SVGLineWriter;
import algorithmrepository.DynamicDoubleArray;

/** Object for collecting contour points and contours */
public class ContourCollector implements ContourCallback{
	
	/** Object describing a single contour */
	public static class Contour {
		/** coordinates of points */
		public double[] x, y;
		/** value of f which the contour is at */
		public double value;		
		/** Why the contouring stopped -  ? */
		public int terminationReason;
		public Contour(double x[], double y[], double value, int terminationReason) {
			this.x = x;
			this.y = y;
			this.value = value;
			this.terminationReason = terminationReason;
		}
		
		public boolean isClosed(){ return terminationReason == Contouring.RETURN_CLOSED; }
		public boolean isComplete(){ return terminationReason == Contouring.RETURN_CLOSED || terminationReason == Contouring.RETURN_HIT_GRID_BOUNDARY; }
		
	}		
	
	DynamicDoubleArray currentX, currentY;
	ArrayList<Contour> contours;
	
	int nPointsTotal;
	int nPointsClosed;
	int nPointsAborted;
	int nPointsHitGridBoundary;
	int nPointsFailed;
	int nContoursTotal;
	int nContoursClosed;
	int nContoursAborted;
	int nContoursHitGridBoundary;
	int nContoursFailed;    
	
	private int nExpectedContours;
	private int nExpectedPoints;
    
	public ContourCollector() { this(100, 1000); }
		
	/** Create contour collector */
	public ContourCollector(int nExpectedContours, int nExpectedPoints) {
	    this.nExpectedPoints = nExpectedPoints;
	    this.nExpectedContours = nExpectedContours;
	    
	    clear();	    
	}
	
	public void clear(){
	    contours = new ArrayList<Contour>(nExpectedContours);
        
        //make the initial arrays
        currentX = new DynamicDoubleArray(nExpectedPoints);
        currentY = new DynamicDoubleArray(nExpectedPoints);
       
        nPointsTotal = 0;
        nPointsClosed = 0;
        nPointsAborted = 0;
        nPointsHitGridBoundary = 0;
        nPointsFailed = 0;
        nContoursTotal = 0;
        nContoursClosed = 0;
        nContoursAborted = 0;
        nContoursHitGridBoundary = 0;
        nContoursFailed = 0;
	}	

	public boolean contourPoint(double contourValue, double x, double y) {
		currentX.add(x);
		currentY.add(y);
		return true;
	}
		
	public boolean contourEnd(double contourValue, int terminationReason) {
	    double x[] = currentX.getTrimmedArray();
	    double y[] = currentY.getTrimmedArray();
	    
		Contour lastContour = new Contour(x,y,contourValue, terminationReason );		
		contours.add(lastContour);
		
		int nPoints = x.length;
		
        nContoursTotal++; nPointsTotal += nPoints;
        switch (terminationReason) {
            case Contouring.RETURN_FAILED: nContoursFailed++; nPointsFailed++; break;
            case Contouring.RETURN_ABORTED: nContoursAborted++; nPointsAborted += nPoints; break;
            case Contouring.RETURN_CLOSED: nContoursClosed++; nPointsClosed += nPoints; break;
            case Contouring.RETURN_HIT_GRID_BOUNDARY: nContoursHitGridBoundary++; nPointsHitGridBoundary += nPoints; break;        
        }
		
        //recreate the arrays back at the originally specified expected size
        //If the arrays are not recrated here (e.g they are just reset), then 
        //the now stored data gets overwritten since they are the same object.
        currentX = new DynamicDoubleArray(nExpectedPoints);
        currentY = new DynamicDoubleArray(nExpectedPoints);
    
		return true;
	}
	
	/** Returns an ArrayList of ContourCollector.Contour objects of the contours collected */
	public ArrayList<ContourCollector.Contour> getContours(){ return contours; }
	
	/** Gets packed arrays for all contours - (see {@link}getPackedArrays()}) */
	public double[][] getPackedArraysAllContours(){ return getPackedArrays(Integer.MIN_VALUE); } 
	
	/** Returns 2xn array containing X and Y coords of points on all contours.
	 * Contours are separated 2 entry descriptor:<BR>
	 * <TABLE BORDER='0'>
	 * <TR><TD>ret[0] = X = [</TD><TD>NaN,</TD><TD>m0,</TD><TD>x00,</TD><TD>x01,</TD><TD>x02</TD><TD>&nbsp;...&nbsp;&nbsp;&nbsp;</TD><TD>x0m0,
	 *                       </TD><TD>NaN,</TD><TD>m1,</TD><TD>x10,</TD><TD>x11</TD><TD>... ]</TD></TR>
	 * <TR><TD>ret[1] = Y = [</TD><TD>Value0,</TD><TD>termFlag0,</TD><TD>y00,</TD><TD>y01,</TD><TD>y02m</TD><TD>&nbsp;...&nbsp;&nbsp;&nbsp;</TD><TD>y0m0,
	 *                       </TD><TD>Value1,</TD><TD>TermFlag1,</TD><TD>y10,</TD><TD>y11</TD><TD>... ]</TD></TR>
	 * </TABLE><!-- yes, I really used a table in javadoc -->
	 * <BR>
	 * m_i is the number of points in the ith contour.<BR>
	 * termFlag_i is the contour termination cause (see Contouring.RETURN_* )<BR>
	 *          
	 */
	public double[][] getPackedArrays(int terminationReason){
	    boolean anyTerm = (terminationReason == Integer.MIN_VALUE);
        int n = getNPoints(terminationReason) + 2 * getNContours(terminationReason);
        double dataOut[][] = new double[2][n];
        
        int j=0;
        for(Contour c : contours)
            if(anyTerm || terminationReason == c.terminationReason){
                dataOut[0][j] = Double.NaN; //mark contour start
                dataOut[1][j++] = c.value; //contour value
                dataOut[0][j] = c.x.length; //length (i.e number of points that follow)
                dataOut[1][j++] = c.terminationReason; //Termination reason
                System.arraycopy(c.x, 0, dataOut[0], j, c.x.length);
                System.arraycopy(c.y, 0, dataOut[1], j, c.x.length);
                j += c.x.length;
            }
        return dataOut;
	}

	/** Returns 2 1D arrays containing all the X and Y coords on all contours and no other information. */
    public double[][] getPointsArraysAllContours(){ return getPointsArrays(Integer.MIN_VALUE); }
    
	/** Returns 2 1D arrays containing all X and Y coords on all contours that were terminated 
	 * for the specified reason (no other information is in the arrays). */
    public double[][] getPointsArrays(int terminationReason){
        boolean anyTerm = (terminationReason == Integer.MIN_VALUE);
        int n = getNPoints(terminationReason);        
        double dataOut[][] = new double[2][n];
        int j=0;
        
        for(Contour c : contours)
            if(anyTerm || terminationReason == c.terminationReason){
                    System.arraycopy(c.x, 0, dataOut[0], j, c.x.length);
                    System.arraycopy(c.y, 0, dataOut[1], j, c.x.length);
                    j += c.x.length;
            }            
                
        return dataOut;
    }
    
    /** Returns 2 1D arrays containing all X and Y coords on the given contour (by index) */
    public double[][] getContourPoints(int index){
        Contour c = contours.get(index);
        return new double[][]{ c.x, c.y };
    }
    
        
    /** returns the coordinates { x[], y[] } of the end positions of all the contours */
    public double[][] getContourEnds(){ return getContourEnds(Integer.MIN_VALUE); }
    
    /** returns the coordinates { x[], y[] } of the end positions of all the specified contours */
    public double[][] getContourEnds(int terminationReason){
        boolean anyTerm = (terminationReason == Integer.MIN_VALUE);
        int n = getNContours(terminationReason);
        double dataOut[][] = new double[2][2*n];
        
        int j=0;
        for(Contour c : contours)
            if(anyTerm || terminationReason == c.terminationReason){
                dataOut[0][j] = c.x[0];
                dataOut[1][j++] = c.y[0];
                dataOut[0][j] = c.x[c.x.length-1];
                dataOut[1][j++] = c.y[c.y.length-1];
            }
            
        return dataOut;
    }
    
    /** Writes basic SVG containing containing all the contours.
     * @param bbox The bounding box for the SVG in contour coords: {x0, y0, x1, y1}
     * @param colourByTerm If true contours are colour by termination reason: 
     *          red=aborted, blue=closed, black=gridboundary, green=other
     * If */
    public void writeSVGAllContours(String fileName, double bbox[], boolean colourByTerm){        
        SVGLineWriter svg = new SVGLineWriter(fileName, bbox);
        String style;
        
        svg.addLineStyle("Lgray", "none", 0.01, "gray");
        svg.addLineStyle("Lred", "none", 0.01, "red");
        svg.addLineStyle("Lblue", "none", 0.01, "blue");
        svg.addLineStyle("Lblack", "none", 0.01, "black");
        svg.addLineStyle("Lgreen", "none", 0.01, "green");
        
        for(Contour c : contours){
            switch(c.terminationReason){
                case Contouring.RETURN_FAILED: style = "Lgray"; break;
                case Contouring.RETURN_ABORTED: style = "Lred"; break;
                case Contouring.RETURN_CLOSED: style = "Lblue"; break;
                case Contouring.RETURN_HIT_GRID_BOUNDARY: style = "Lblack"; break;
                default: style = "Lgreen"; // ??
            }
            svg.addLine(c.x, c.y, style);
        }
        
                
        svg.destroy();  
    }
    
    /**  Writes basic SVG containing containing the contours that were terminated 
     * for the specified reason. */
    public void writeSVGByTerm(String fileName, double bbox[], int terminationReason){
        boolean anyTerm = (terminationReason == Integer.MIN_VALUE);
        SVGLineWriter svg = new SVGLineWriter(fileName, bbox);
        
        for(Contour c : contours)
            if(anyTerm || terminationReason == c.terminationReason)
                    svg.addLine(c.x, c.y);
                
        svg.destroy();        
    }
    
    /** Gets the number of points on all contours of a given termination type */
    public final int getNPoints(int terminationReason){ 
        switch(terminationReason){
            case Contouring.RETURN_FAILED: return nPointsFailed;
            case Contouring.RETURN_ABORTED: return nPointsAborted; 
            case Contouring.RETURN_CLOSED: return nPointsClosed;
            case Contouring.RETURN_HIT_GRID_BOUNDARY: return nPointsHitGridBoundary;
            default: return nPointsTotal;
        }
    }
    
    /** Gets the number of contours of a given termination type */
    public final int getNContours(int terminationReason){ 
        switch(terminationReason){
            case Contouring.RETURN_FAILED: return nContoursFailed;
            case Contouring.RETURN_ABORTED: return nContoursAborted; 
            case Contouring.RETURN_CLOSED: return nContoursClosed;
            case Contouring.RETURN_HIT_GRID_BOUNDARY: return nContoursHitGridBoundary;
            default: return nContoursTotal;
        }
    }
    
    public final int getNPointsTotal() { return nPointsTotal;  }
    public final int getNPointsClosed() { return nPointsClosed;  }
    public final int getNPointsAborted() { return nPointsAborted; }
    public final int getNPointsHitGridBoundary() { return nPointsHitGridBoundary; }
    public final int getNPointsFailed() { return nPointsFailed; }
    public final int getNContoursTotal() { return  nContoursTotal; }
    public final int getNContoursClosed() { return nContoursClosed; }
    public final int getNContoursAborted() { return nContoursAborted; }
    public final int getNContoursHitGridBoundary() { return nContoursHitGridBoundary; }
    public final int getNContoursPathological() { return nContoursFailed; }

    public final String toString(){
        return "Collection of: " + 
                "Contours: "
                + nContoursClosed + " closed, "
                + nContoursHitGridBoundary + " hit boundary, "
                + nContoursAborted + " aborted, "
                + nContoursFailed + " failed, "
                + nContoursTotal + " total; " + 
                "with points: "
                + nPointsClosed + " closed, "
                + nPointsHitGridBoundary + " hit boundary, "
                + nPointsAborted + " aborted, "
                + nPointsFailed + " failed, "
                + nPointsTotal + "total.";
    }

}
