/**
 *
 * Simple Vector Graphics viewer
 *
 * (C) 2001 Beartronics 
 * Author: Henry Minsky (hqm@alum.mit.edu)
 *
 * Licensed under terms "Artistic License"
 * http://www.opensource.org/licenses/artistic-license.html
 *
 * <pre>
 * Commands:  
 * 
 * xx = 8 bit byte
 * xxxx = 16 bit int (little endian)
 *
 * Set Color:
 * S      R     G       B
 * 83     INT8    INT8      INT8
 * 
 * Polyline: 16 bit integers, little endian, followed by 8 bit deltas,
 *  terminated by an dx and dy value of 0
 * deltax, deltay are 8 bit signed bytes (byte). A polygon may have
 * up to 1023 points in it.
 * p    x1      y1     dx2      dy2      ...     0 0
 * 80   INT16    INT16   deltax   deltay
 *
 * P x1 y1 x2 y2 ... 65535  // all 16 bit ints
 * 
 * Polygon fill:
 * f    x1      y1      dx2      dy2      ...     0 0
 * 80   INT16   INT16    INT8       INT8
 *
 * F    x1      y1      x2      y2      ...     0 0
 *      INT16    INT16    INT16    INT16
 * 
 * Text: null terminated string
 * T     x1    y1       c1     c2      c3  ...     \000
 *       16    16       INT8     INT8      INT8
 *
 * Draw bitmap (gif) image
 * I     x1     y1     URL         \000
 *       INT16  INT16  charstring
 * End map:
 * E
 *
 * URL is a null terminated string such as "resource:///foo.gif" or "bar.gif"
 * (A relative URL is implicitly HTTP to the app host server)
 *  
 * </pre>
 */

import com.nttdocomo.ui.*;
import com.nttdocomo.io.*;
import com.nttdocomo.net.*;

import javax.microedition.io.*;
import java.io.*;
import java.util.*;

public class VGView extends IApplication {
    public void start() {
	VGCanvas viewer = new VGCanvas();
	Display.setCurrent(viewer);
	viewer.go();
    }
}

class VGCanvas extends Canvas {

    Vector shapes = new Vector();
    static String mapName = "map.ivg";

    // Up to 64 images can be downloaded
    Object tiles[] = new Object[64];
    int tile = 0;

    int        xpos = 0;
    int        ypos = 0;

    int        jumpX = 16;
    int        jumpY = 16;

    /**
     * Scrolling view offset
     */
    int offsetX   = 0;
    int offsetY   = 0;

    int xpts[];
    int ypts[];

    public static final int MAXPTS = 1024;

    int screenWidth;
    int screenHeight;



    /**
     *
     */
    public VGCanvas()
    {
	xpts = new int[MAXPTS];
	ypts = new int[MAXPTS];
        setSoftLabel(Frame.SOFT_KEY_1, "IN");
        setSoftLabel(Frame.SOFT_KEY_2, "OUT");
	screenWidth = (this.getWidth() / 2);
	screenHeight = (this.getHeight() / 2);

    }

    static final int LOADING = 1;
    static final int RUNNING = 2;
    static final int ERROR   = 3;

    int state;

    public void go() {
	state = LOADING;
	repaint();
	getMap();
	parseMap();
	state = RUNNING;
	repaint();
    }
    
    void getMap () {
	getMapData(mapName);
	/* // test data
	  mapbuffer = new byte[] {
	    (byte)'S', 0,0,0,

	    (byte) 'p', 80, 0, 80,0, 20, 0, 0, -20,  -20, 0, 0, 20, 0, 0,

	    (byte) 'P', 30, 0, 90,0, 30, 0, 120, 0, 60, 0, 120, 0, 60, 0, 90, 0, (byte) 0xff, (byte) 0xff,

	    (byte)'S', (byte) 0xff,0,0, // red

	    (byte) 'P', 48, 0, 50,0, 68, 0, 50, 0, 68, 0, 70, 0, 48,0,50,0, (byte) 0xff, (byte) 0xff,

	    (byte)'S', 0,0,(byte) 0xff,
	    (byte) 'f', 80, 0, 50,0, 20, 0, -10,  -16,  -10, 16, 0, 0,

	    (byte) 'S', (byte) 0xff, 0,0,

	    (byte) 'T', 0,0, 20,0, (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)'!', 0,
	    //(byte) 'I', 40,0, 90,0, (byte)'r', (byte)'e', (byte)'s', (byte)'o', (byte)'u', (byte)'r', (byte)'c', (byte)'e', (byte)':', (byte)'/', (byte)'/', (byte)'/', (byte)'p', (byte)'3', (byte)'2', (byte)'.', (byte)'g', (byte)'i', (byte)'f', 0,
	    (byte) 'E'
	};

	*/
    }

