/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2003-2006, Geotools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 */
package org.geotools.data.oracle.sdo;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateList;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.PrecisionModel;


/**
 * HelperClass for dealing with JTS14 CoordianteSequences.
 * 
 * <p>
 * JTS14 does not supply suffiecnt API to allow the modification of
 * CoordinateSequence in a lossless manner. To make full use of this class
 * your CoordianteSequence will need to support the additional methods
 * outlined in CoordinateAccess.
 * </p>
 *
 * @author bowens , Refractions Research, Inc.
 * @author $Author: jgarnett $ (last modification)
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/oracle-spatial/src/main/java/org/geotools/data/oracle/sdo/Coordinates.java $
 * @version $Id: Coordinates.java 23604 2006-12-27 09:53:19Z aaime $
 */
public class Coordinates {
    private Coordinates() {
        // utility class do not inaniate
    }

    /**
     * Sublist opperation for CoordinateSequence.
     * 
     * <p>
     * Opperates in the same manner as corrasponding java util List method.
     * </p>
     *
     * @param factory factory used to manage sequence
     * @param sequence coordinate sequence
     * @param fromIndex - low endpoint (inclusive) of the subList.
     * @param toIndex - high endpoint (exclusive) of the subList.
     *
     * @return sublist of sequence (start,end] as provided by factory
     */
    public static CoordinateSequence subList(
        CoordinateSequenceFactory factory, CoordinateSequence sequence,
        int fromIndex, int toIndex) {
        if ((fromIndex == 0) && (toIndex == sequence.size())) {
            return sequence; // same list so just return it
        }

        if (sequence instanceof List) {
            List sublist = ((List) sequence).subList(fromIndex, toIndex);

            if (sublist instanceof CoordinateSequence) {
                return (CoordinateSequence) sublist;
            }
        }

        if (sequence instanceof CoordinateAccess) {
            CoordinateAccess access = (CoordinateAccess) sequence;
            double[][] coordArray = access.toOrdinateArrays();
            Object[] attributeArray = access.toAttributeArrays();

            double[][] subCoordArray = new double[access.getDimension()][];
            Object[][] subAttributeArray = new Object[access.getNumAttributes()][];

            //							System.out.println("Dimension = " + access.getDimension());
            //							System.out.println("coordArray.length = " + coordArray.length);
            //							System.out.println("fromIndex= " + fromIndex + ", toIndex= " + toIndex);
            //							System.out.println("coordArray: ");
            //							System.out.print("X   ");
            //							for (int p=0; p<coordArray[0].length; p++)
            //								System.out.print(coordArray[0][p] + " ");
            //							System.out.print("\nY   ");
            //							for (int p=0; p<coordArray[1].length; p++)
            //								System.out.print(coordArray[1][p] + " ");
            //							System.out.println("");
            //								
            //							System.out.println("Num attributes = " + access.getNumAttributes());
            //							System.out.println("attributeArray.length = " + attributeArray.length);
            //							System.out.println("attributeArray: ");
            //							System.out.print("Z   ");
            //							for (int p=0; p<attributeArray[0].length; p++)
            //								System.out.print(attributeArray[0][p] + " ");
            //							System.out.print("\nT   ");
            //							for (int p=0; p<attributeArray[1].length; p++)
            //								System.out.print(attributeArray[1][p] + " ");
            //							System.out.println("");
            //			try
            //			{
            for (int i = 0; i < access.getDimension(); i++) {
                subCoordArray[i] = new OrdinateList(coordArray[i], 0, 1,
                        fromIndex, toIndex).toDoubleArray();
            }

            //			}
            //			catch (ArrayIndexOutOfBoundsException e)
            //			{
            //				e.printStackTrace();
            //				System.out.println("Dimension = " + access.getDimension());
            //				System.out.println("coordArray.length = " + coordArray.length);
            //				System.out.println("fromIndex= " + fromIndex + ", toIndex= " + toIndex);
            //				System.out.println("coordArray: ");
            //				System.out.print("X   ");
            //				for (int p=0; p<coordArray[0].length; p++)
            //					System.out.print(coordArray[0][p] + " ");
            //				System.out.print("\nY   ");
            //				for (int p=0; p<coordArray[1].length; p++)
            //					System.out.print(coordArray[1][p] + " ");
            //				System.out.println("");
            //			}
            for (int i = 0; i < access.getNumAttributes(); i++) {
                subAttributeArray[i] = new AttributeList(attributeArray[i], 0,
                        1, fromIndex, toIndex).toObjectArray();
            }

            System.out.println("subCoordArray.length = " + subCoordArray.length);
            System.out.println("subCoordArray: ");
            System.out.print("X   ");

            for (int p = 0; p < subCoordArray[0].length; p++)
                System.out.print(subCoordArray[0][p] + " ");

            System.out.print("\nY   ");

            for (int p = 0; p < subCoordArray[1].length; p++)
                System.out.print(subCoordArray[1][p] + " ");

            System.out.println("");

            System.out.println("subAttributeArray.length = "
                + subAttributeArray.length);
            System.out.println("subAttributeArray: ");
            System.out.print("Z   ");

            for (int p = 0; p < subAttributeArray[0].length; p++)
                System.out.print(subAttributeArray[0][p] + " ");

            System.out.print("\nT   ");

            for (int p = 0; p < subAttributeArray[1].length; p++)
                System.out.print(subAttributeArray[1][p] + " ");

            System.out.println("");

            CoordinateAccess c = (CoordinateAccess) ((CoordinateAccessFactory) factory)
                .create(subCoordArray, subAttributeArray);

            return c;
        }

        Coordinate[] array = new Coordinate[toIndex - fromIndex];
        int index = 0;
        for(int i = fromIndex; i < toIndex; i++, index++) {
            array[index] = sequence.getCoordinate(i);
        }

        return factory.create(array);
    }

