/**
 * @author
 *  Behrooz Badii 	bb2122@columbia.edu
 *  Hanhua Feng         hanhua@cs.columbia.edu
 *  Edan Harel 	        edan@columbia.edu
 */
package CookieCutter.g6;

import java.util.Vector;
import java.io.Serializable;

/**
 * A class that describe regions by polygons.
 */
class Region implements Serializable {
    public static final double EPS = Plane.EPS * 10;

    Polygon _pg;          // polygons that constitute the region
    Region[] _holes;      // regions removed from correspoding Polygons
    int _nh;

    /** construct a region from a single polygon 
     */
    public Region( Polygon pg ) {
        _pg = pg;
        _holes = null;
        _nh = 0;

        throw new UnsupportedOperationException();
    }

    /** construct a region from a polygon and hole regions
     */
    public Region( Polygon pg, Region[] holes ) {
        _pg = pg;
        _holes = holes;
        _nh = ( null == holes ? 0 : _holes.length );

        throw new UnsupportedOperationException();
    }

    /** concatenate an array of region arrays to a single region array
     */
    public static Region[] concat( Region[][] regs ) {
        int n = 0;
        // calculate the total number of regions
        for ( int i=0; i<regs.length; i++ )
            if ( regs[i] != null )
                n += regs[i].length;
        if ( 0 == n )
            return null;

        Region[] ret = new Region[n];
        // copy and concatenate the two dimensional array to one-d
        for ( int i=0, j=0; i<regs.length; i++ )
            if ( regs[i] != null && regs[i].length > 0 )
            {
                int len = regs[i].length;
                System.arraycopy( regs[i], 0, ret, j, len );
                j += len;
            }

        return ret;
    }

    /** append a region to an array of regions
     */
    public static Region[] append( Region[] regs, Region reg0 ) {
        int n = ( null==regs ? 0 : regs.length );
        Region[] ret = new Region[n+1];
        // copy the original array
        if ( n > 0 )
            System.arraycopy( regs, 0, ret, 0, n );
        // append one more element
        ret[n] = reg0;
        return ret;
    }

    /** prepend a region in front of an array of regions
     */
    public static Region[] prepend( Region reg0, Region[] regs ) {
        int n = ( null==regs ? 0 : regs.length );
        Region[] ret = new Region[n+1];
        // copy the original array
        if ( n > 0 )
            System.arraycopy( regs, 0, ret, 1, n );
        // append one more element
        ret[0] = reg0;
        return ret;
    }

    /** append a region to an array of regions
     */
    public static Region[] append( Region[] regs, Region[] regs0 ) {
        int n = ( null==regs ? 0 : regs.length );
        int n0 = ( null==regs0 ? 0 : regs0.length );
        Region[] ret = new Region[n+n0];
        // copy the original array
        if ( n > 0 )
            System.arraycopy( regs, 0, ret, 0, n );
        if ( n0 > 0 )
            System.arraycopy( regs0, 0, ret, n, n0 );
        return ret;
    }

    public static final int IS = 0;    // two polygon intersections
    public static final int GT = 1;    // the first covers the second
    public static final int LT = 2;    // the second coverts the first
    public static final int DJ = 3;    // disjoint

    /** check the relation between two polygons
     */
    public static final int compare( Polygon pg0, Polygon pg1 ) {
        int in0 = 0;
        int out0 = 0;
        int in1 = 0;
        int out1 = 0;

    outer_loop0:  // check #s of vertices of pg0 in or out of pg1
        for ( int i=0; i<pg0.n(); i++ )
        {
            Point p = pg0.vertex(i);
            for ( int j=0; j<pg1.n(); j++ )
                if ( p == pg1.vertex(j) )
                    continue outer_loop0;

            if ( pg1.contains( p ) )
                in0++;
            else
                out0++;
        }

        // if all vertices of pg0 are in pg1, then pg0 <= pg1
        if ( 0 == out0 )
            return LT;

    outer_loop1:  // check #s of vertices of pg1 in or out of pg0
        for ( int i=0; i<pg1.n(); i++ )
        {
            Point p = pg1.vertex(i);
            for ( int j=0; j<pg0.n(); j++ )
                if ( p == pg0.vertex(j) )
                    continue outer_loop1;

            if ( pg0.contains( p ) )
                in1++;
            else
                out1++;
        }

        // if all vertices of pg1 are in pg0, then pg0 >= pg1
        if ( 0 == out1 )
            return GT;
        // if no vertex of one polygon is in the other, pg0 DISJOINT pg1
        if ( 0 == in0 && 0 == in1 )
            return DJ;
        // otherwise, they intersect.
        return IS;
    }