    //cx = 60
    //cy = 65



    void splashScreen(Graphics g) {
	g.clearRect(0, 0, this.getWidth(), this.getHeight());
	int color = Graphics.getColorOfRGB(0,0,0);
	g.setColor(color);
	g.drawString("LOADING MAP...", 0, 20);
	g.setColor( Graphics.getColorOfRGB(0xff,0,0));
	g.drawString(" This will take", 0, 34);
	g.drawString(" a minute or so.", 0, 48);
    }

    /*    void debug (String s) {
	System.out.println(s);
    }
    */

    void parseMap () {
	// Interpret the map data
	int n = 0;
	int hi;
	int lo;
	int npts;
    evaluator:
	while (n < MAX_MAPSIZE) {
	    int cmd = mapbuffer[n++];
	    switch (cmd) {
	    case 'E': // end of map data
		//debug("End");
		break evaluator;
	    case 'P':  // polyline x1 y1 x2 y2 ... 65535
	    case 'F':  // polyfill "  "   "   "
		npts = 0;
		while (true) {
		    // all points are 16 bit ints
		    lo = (mapbuffer[n++] & 0xff);
		    hi = (mapbuffer[n++] & 0xff);
		    xpos = (hi<<8) | (lo & 0xff);

		    if (xpos == 65535) {
			break;
		    }

		    lo = mapbuffer[n++];
		    hi = mapbuffer[n++];
		    ypos = (hi<<8) | (lo & 0xff);
		    //		    debug ("ABSP: xpos="+xpos+","+ypos);
		    xpts[npts] = xpos;
		    ypts[npts] = ypos;
		    npts++;
		}
		GPolygon pcmd1 = new GPolygon(xpts, ypts, npts, (cmd == 'F'));
		shapes.addElement(pcmd1);
		break;
		// More compact shapes using deltas
	    case 'p':  // polyline int int dx dy dx dy ... 0 0
	    case 'f':  // polyfill "  "   "   "
		// first two points are 16 bit ints
		lo = mapbuffer[n++];
		hi = mapbuffer[n++];
		xpos = (hi<<8) | (lo & 0xff);

		lo = mapbuffer[n++];
		hi = mapbuffer[n++];
		ypos = (hi<<8) | (lo & 0xff);
		    
		// Read points until dx=dy=0 is read. 

		xpts[0] = xpos;
		ypts[0] = ypos;

		//debug("Poly: "+xpos+","+ypos);

		npts = 1;
		while (npts < xpts.length) {
		    byte dx = mapbuffer[n++];
		    byte dy = mapbuffer[n++];
		    if ((dx == 0) && (dy == 0)) {
			break;
		    }
		    
		    xpos += dx;
		    ypos += dy;
		    xpts[npts] = xpos;
		    ypts[npts] = ypos;

		    //debug("   : "+(xpos+dx)+","+(ypos+dy));
		    npts++;
		}
		GPolygon pcmd = new GPolygon(xpts, ypts, npts, (cmd == 'f'));
		shapes.addElement(pcmd);
		break;
	    case 'T': // text x16
	    case 'I': // text x16
		// first two points are 16 bit ints
		lo = mapbuffer[n++];
		hi = mapbuffer[n++];
		xpos = (hi<<8)+lo;

		lo = mapbuffer[n++];
		hi = mapbuffer[n++];
		ypos = (hi<<8)+lo;
		    
		// how do we convert these bytes
		// into a SJIS string?
		// read into a byte array output stream,
		// and convert to string.
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int strlen = 0;
		int soffset = n;
		while (true) {
		    int ch = mapbuffer[n++];
		    if (ch == 0) {
			break;
		    }
		    strlen++;
		}
		baos.write(mapbuffer, soffset, strlen);
		String msg;
		try {
		    msg = new String(baos.toByteArray(), "SJIS");
		} catch (IOException e) {
		    msg = new String(baos.toByteArray());
		}
		if (cmd == 'T') {
		    //debug("Text: "+xpos+","+ypos+" msg="+msg);
		    GText tex = new GText(xpos, ypos, msg);
		    shapes.addElement(tex);
		} else {
		    // its an image
		    //debug("Image: "+xpos+","+ypos+" url="+msg);
		    Image im = getImage(msg);
		    //debug("    : "+"im="+im);
		    GImage gim = new GImage(xpos, ypos, im);
		    shapes.addElement(gim);
		}
		break;
	    case 'S':  // set color
		int red   = mapbuffer[n++] & 0xff;
		int green = mapbuffer[n++] & 0xff;
		int blue  = mapbuffer[n++] & 0xff;

		//debug("Setcolor: "+red+","+green+","+blue);
		GColorCmd gcmd = new GColorCmd(red, green, blue);
		shapes.addElement(gcmd);
		break;
	    }
	}
    }