    /**
     * DOCUMENT ME!
     *
     * @param factory
     * @param sequence
     *
     */
    public static CoordinateSequence reverse(
        CoordinateSequenceFactory factory, CoordinateSequence sequence) {
        if (sequence instanceof CoordinateAccess) {
            CoordinateAccess access = (CoordinateAccess) sequence;
            double[][] coordArray = access.toOrdinateArrays();
            Object[] attributeArray = access.toAttributeArrays();

            double[][] subCoordArray = new double[access.getDimension()][];
            Object[][] subAttributeArray = new Object[access.getNumAttributes()][];

            for (int i = 0; i < access.getDimension(); i++) {
                subCoordArray[i] = new OrdinateList(coordArray[i], 0, 1,
                        access.size() - 1, -1).toDoubleArray();
            }

            for (int i = 0; i < access.getNumAttributes(); i++) {
                subAttributeArray[i] = new AttributeList(attributeArray[i], 0,
                        1, access.size() - 1, -1).toObjectArray();
            }

            CoordinateAccess c = (CoordinateAccess) ((CoordinateAccessFactory) factory)
                .create(subCoordArray, subAttributeArray);

            return c;
        } else // else CoordinateSequence
         {
            CoordinateList list = new CoordinateList(sequence.toCoordinateArray());
            Collections.reverse(list);

            return factory.create(list.toCoordinateArray());
        }
    }

    public static String toString(CoordinateSequence cs, int coordinate,
        NumberFormat nf) {
        StringBuffer buf = new StringBuffer();
        append(buf, cs, coordinate, nf);

        return buf.toString();
    }

    public static void append(StringBuffer buf, CoordinateSequence cs,
        int coordinate, NumberFormat nf) {
        if (cs instanceof CoordinateAccess) {
            CoordinateAccess ca = (CoordinateAccess) cs;
            append(buf, ca, coordinate, LEN(ca), nf);
        } else {
            append(buf, cs, coordinate, LEN(cs), nf);
        }
    }

    public static void append(StringBuffer buf, CoordinateSequence cs,
        int coordinate, int LEN, NumberFormat nf) {
        Coordinate c = cs.getCoordinate(coordinate);
        buf.append(nf.format(c.x));
        buf.append(" ");
        buf.append(nf.format(c.y));

        if (LEN == 3) {
            buf.append(" ");
            buf.append(nf.format(c.z));
        }
    }

    public static void append(StringBuffer buf, CoordinateAccess ca,
        int coordinate, int LEN, NumberFormat nf) {
        buf.append(nf.format(ca.getOrdinate(coordinate, 0)));

        for (int i = 1; i < LEN; i++) {
            buf.append(" ");
            buf.append(nf.format(ca.getOrdinate(coordinate, i)));
        }
    }

    public static int LEN(CoordinateSequence cs) {
        return D(cs) + L(cs);
    }

    public static int D(CoordinateSequence cs) {
        if (cs instanceof CoordinateAccess) {
            return ((CoordinateAccess) cs).getDimension();
        }

        if (cs.size() > 0) {
            return Double.isNaN(cs.getCoordinate(0).z) ? 2 : 3;
        }

        return 3;
    }

    public static int L(CoordinateSequence cs) {
        if (cs instanceof CoordinateAccess) {
            return ((CoordinateAccess) cs).getNumAttributes();
        }

        return 0;
    }

    public static NumberFormat format(PrecisionModel pm) {
        DecimalFormatSymbols symbols = new DecimalFormatSymbols();
        symbols.setNaN("NaN");

        DecimalFormat f = new DecimalFormat();
        f.setDecimalFormatSymbols(symbols);

        if (pm == null) {
            f.setMaximumFractionDigits(0);

            return f;
        }

        f.setMinimumFractionDigits(0);
        f.setMaximumFractionDigits(pm.getMaximumSignificantDigits());

        return f;
    }

    public static String toString(CoordinateSequence cs, PrecisionModel pm) {
        StringBuffer buf = new StringBuffer();
        append(buf, cs, format(pm));

        return buf.toString();
    }

    public static void append(StringBuffer buf, CoordinateSequence cs,
        NumberFormat nf) {
        if (cs instanceof CoordinateAccess) {
            append(buf, (CoordinateAccess) cs, nf);
        } else {
            int LEN = LEN(cs); // 2 or 3

            if (cs.size() == 0) {
                return;
            }

            append(buf, cs, 0, LEN, nf);

            if (cs.size() == 1) {
                return;
            }

            for (int i = 1; i < cs.size(); i++) {
                buf.append(", ");
                append(buf, cs, i, LEN, nf);
            }
        }
    }

    public static void append(StringBuffer buf, CoordinateAccess ca,
        NumberFormat nf) {
        int LEN = LEN(ca);

        if (ca.size() == 0) {
            return;
        }

        append(buf, ca, 0, LEN, nf);

        if (ca.size() == 1) {
            return;
        }

        for (int i = 1; i < ca.size(); i++) {
            buf.append(", ");
            append(buf, ca, i, LEN, nf);
        }
    }
}