    /** compute the difference between this region and a polygon ---
     * (this - pg)
     */
    public Region[] difference( Polygon pg ) {
        int cmp = compare( _pg, pg );
        Region[] ret = null;
        switch ( cmp )
        {
        case IS:
            // if _pg X pg, ret = _pg - pg - holes = _pg - (pg+holes);
            if ( 0 == _nh )
                ret = difference( _pg, pg );
            else
                ret = difference( _pg, union( _holes, pg ) );
            break;
        case GT:
            // if _pg >= pg, ret = _pg - (holes + pg);
            _holes = union( _holes, pg );
            break;
        case LT:
            // if _pg <= pg, ret = 0;
            ret = null;
            break;
        case DJ:
            // if _pg // pg, ret = { _pg, pg };
            ret = new Region[] { this, new Region( pg ) };
            break;
        default:
            throw new RuntimeException( "Program internal error" );
        }
        return ret;
    }

    /** compute the difference of a polygon to this region -----
     * ( pg - this )
     */
    public Region[] complement( Polygon pg ) {
        int cmp = compare( _pg, pg );
        Region[] ret = null;
        switch ( cmp )
        {
        case IS:
        case LT:
            // if _pg X pg || _pg <= pg, ret = pg - _pg + (holes-pg);
            ret = difference( pg, _pg );
            if ( 0 == _nh )
                break;
            Region[][] regs = new Region[_nh][];
            for ( int i=0; i<_nh; i++ )
                regs[i] = _holes[i].complement( pg );
            ret = append( concat( regs ), ret );
            break;
        case GT:
            // if _pg >= pg, ret = pg * holes; pairwise disjoint
            ret = intersection( _holes, pg );
            break;
        case DJ:
            // if _pg // pg, ret = pg;
            ret = new Region[] { new Region( pg ) };
        default:
            throw new RuntimeException( "Program internal error" );
        }
        return ret;
    }

    /** compute the union of a polygon to this region -----
     * this + pg
     */
    public Region[] union( Polygon pg ) {
        int cmp = compare( _pg, pg );
        Region[] ret = null;
        switch ( cmp )
        {
        case IS:
            // if _pg X pg, ret = _pg + pg - holes.  Assumption: the
            // first region of the union of two polygons (_pg+pg) must
            // embrace the previous holes the first polygon (_pg).
            ret = union( _pg, pg );
            ret[0]._holes = append( ret[0]._holes,
                                    difference( _holes, pg ) );
            break;
        case GT:
            // if _pg >= pg, ret = _pg - (holes - pg );
            // (holes-pg) are pairwise disjoint and be embraced in pg
            if ( _nh == 0 )
            {
                ret = new Region[] { this };
                break;
            }

            Region[][] regs = new Region[_nh][];
            for ( int i=0; i<_nh; i++ )
                regs[i] = _holes[i].difference( pg );
            ret = concat( regs );
            break;
        case LT:
            // if _pg <= pg, ret = pg;
            ret = new Region[] { new Region( pg ) };
            break;
        case DJ:
            // if _pg // pg, ret = { _pg, pg };
            ret = new Region[] { this, new Region( pg ) };
            break;
        }
        return ret;
    }

    /** compute the union of a polygon to this region -----
     * this * pg
     */
    public Region[] intersection( Polygon pg ) {
        int cmp = compare( _pg, pg );
        Region[] ret = null;
        switch ( cmp )
        {
        case IS:
            // if _pg X pg, ret = _pg * pg - holes;
            if ( 0 == _nh )
                ret = intersection( _pg, pg );
            else
                ret = intersection( difference( _holes, pg ), _pg );
            break;
        case GT:
            // if _pg >= pg, ret = pg - holes;
            ret = difference( pg, _holes );
            break;
        case LT:
            // if _pg <= pg, nothing changed
            ret = new Region[] { this };
            break;
        case DJ:
            // if _pg // pg, ret = empty set
            ret = null;
            break;
        }
        return ret;
    }

    /** the difference of a region set and a polygon
     */
    public static Region[] difference( Region[] regs, Polygon pg ) {
        // empty set?
        if ( null == regs || regs.length == 0 )
            return null;

        // just remove pg from each region independently
        Region[][] ret = new Region[regs.length][];
        for ( int i=0; i<ret.length; i++ )
            ret[i] = regs[i].difference( pg );
        return concat( ret );
    }

    /** substruct a region set from a polygon 
     */
    public static Region[] difference( Polygon pg, Region[] regs ) {
        // the most complicated case.
        throw new UnsupportedOperationException();
/*
        // empty set: return pg
        if ( null == regs || regs.length == 0 )
            return new Region[] { new Region( pg ) };

        // first remove all first region from pg
        Region[] ret = regs[0].complement( pg );

        // Remove rest regions by first removing the root polygon,
        // then adding back the intersections of pg and each hole
        for ( int i=1; i<regs.length; i++ )
        {
            ret = difference( ret, regs[i]._pg );
            for ( int j=0; j<regs[i]._nh; j++ )
                ret = union( ret, intersection( ret, regs[i]._holes[j] ) );
        }
*/
    }