    /**
     * Redisplay the current page. 
     *
     * Interpret the map data in mapbuffer[] on the fly.
     */
    public void paint(Graphics g)
    {
        g.lock();
	if (state == LOADING) {
	    splashScreen(g);
	} else if (state == ERROR) {
	    int color = Graphics.getColorOfRGB(0,0,0);
	    g.setColor(color);
	    g.drawString(errString, 0, 20);
	} else {
	    g.clearRect(0, 0, this.getWidth(), this.getHeight());
	    for (int i = 0; i < shapes.size(); i++) {
		GCmd gcmd = (GCmd) shapes.elementAt(i);
		gcmd.draw(g, offsetX, offsetY, zoom);
	    }
	}
	g.unlock(true);
    }

    char textbuf[] = new char[256];

    static final String BROKEN_IMAGE = "resource:///broken.gif";


    /** Resource name example: "resource:///test.gif"
     */
    Image getImage (String resourceName) {
     	if (!(resourceName.startsWith("resource"))) {
	    resourceName = 
		IApplication.getCurrentApp().getSourceURL() + resourceName;
	}

 	MediaImage mi = MediaManager.getImage(resourceName);

	try {
	    mi.use();
	} catch (ConnectionException e) {
	    return(getImage(BROKEN_IMAGE));
	}
	return mi.getImage();
    }

    void panLeft () {
	offsetX -= jumpX;
    }

    void panRight () {
	offsetX += jumpX;
    }

    void panUp () {
	offsetY -= jumpY;
    }
    void panDown () {
	offsetY += jumpY;
    }

    void panHome () {
	zoom = 0;
	offsetX = 0;
	offsetY = 0;
    }

    int zoom = 0;

    void zoomIn () {
	zoom += 1;
	offsetX = (offsetX * 2) + screenWidth;
	offsetY = (offsetY * 2) + screenHeight;
    }

    void zoomOut () {
	zoom -= 1;
	offsetX = (offsetX / 2) - (screenWidth/2);
	offsetY = (offsetY / 2) - (screenHeight/2);
    }



    /**
     * Handle a keypad event
     */
    public void processEvent(int nType, int nParm) {
        if(nType == Display.KEY_PRESSED_EVENT) {
            switch(nParm) {
	    case Display.KEY_SELECT:
		// select hyperlink
		panHome();
		break;
	    case Display.KEY_SOFT1:
		zoomIn();
		break;
	    case Display.KEY_LEFT:
	    case Display.KEY_1:
		// select hyperlink
		panLeft();
		break;
	    case Display.KEY_SOFT2:
		zoomOut();
		break;
	    case Display.KEY_3:
	    case Display.KEY_RIGHT:
		// scroll right
		panRight();
		break;
	    case Display.KEY_UP:
	    case Display.KEY_2:
		panUp();
		break;

	    case Display.KEY_DOWN:
	    case Display.KEY_8:
		panDown();
		break;

	    case Display.KEY_POUND:
		IApplication.getCurrentApp().terminate();
		break;
            }
        }
        repaint();
    }

    /** HTTP */

