/*
 * ComposedGeom.java
 * 
 * PostGIS extension for PostgreSQL JDBC driver - geometry model
 * 
 * (C) 2004 Paul Ramsey, pramsey@refractions.net
 * 
 * (C) 2005 Markus Schaber, markus.schaber@logix-tt.com
 * 
 * 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.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA or visit the web at
 * http://www.gnu.org.
 * 
 * $Id: ComposedGeom.java,v 1.7 2005/07/28 12:23:16 mschaber Exp $
 */

package org.postgis;

import org.postgresql.util.PGtokenizer;

import java.sql.SQLException;
import java.util.Iterator;

/**
 * ComposedGeom - Abstract base class for all Geometries that are composed out
 * of other Geometries.
 * 
 * In fact, this currently are all Geometry subclasses except Point.
 * 
 * @author markus.schaber@logix-tt.com
 * 
 * 
 */
public abstract class ComposedGeom extends Geometry {
    /* JDK 1.5 Serialization */
    private static final long serialVersionUID = 0x100;

    public static final Geometry[] EMPTY = new Geometry[0];

    /**
     * The Array containing the geometries
     * 
     * This is only to be exposed by concrete subclasses, to retain type safety.
     */
    protected Geometry[] subgeoms = EMPTY;

    /**
     * @param type
     */
    public ComposedGeom(int type) {
        super(type);
    }

    public Geometry getSubGeometry(int index) {
        return subgeoms[index];
    }

    public int numGeoms() {
        return subgeoms.length;
    }

    protected ComposedGeom(int type, Geometry[] geoms) {
        this(type);
        this.subgeoms = geoms;
        if (geoms.length > 0) {
            dimension = geoms[0].dimension;
            haveMeasure = geoms[0].haveMeasure;
        } else {
            dimension = 0;
        }
    }

    protected ComposedGeom(int type, String value, boolean haveM) throws SQLException {
        super(type);
        value = initSRID(value);

        String typestring = getTypeString();
        if (value.indexOf(typestring) == 0) {
            int pfxlen = typestring.length();
            if (value.charAt(pfxlen) == 'M') {
                pfxlen += 1;
                haveM = true;
            }
            value = value.substring(pfxlen).trim();
        } else if (value.charAt(0) != '(') {
            // we are neigher inner nor outer rep.
            throw new SQLException("Error parsing a " + typestring + " out of " + value);
        }
        if (value.equals("(EMPTY)")) {
            // Special case for PostGIS 0.X style empty geometry collections
            // (which are not OpenGIS compliant)
            return;
        }
        PGtokenizer t = new PGtokenizer(PGtokenizer.removePara(value), ',');
        int subgeomcount = t.getSize();
        subgeoms = createSubGeomArray(subgeomcount);
        for (int p = 0; p < subgeomcount; p++) {
            subgeoms[p] = createSubGeomInstance(t.getToken(p), haveM);
        }
        dimension = subgeoms[0].dimension;
        // fetch haveMeasure from subpoint because haveM does only work with
        // 2d+M, not with 3d+M geometries
        haveMeasure = subgeoms[0].haveMeasure;
    }

    /**
     * Return the appropriate instance of the subgeometry - this encapsulates
     * subclass specific constructor calls
     */
    protected abstract Geometry createSubGeomInstance(String token, boolean haveM)
            throws SQLException;

    /**
     * Return the appropriate instance of the subgeometry array - this
     * encapsulates subclass specific array instantiation
     */
    protected abstract Geometry[] createSubGeomArray(int size);

    protected boolean equalsintern(Geometry other) {
        // Can be assumed to be the same subclass of Geometry, so it must be a
        // ComposedGeom, too.
        ComposedGeom cother = (ComposedGeom) other;

        if (cother.subgeoms == null && subgeoms == null) {
            return true;
        } else if (cother.subgeoms == null || subgeoms == null) {
            return false;
        } else if (cother.subgeoms.length != subgeoms.length) {
            return false;
        } else if (subgeoms.length == 0) {
            return true;
        } else {
            for (int i = 0; i < subgeoms.length; i++) {
                if (!cother.subgeoms[i].equalsintern(this.subgeoms[i])) {
                    return false;
                }
            }
        }
        return true;
    }

    public int numPoints() {
        if ((subgeoms == null) || (subgeoms.length == 0)) {
            return 0;
        } else {
            int result = 0;
            for (int i = 0; i < subgeoms.length; i++) {
                result += subgeoms[i].numPoints();
            }
            return result;
        }
    }

    public Point getPoint(int n) {
        if (n < 0) {
            throw new ArrayIndexOutOfBoundsException("Negative index not allowed");
        } else if ((subgeoms == null) || (subgeoms.length == 0)) {
            throw new ArrayIndexOutOfBoundsException("Empty Geometry has no Points!");
        } else {
            for (int i = 0; i < subgeoms.length; i++) {

                Geometry current = subgeoms[i];
                int np = current.numPoints();
                if (n < np) {
                    return current.getPoint(n);
                } else {
                    n -= np;
                }
            }
            throw new ArrayIndexOutOfBoundsException("Index too large!");
        }
    }

    /**
     * Optimized version
     */
    public Point getLastPoint() {
        if ((subgeoms == null) || (subgeoms.length == 0)) {
            throw new ArrayIndexOutOfBoundsException("Empty Geometry has no Points!");
        } else {
            return subgeoms[subgeoms.length - 1].getLastPoint();
        }
    }

    /**
     * Optimized version
     */
    public Point getFirstPoint() {
        if ((subgeoms == null) || (subgeoms.length == 0)) {
            throw new ArrayIndexOutOfBoundsException("Empty Geometry has no Points!");
        } else {
            return subgeoms[0].getFirstPoint();
        }
    }

    public Iterator iterator() {
        return java.util.Arrays.asList(subgeoms).iterator();
    }

    public boolean isEmpty() {
        return (subgeoms == null) || (subgeoms.length == 0);
    }

    protected void mediumWKT(StringBuffer sb) {
        if ((subgeoms == null) || (subgeoms.length == 0)) {
            sb.append(" EMPTY");
        } else {
            sb.append('(');
            innerWKT(sb);
            sb.append(')');
        }
    }

    protected void innerWKT(StringBuffer sb) {
        subgeoms[0].mediumWKT(sb);
        for (int i = 1; i < subgeoms.length; i++) {
            sb.append(',');
            subgeoms[i].mediumWKT(sb);
        }
    }

    // Hashing - still buggy!
    boolean nohash = true;
    int hashcode = 0;

    public int hashCode() {
        if (nohash) {
            hashcode = super.hashCode() ^ subgeoms.hashCode();
            nohash = false;
        }
        return hashcode;
    }

    public boolean checkConsistency() {
        if (super.checkConsistency()) {
            if (isEmpty()) {
                return true;
            }
            // cache to avoid getMember opcode
            int _dimension = this.dimension;
            boolean _haveMeasure = this.haveMeasure;
            int _srid = this.srid;
            for (int i = 0; i < subgeoms.length; i++) {
                Geometry sub = subgeoms[i];
                if (!(sub.checkConsistency() && sub.dimension == _dimension
                        && sub.haveMeasure == _haveMeasure && sub.srid == _srid)) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }
}
