// MapEntry.java
// $Id: MapEntry.java,v 1.8 1998/02/19 10:48:09 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html


package org.w3c.jigsaw.map ;

import java.util.*;
import java.io.*;

/** Represents an entry in a map */

abstract class MapEntry {
    // The methods (used for pickling)
    protected static final int DEFAULT = 0;
    protected static final int NOSEARCH = 5;
    protected static final int CIRCLE = 1;
    protected static final int POLYGON = 2;
    protected static final int RECTANGLE = 3;
    protected static final int POINT = 4;

    protected String URL;


    /**
     * Returns a new MapEntry parsed from a map file line.
     */
    public static MapEntry parse(String line, boolean useNCSA)
	throws MapException
    {
	StringBuffer sb = new StringBuffer(10);
	String rest = null;
	MapEntry result = null;

	for(int i=0;i<line.length();i++) {
	    char c = line.charAt(i);
	    if(c == ' ' || c == '\t') {
		rest = line.substring(i+1);
		break;
	    } 
	    sb.append(c);
	}
	String method = sb.toString().trim();

	if(method.equalsIgnoreCase("def") ||
	   method.equalsIgnoreCase("default")) {

	    result = new DefaultMapEntry();

	} else if (method.equalsIgnoreCase("nosearch")) {

	    result = new NoSearchMapEntry();
	    
	} else if(method.equalsIgnoreCase("circ") ||
		  method.equalsIgnoreCase("circle")) {

	    result = new CircleMapEntry();

	} else if(method.equalsIgnoreCase("rect") ||
		  method.equalsIgnoreCase("rectangle")) {

	    result = new RectangleMapEntry();

	} else if(method.equalsIgnoreCase("poly") ||
		  method.equalsIgnoreCase("polygon")) {

	    result = new PolygonMapEntry();

	} else if(method.equalsIgnoreCase("point")) {

	    result = new PointMapEntry();

	} else {

	    throw new MapException("unknown map entry method: "+method);

	}

	result.parseRest(rest,useNCSA);
	return result;
    }

    private void parseRest(String rest, boolean useNCSA)
    {
	if(useNCSA)
	    parseRestNCSA(rest);
	else
	    parseRestW3C(rest);
    }
    
    protected abstract void parseRestW3C(String line) throws MapException ;
    protected abstract void parseRestNCSA(String line) throws MapException ;

    public static MapEntry unpickle(DataInputStream in)
    throws IOException {

	MapEntry entry = null;

	String URL = in.readUTF();
	int method = in.readInt();
	switch(method) {
	  case DEFAULT:
	      entry = new DefaultMapEntry();
	      break;
	  case NOSEARCH:
	      entry = new NoSearchMapEntry();
	      break;
	  case CIRCLE:
	      entry = new CircleMapEntry();
	      break;
	  case POLYGON:
	      entry = new PolygonMapEntry();
	      break;
	  case RECTANGLE:
	      entry = new RectangleMapEntry();
	      break;
	  case POINT:
	      entry = new PointMapEntry();
	      break;
	}

	entry.URL = URL;
	entry.unpickleRest(in);
	return entry;
    }

    public void pickle(DataOutputStream out) 
	throws IOException
    {
	out.writeUTF(URL);
    }

    protected abstract void unpickleRest(DataInputStream in)
	throws IOException ; 

    public abstract boolean matches(Point p) ;

    public void setURL(String URL)
    {
	this.URL = URL;
    }

    public String getURL()
    {
	return URL;
    }

}


class RectangleMapEntry extends MapEntry {
    private static final String errorString = new String("rectangle parse error");

    private Point ul;
    private Point lr;

    public RectangleMapEntry()
    {
	ul = null;
	lr = null;
    }

    public RectangleMapEntry(Point ul, Point lr)
    {
	this.ul = ul;
	this.lr = lr;
    }

    public final void setUL(Point ul)
    {
	this.ul = ul;
    }

    public final void setLT(Point lr)
    {
	this.lr = lr;
    }
    
    public boolean matches(Point p)
    {
	return (
		   ul.x <=  p.x
		&& ul.y <=  p.y
		
		&&  p.x <= lr.x
		&&  p.y <= lr.y
		);
    }

    protected void unpickleRest(DataInputStream in)
	throws IOException
    {
	ul = new Point();
	lr = new Point();

	ul.unpickle(in);
	lr.unpickle(in);
    }

    public void pickle(DataOutputStream out)
	throws IOException
    {
	super.pickle(out);
	
	out.writeInt(RECTANGLE);

	ul.pickle(out);
	lr.pickle(out);
    }

    protected void parseRestNCSA(String line)
	throws MapException
    {
	StringTokenizer strtok = new StringTokenizer(line);
	try {
	    URL = strtok.nextToken();
	    ul = Point.parseNCSA(strtok.nextToken());
	    lr = Point.parseNCSA(strtok.nextToken());
	    if(strtok.hasMoreTokens())
		throw new MapException(errorString);
	} catch(NoSuchElementException ex) {
	    throw new MapException(errorString);
	} catch(NumberFormatException ex) {
	    throw new MapException(errorString);
	}
    }

    protected void parseRestW3C(String line)
	throws MapException
    {
	StringTokenizer strtok = new StringTokenizer(line);

	try {
	    ul = Point.parseW3C(strtok.nextToken());
	    lr = Point.parseW3C(strtok.nextToken());
	    URL  = strtok.nextToken();
	    if(ul == null || lr == null || strtok.hasMoreTokens())
		throw new MapException(errorString);
	} catch(NumberFormatException ex) {
	    throw new MapException(errorString);
	}
    }
}