    /**
     * Get an HTML document using HTTP protocol.
     *
     * takes relative URL
     *
     * Fills mapbuffer with raw byte data.
     */
    public void getMapData (String url) {
	String absurl = IApplication.getCurrentApp().getSourceURL() + url;
	try {
	    HttpConnection conn = (HttpConnection)Connector.open(absurl, Connector.READ, true);
	    conn.setRequestMethod(HttpConnection.GET);
	    conn.setRequestProperty("Content-Type", "text/plain");
	    conn.connect();
	    InputStream in = conn.openInputStream();
	    if (in == null)
		throw new IOException();

	    int pos = 0;
	    for (;;) {
		int len = in.read(netbuffer);
		if (len < 0)
		    break;
		// get the bytes... 
		System.arraycopy(netbuffer, 0, mapbuffer, pos, len);
		pos += len;
	    }
	    in.close();
	    conn.close();
	} catch (Exception e) {
	    errString = e.toString();
	    state = ERROR;
	}
    }
    
    String errString = "no error";

    static final int MAX_MAPSIZE = 0x8000;
    static byte[] mapbuffer = new byte[MAX_MAPSIZE]; // a 32 kbyte array buffer
    static byte[] netbuffer = new byte[0x1000];
}

/****************************************************************
 * Shape Objects
 ****************************************************************/

abstract class GCmd {
    static int xpbuf[] = new int[VGCanvas.MAXPTS];
    static int ypbuf[] = new int[VGCanvas.MAXPTS];

    abstract void draw (Graphics g, int dx, int dy, int zoom);
}

class GPolygon extends GCmd {
    int xpts[];
    int ypts[];
    int npts;
    boolean fill_p;

    GPolygon (int xpts[], int ypts[], int npts, boolean fill_p) {
	this.fill_p = fill_p;
	this.npts = npts;
	this.xpts = new int[npts];
	this.ypts = new int[npts];
	System.arraycopy(xpts, 0, this.xpts, 0, npts);
	System.arraycopy(ypts, 0, this.ypts, 0, npts);
    }

    /** Points in x and y have offset already added in */
    public void draw (Graphics g, int dx, int dy, int zoom) {
	// copy and scale points to tmp buf
	if (zoom >= 0) {
	    for (int i = 0; i < xpts.length; i++) {
		xpbuf[i] = (xpts[i] << zoom) - dx;
		ypbuf[i] = (ypts[i] << zoom) - dy;
	    }
	} else {
	    for (int i = 0; i < xpts.length; i++) {
		xpbuf[i] = (xpts[i] >> -zoom) - dx;
		ypbuf[i] = (ypts[i] >> -zoom) - dy;
	    }
	}

	if (fill_p) {
	    g.fillPolygon(xpbuf, ypbuf, npts);
	} else {
	    g.drawPolyline(xpbuf, ypbuf, npts);
	}
    }



}

class GText extends GCmd {
    String s;
    int xpos;
    int ypos;

    GText (int xpos, int ypos, String s) {
	this.xpos = xpos;
	this.ypos = ypos;
	this.s = s;
    }

    public void draw (Graphics g, int dx, int dy, int zoom) {
	if (zoom >= 0) {
	    g.drawString(s, (xpos >> -zoom) - dx, (ypos >> -zoom) - dy);	
	} else {
	    g.drawString(s, (xpos << zoom) - dx, (ypos << zoom) - dy);	
	}
    }
}


class GImage extends GCmd {
    Image im;
    int xpos;
    int ypos;

    GImage (int xpos, int ypos, Image im) {
	this.xpos = xpos;
	this.ypos = ypos;
	this.im = im;
    }

    public void draw (Graphics g, int dx, int dy, int zoom) {
	if (zoom >= 0) {
	    g.drawImage(im, (xpos << zoom) - dx, (ypos << zoom) - dy);	
	} else {
	    g.drawImage(im, (xpos >> -zoom) - dx, (ypos >> -zoom) - dy);	
	}
    }
}

class GColorCmd extends GCmd {
    int red;
    int green;
    int blue;

    GColorCmd (int red, int green, int blue) {
	this.red = red;
	this.green = green;
	this.blue = blue;
    }

    public void draw (Graphics g, int dx, int dy, int zoom) {
	g.setColor(Graphics.getColorOfRGB(red, green,blue));
    }
}