    /** compute the union a region set 
     */
    public static Region[] union( Region[] regs, Polygon pg ) {
        // almost as complicated as the previous case
        throw new UnsupportedOperationException();
/*
        // empty set? return pg
        if ( null == regs || regs.length == 0 )
            return new Region[] { new Region( pg ) };

        // first union the polygon with the first region
        Region[] ret = regs[0].union( pg );
        // then union the root polygons and substract holes
        for ( int i=1; i<regs.length; i++ )
        {
            ret = union( ret, regs[i]._pg );
            for ( int j=0; j<regs[i]._nh; j++ )
                ret = difference( ret, regs[i]._holes[j] );
        }

        return ret;
*/
    }

    /** compute the intersection of a region set and a polygon
     */
    public Region[] intersection( Region[] regs, Polygon pg ) {
        // empty set?
        if ( null == regs || regs.length == 0 )
            return null;

        // much easier: independent and disjoint regions
        Region[][] ret = new Region[regs.length][];
        for ( int i=0; i<ret.length; i++ )
            ret[i] = regs[i].intersection( pg );
        return concat( ret );
    }

    /* compute the difference of two polygons
     */
    public static Region[] difference( Polygon pg0, Polygon pg1 ) {
        throw new UnsupportedOperationException();
/*   
        int cmp = compare( pg0, pg1 );
        Region[] ret;
        switch ( cmp )
        {
        case IS:
            // if pg0 X pg1, call _cutsets
            Polygon[] pgs = pg1.cutsets( pg0 )[1];
            if ( pgs == null || pgs.length < 0 )
                return null;
            ret = new Region[pgs.length];
            for ( int i=0; i<pgs.length; i++ )
                ret[i] = new Region( pgs[i] );
            break;
        case GT:
            // if pg0 >= pg1, pg1 is a hole of pg0
            ret = new Region[] {
                new Region( pg0, new Region[] { new Region( pg1 ) } ) };
            break;
        case LT:
        case DJ:
            // if pg0 <= pg1 or pg0 // pg1 , return pg0
            ret = new Region[] { new Region( pg0 ) };
            break;
        default:
            throw new RuntimeException( "Program internal error" );
        }

        return ret;
*/
    }

    /** find the union of two (not necessarily disjoint) polygons 
     */
    public static Region[] union( Polygon pg0, Polygon pg1 ) {
        // the first region of the returned set must contain pg0!!!
        return prepend( new Region( pg0 ), difference( pg1, pg0 ) );
    }

    /** find the intersection of two (not necessarily disjoint) polygons 
     */
    public static Region[] intersection( Polygon pg0, Polygon pg1 ) {
        throw new UnsupportedOperationException();
/*
        int cmp = compare( pg0, pg1 );
        Region[] ret = null;
        switch ( cmp )
        {
        case IS:
            // if pg0 X pg1, call _cutsets
            Polygon[] pgs = pg1.cutsets( pg0 )[0];
            if ( pgs == null || pgs.length < 0 )
                return null;
            ret = new Region[pgs.length];
            for ( int i=0; i<pgs.length; i++ )
                ret[i] = new Region( pgs[i] );
            break;
        case GT:
            // if pg0 >= pg1, return pg1
            ret = new Region[] { new Region( pg1 ) };
            break;
        case LT:
            // if pg0 <= pg1, return pg0
            ret = new Region[] { new Region( pg0 ) };
            break;
        case DJ:
            // if pg0 // pg1, return null;
            ret = null;
            break;
        }
        return ret;
*/
    }


    /** the bounding box of the region
     */
    public Rectangle bound() {
        return _pg.bound();
    }

    /** the bounding box of a region set
     */
    public static Rectangle bound( Region[] regs ) {
        if ( null == regs || regs.length == 0 )
            return null;
        Rectangle ret = regs[0].bound();
        for ( int i=1; i<regs.length; i++ )
            ret = ret.union( regs[i].bound() );
        return ret;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();

        sb.append( _pg.toString() );
        sb.append( "-" );
        sb.append( toString( _holes ) );
        return sb.toString();
    }

    public static String toString( Region[] regs ) {
        StringBuffer sb = new StringBuffer();

        sb.append( "{" );
        for ( int i=0; ; i++ )
        {
            if ( null != regs[i] )
                sb.append( regs[i].toString() );
            if ( i >= regs.length-1 )
                break;
            sb.append( "," );
        }
        sb.append( "}" );
        
        return sb.toString();
    }
}