class CircleMapEntry extends MapEntry {
    private static final String errorString = new String("circle parse error");

    int r; // The radius
    int rSq; // The radius squared

    Point center; // The center

    public CircleMapEntry()
    {
	rSq = r = 0;
	center = null;
    }

    public CircleMapEntry(int r, Point center)
    {
	this.r = this.rSq = r;
	this.rSq *= r;
	this.center = center;
    }

    public void setRadius(int r)
    {
	this.r = this.rSq = r;
	this.rSq *= r;
    }

    public void setCenter(Point center)
    {
	this.center = center;
    }

    public boolean matches(Point p)
    {
	int distSq = center.distanceSquared(p);
	return distSq <= rSq ;
    }
    
    protected void unpickleRest(DataInputStream in)
	throws IOException
    {
	center = new Point();
	center.unpickle(in);
	rSq = in.readInt();

    }
    
    public void pickle(DataOutputStream out)
	throws IOException
    {
	super.pickle(out);

	out.writeInt(CIRCLE);
	
	center.pickle(out);
	out.writeInt(rSq);
    }

    protected void parseRestNCSA(String line)
    {
	StringTokenizer strtok = new StringTokenizer(line);
	try {
	    URL = strtok.nextToken();
	    center = Point.parseNCSA(strtok.nextToken());
	    Point atEdge = Point.parseNCSA(strtok.nextToken());
	    rSq = center.distanceSquared(atEdge);
	    if(strtok.hasMoreTokens())
		throw new MapException(errorString);
	} catch(NoSuchElementException ex) {
	    throw new MapException(errorString);
	} catch(NumberFormatException ex) {
	    throw new MapException(errorString);
	}
    }

    protected void parseRestW3C(String line)
	throws MapException
    {
	StringTokenizer strtok = new StringTokenizer(line);
	try {
	    center = Point.parseW3C(strtok.nextToken());
	    rSq = Integer.parseInt(strtok.nextToken());
	    rSq *= rSq;
	    URL = strtok.nextToken();

	    if(center == null || strtok.hasMoreTokens())
		throw new MapException(errorString);
	} catch(NoSuchElementException ex) {
	    throw new MapException(errorString);
	} catch(NumberFormatException ex) {
	    throw new MapException(errorString);
	}
    }
}

class PolygonMapEntry extends MapEntry {
    private static final String errorString =
	new String("polygon parse error");

    Vector points;
    
    public PolygonMapEntry()
    {
	points = null;
    }

    private final int sign(int x)
    {
	return x>0?1:(x<0?-1:0);
    }

    // (Adapted code from the comp.graphics.algorithms FAQ) :
    // (Unreadable thanks to elementAt and casts. Templates are NEEDED.).
    public boolean matches(Point p)
    {
	boolean result = false;
	int i,j,m;
	int n = points.size();
	int sgn = 0;

	for(i = 0,j = n-1; i < n ; j = i++) {
	    m = ((Point)points.elementAt(j)).y-((Point)points.elementAt(i)).y;
	    if ((((((Point)points.elementAt(i)).y<=p.y) &&
		  (p.y<((Point)points.elementAt(j)).y)) ||
		 ((((Point)points.elementAt(j)).y<=p.y) &&
		  (p.y<((Point)points.elementAt(i)).y))) &&
		((p.y - ((Point)points.elementAt(i)).y) *
		 (((Point)points.elementAt(j)).x -
		  ((Point)points.elementAt(i)).x) -
		 (p.x - ((Point)points.elementAt(i)).x) * m )
		* sign(m) > 0)
		result = !result;
	}
	return result;
    }

    protected void unpickleRest(DataInputStream in)
	throws IOException
    {
	int n = in.readInt();

	points = new Vector(n);

	Point p;
	for(int i=0;i<n;i++) {
	    p = new Point();
	    p.unpickle(in);
	    points.addElement(p);
	}
    }

    public void pickle(DataOutputStream out)
	throws IOException
    {
	super.pickle(out);

	out.writeInt(POLYGON);

	int n = points.size();
	out.writeInt(n);
	for(int i=0;i<n;i++) 
	    ( (Point) points.elementAt(i) ).pickle(out);
    }

    protected void parseRestNCSA(String line)
    {
	StringTokenizer strtok = new StringTokenizer(line);
	try {
	    URL = strtok.nextToken();
	    points = new Vector(6);
	    while(strtok.hasMoreTokens())
		points.addElement(Point.parseNCSA(strtok.nextToken()));
	} catch(NoSuchElementException ex) {
	    throw new MapException(errorString);
	} catch(NumberFormatException ex) {
	    throw new MapException(errorString);
	}
    }

    protected void parseRestW3C(String line)
	throws MapException
    {
	StringTokenizer strtok = new StringTokenizer(line);
	try {
	    points = new Vector(6);
	    Point newPoint;
	    String token = null;
	    while(strtok.hasMoreTokens()) {
		token = strtok.nextToken();
		if( (newPoint = Point.parseW3C(token) ) != null)
		    points.addElement(newPoint);
		else break;
	    }
	    URL = token;
	    if(URL == null || strtok.hasMoreTokens())
		throw new MapException(errorString);
	} catch(NoSuchElementException ex) {
	    throw new MapException(errorString);
	} catch(NumberFormatException ex) {
	    throw new MapException(errorString);
	}
    }
	
		

    public String toString()
    {
	StringBuffer strbuf = new StringBuffer(30);
	strbuf.append('<');
	if(points == null)
	    strbuf.append("no points");
	else for(int i=0;i<points.size();i++) 
	    strbuf.append(((Point)points.elementAt(i)).toString());
	strbuf.append('>');
	return strbuf.toString();
    }
}





