import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import java.net.*;

public class XpmDecodeTest extends Applet {
  Image image;
  static String rgbfile = "rgbtable";
  static Hashtable rgbtable = null;

  /***
    * Applet initialization.
    */
  public void init() {
    String filename = getParameter("xpmfile");
    URL url = null;
    URL rgburl = null;
    InputStream is = null;
    InputStream rgbis = null;
    XpmImageSource proc = null;

    if (filename == null) {
      System.out.println("Please input filename.");
      //System.exit(1);
      stop();
    }

    try {
      if (filename != null)
	url = new URL(getCodeBase(), filename);
      rgburl = new URL(getCodeBase(), rgbfile);
    } catch (MalformedURLException e) {
      System.out.println("URL: " + filename + " is invalid.");
      //System.exit(1);
      stop();
    }

    try {
	if (url != null)
	    is = url.openStream();
	rgbis = rgburl.openStream();
    } catch (IOException e) {
	System.out.println("Cannot open file: " + filename);
	//System.exit(1);
	stop();
    }

    try {
	if (rgbis != null) {
	    RGBTableParser parser
		= new RGBTableParser(new InputStreamReader(rgbis));
	    parser.parse();
	    rgbtable = parser.rgbtable;
	}
	if (is != null)
	    proc = new XpmImageSource(is, rgbtable);
    } catch (IOException e) {
	System.out.println("Cannot initialize XpmImageSource: "
			   + e.getMessage());
	e.printStackTrace();
	//System.exit(1);
	stop();
    }

    if (proc != null)
	image = createImage(proc);
  }

  public void paint(Graphics g) {
    g.setColor(Color.red);
    g.fillRect(0,0,100,100);
    if (image != null)
      g.drawImage(image, image.getWidth(this), image.getHeight(this), this);
  }

  /***
    * main test routine.
    */
  static public void main(String args[]) {
    String filename = args[0];
    InputStream is = null;
    InputStream rgbis = null;

    if (filename == null) {
      System.out.println("Please input filename.");
      System.exit(1);
    }

    try {
	is = new FileInputStream(filename);
	rgbis = new FileInputStream(rgbfile);
    } catch (IOException e) {
	System.out.println("Cannot open file: " + filename);
	System.exit(1);
	//stop();
    }

    try {
	if (rgbis != null) {
	    RGBTableParser parser
		= new RGBTableParser(new InputStreamReader(rgbis));
	    parser.parse();
	    rgbtable = parser.rgbtable;
	}
	XpmImageSource proc = new XpmImageSource(is, rgbtable);
    } catch (IOException e) {
	System.out.println("Cannot initialize XpmImageSource: "
			   + e.getMessage());
	e.printStackTrace();
	System.exit(1);
    }
  }
}

class XpmImageSource implements ImageProducer {
  /***
    * using memery image source for containing data.
    * Should the sun.awt.image.InputStreamImageSource be used?
    */
  MemoryImageSource memsrc;

  XpmImageSource(InputStream is, Hashtable rgbtable) throws IOException {
    XpmDecoder dec = new XpmDecoder(is, rgbtable);
    //dec.readChar();
    try {
	dec.parse();
	int pixels[] = dec.pixels;
	IndexColorModel model = dec.getIndexColorModel();
	memsrc = new MemoryImageSource(dec.width, dec.height, model,
				       pixels, 0, dec.width);
    } catch (XpmParseError e) {
	System.out.println("ParseError");
	e.printStackTrace();
	throw new IOException("XpmParseError");
    }
  }

  public void addConsumer(ImageConsumer ic) {
    memsrc.addConsumer(ic);
  }

  public boolean isConsumer(ImageConsumer ic) {
    return memsrc.isConsumer(ic);
  }

  public void removeConsumer(ImageConsumer ic) {
    memsrc.removeConsumer(ic);
  }

  public void startProduction(ImageConsumer ic) {
    memsrc.startProduction(ic);
  }

  public void requestTopDownLeftRightResend(ImageConsumer ic) {
    memsrc.requestTopDownLeftRightResend(ic);
  }

}

class XpmDecoder {
  Hashtable rgbtable;

  /***
    * a given xpm file. treating the file as PushbackReader.
    */
  PushbackReader pbckrdr;

  /***
    * file format information.
    * set in parseHeader
    */
  int line; // no use?
  int format; /* 1 if XPM1, 0 otherwise */

  /***
    * used for scanning, mainly necessary for XPM 1.
    * but I'm not sure...
    */
  String cmt_bgn;
  String cmt_end;
  char str_bgn;
  char str_end;

  /***
    * store comment temporaly.
    */
  String comment = "";

  /***
    * values
    * set in parseValues.
    */
  int width, height, ncolors, cpp;
  int x_hotspot, y_hotspot;
  boolean  hotspot, extensions;

  /***
    * store XpmColors.
    * created in parseColors.
    */
  ColorTable ctable;

  /***
    * store color index. pixel array
    * created in parsePixels.
    */
  int pixels[];

  /***
    * constructor
    */
  XpmDecoder (InputStream is, Hashtable rgbtable) {
    this.rgbtable = rgbtable;
    pbckrdr = new PushbackReader(new InputStreamReader(is), 16);
  }

  /***
    * start parse.
    */
  public void parse() throws XpmParseError {
    parseHeader();
    parseValues();
    parseColors();
    /*
     * store the colors comment line
     */
    //if (cmts)
	//xpmGetCmt(data, &colors_cmt);
    parsePixels();
    /*
     * store the colors comment line
     */
    //if (cmts)
	//xpmGetCmt(data, &colors_cmt);
    //parseExtensions();
  }

  IndexColorModel model;
  /***
    *
    */
  IndexColorModel getIndexColorModel() throws XpmParseError {
      if (model == null) {
	int bits = 8;
	byte r[] = new byte[ncolors];
	byte g[] = new byte[ncolors];
	byte b[] = new byte[ncolors];
	int trans = -1;

	int color_key = XpmColor.COLOR;
	/* I don't know how to use SYMBOL... */
	/* And I cannot handle X defined color name... */
	for (int cidx=0;cidx < ncolors;cidx++) {
	    XpmColor color = ctable.getColorAt(cidx);
	    String color_name = color.getColorString(color_key);
	    RGB rgb = parseColorName(color_name);
	    if (rgb.trans) trans = cidx;
	    r[cidx] = rgb.r;
	    g[cidx] = rgb.g;
	    b[cidx] = rgb.b;
	}

	if (trans > -1) {
	  model = new IndexColorModel(bits, ncolors, r, g, b, trans);
        } else {
	  model = new IndexColorModel(bits, ncolors, r, g, b);
        }
      }
      return model;
  }
  
  private RGB parseColorName(String color_name) throws XpmParseError {
    RGB rgb = null;
    if (color_name.charAt(0) == '#') {
	String rstr = color_name.substring(1,3);
	String gstr = color_name.substring(5,7);
	String bstr = color_name.substring(9,11);
	/*
	System.out.println("parse color code: " + " " + rstr + ", " +
			   gstr + ", " + bstr);
			   */
	byte r = (byte)Integer.parseInt(rstr, 16);
	byte g = (byte)Integer.parseInt(gstr, 16);
	byte b = (byte)Integer.parseInt(bstr, 16);
	rgb = new RGB(r, g, b);
	//if (rgb != null)
	System.out.println(rgb);
    } else {
	//rgb = (RGB)colorNameTable.get(color_name);
	/* currently cannot handle color name */
	if (color_name.equals("none")) {
	    rgb = new RGB((byte)0, (byte)0, (byte)0);
	    rgb.trans = true;
	} else {
	    NameRGBPair rgbp = (NameRGBPair)rgbtable.get(color_name);
	    if (rgbp == null)
		error ("No such color: " + color_name);
	    System.out.println("NameRGBPair: " + rgbp);
	    //error("Currently cannot handle color name.");
	    rgb = new RGB(rgbp.r, rgbp.g, rgbp.b);
	}
    }
    return rgb;
  }

  /***
    * parse the header of a xpm file, and set the type, XPM 1, 2 or 3,
    * and NATURAL, C or LISP.
    */
  private void parseHeader() throws XpmParseError { //throws ParseErrorException {
    String word;
    str_bgn = '\0';
    str_end = '\n';
    cmt_bgn = cmt_end = null;

    int typecode = XpmDataType.NATURAL;
    format = 0;

    word = readNextWord();
    if (word.equals("#define")) {
      /* this maybe an XPM 1 file */
      word = readNextWord();
      if (word.length() == 0) error("XpmFileInvalid");
      int ptr = word.lastIndexOf('_');
      String substr = word.substring(ptr, word.length());
      System.out.println(substr);
      if (ptr == 0
	  || !substr.equals("_format"))
	error ("XpmFileInvalid");
      /* this is definitely an XPM 1 file */
      format = 1;
      //System.out.println("Set format.");
      typecode = XpmDataType.C;	/* handle XPM1 as mainly XPM2 C */
      readNextString();
    } else {
      /*
       * skip the first word, get the second one, and see if this is
       * XPM 2 or 3
       */
      word = readNextWord();
      if (word.equals("XPM")) {
	typecode = XpmDataType.C; /* handle XPM as XPM2 C */
      } else if (word.equals("XPM2")) {
	/* get the type key word */
	word = readNextWord();
	typecode = XpmDataType.getTypeCode(word);
      } else error("XpmFileInvalid");

      XpmDataType datatype = XpmDataType.getDataType(typecode);
      if (datatype.type == null) error("XpmFileInvalid");
      else {
	if (typecode == XpmDataType.NATURAL) {
	  cmt_bgn = datatype.cmt_bgn;
	  cmt_end = datatype.cmt_end;
	  readNextString(); /* skip the end of headerline */
	  str_bgn = datatype.str_bgn;
	  str_end = datatype.str_end;
	} else {
	  cmt_bgn = datatype.cmt_bgn;
	  cmt_end = datatype.cmt_end;
	  if (format == 0) {
	    str_bgn = datatype.str_bgn;
	    str_end = '\0';
	    /* get to the beginning of the first string */
	    readNextString(); /* skip the end of headerline */
	    str_end = datatype.str_end;
	  } else
	    readNextString();
	}
      }
    }
  }

  /***
    * parse values such as width, height, ncolors, cpp and so on.
    */
  private void parseValues() throws XpmParseError {
    if (format == 0) { /* XPM 2 or 3 */
      /*
       * read values: width, height, ncolors, chars_per_pixel
       */
      if ((width = readNextNumber()) == 0 ||
	  (height = readNextNumber()) == 0 ||
	  (ncolors = readNextNumber()) == 0 ||
	  (cpp = readNextNumber()) == 0) {
	error("XpmFileInvalid");
      }

      /*
       * read optional information (hotspot and/or XPMEXT) if any
       */
      String word = readNextWord();
      if (word.length()>0) {
	extensions = word.equals("XPMEXT");
	if (extensions) {
	  x_hotspot = readNextNumber();
	  y_hotspot = readNextNumber();
	  hotspot = true;
	} else {
	  x_hotspot = Integer.parseInt(word);
	  y_hotspot = readNextNumber();
	  hotspot = true;
	  word = readNextWord();
	  extensions = word.equals("XPMEXT");
	}
      }
    } else {
      /*
       * XPM 1 file read values: width, height, ncolors, chars_per_pixel
       */
      int index;
      boolean got_one, saw_width = false, saw_height = false;
      boolean saw_ncolors = false, saw_chars_per_pixel = false;

      String word;
      for (int i = 0; i < 4; i++) {
	word = readNextWord();
	//System.out.println(word);
	if (!word.equals("#define"))
	  error ("XpmFileInvalid");
	word = readNextWord();
	if (word.length()==0)
	  error("XpmFileInvalid");
	index = 0;
	got_one = false;
	while (!got_one) {
	  index = word.indexOf('_');
	  if (index == -1)
	    error("XpmFileInvalid");
	  String substr = word.substring(index, word.length());
	  //System.out.println(substr);
	  if (substr.equals("_width")) {
	    if (saw_width)
	      error("XpmFileInvalid");
	    width = readNextNumber();
	    saw_width = true;
	    got_one = true;
	  } else if (substr.equals("_height")) {
	    if (saw_height)
	      error("XpmFileInvalid");
	    height = readNextNumber();
	    saw_height = true;
	    got_one = true;
	  } else if (substr.equals("_ncolors")) {
	    if (saw_ncolors)
	      error("XpmFileInvalid");
	    ncolors = readNextNumber();
	    saw_ncolors = true;
	    got_one = true;
	  } else if (substr.equals("_chars_per_pixel")) {
	    if (saw_chars_per_pixel)
	      error("XpmFileInvalid");
	    cpp = readNextNumber();
	    saw_chars_per_pixel = true;
	    got_one = true;
	  } else {
	    index++;
	  }
	}
	/* skip the end of line */
	readNextString();
      }
      if (!saw_width || !saw_height ||
	  !saw_ncolors || !saw_chars_per_pixel)
	error ("XpmFileInvalid");
      hotspot = false;
      extensions = false;
    }
    /*
    System.out.println("width: " + width + " height: " + height
		       + " ncolors: " + ncolors + " cpp: " + cpp);
		       */
    if (hotspot) {
      System.out.println("hotspot x: " + x_hotspot + " y: " + y_hotspot);
    }
    if (extensions) 
      System.out.println("Extensions");
  }

  /***
    * parse and create a ColorTable
    */
  private void parseColors() throws XpmParseError {
    ctable = new ColorTable(ncolors, cpp);
    String word;

    if (format == 0) {		/* XPM 2 or 3 */
      for (int cidx = 0; cidx < ncolors; cidx++) {
	readNextString();	/* skip the line */
	  
	/*
	 * read pixel value
	 */
	StringBuffer strbuf = new StringBuffer();
	for (int index = 0; index < cpp; index++)
	  strbuf.append(readChar());
	XpmColor color = new XpmColor();
	color.string = strbuf.toString();

	/*
	 * store the string in the hashtable with
	 * its color index number
	 */
	ctable.addColor(color);
	
	/*
	 * read color keys and values
	 */
	int curkey = -1;	/* current color key */
	boolean lastwaskey = false;	/* key read */
	StringBuffer curbuf = new StringBuffer(); /* init curbuf */
	while ((word = readNextWord()).length() != 0) {
	  int key = 0;
	  if (!lastwaskey) {
	    for (key = 0; key < XpmColor.NKEYS; key++)
	      if (word.equals(XpmColor.getColorKey(key)))
		break;
	  }
	  if (!lastwaskey && key < XpmColor.NKEYS) {
	    /* open new key */
	    if (curkey != -1) {	/* flush string */
	      color.setColorString(curkey, curbuf.toString());
	      //color.setColorString(word, curbuf.toString());
	    }
	    curkey = key;	/* set new key  */
	    curbuf = new StringBuffer();	/* reset curbuf */
	    lastwaskey = true;
	  } else {
	    if (curkey < 0) {	/* key without value */
	      error("XpmFileInvalid");
	    }
	    if (!lastwaskey)
	      curbuf.append(" ");	/* append space */
	    curbuf.append(word);/* append buf */
	    lastwaskey = false;
	  }
	}
	if (curkey < 0) {		/* key without value */
	  error("XpmFileInvalid");
	}
	color.setColorString(curkey, curbuf.toString());
      }
    } else {				/* XPM 1 */
      /* get to the beginning of the first string */
      str_bgn = '"';
      str_end = '\0';
      readNextString();
      str_end = '"';
      for (int cidx = 0; cidx < ncolors; cidx++) {
	/*
	 * read pixel value
	 */
	StringBuffer strbuf = new StringBuffer();
	for (int index = 0; index < cpp; index++)
	  strbuf.append(readChar());
	XpmColor color = new XpmColor();
	color.string = strbuf.toString();
	
	/*
	 * store the string in the hashtable with its color index number
	 */
	ctable.addColor(color);
	
	/*
	 * read color values
	 */
	readNextString();	/* get to the next string */
	StringBuffer curbuf = new StringBuffer(); /* init curbuf */
	while ((word = readNextWord()).length() != 0) {
	  if (curbuf.length() != 0)
	    curbuf.append(" ");/* append space */
	  curbuf.append(word);	/* append buf */
	}
	color.c_color = curbuf.toString();
	curbuf = new StringBuffer();		/* reset curbuf */
	if (cidx < ncolors - 1)
	  readNextString();	/* get to the next string */
      }
    }
    /*
    System.out.println(ctable.toString());
    */
    //return (XpmSuccess);
  }

  /***
    * parse and create pixel array with color index.
    */
  private void parsePixels() throws XpmParseError {
    pixels = new int[width * height];
    int index = 0, cidx;

    if (cpp == 1) {
      for (int y = 0; y < height; y++) {
	readNextString();
	for (int x = 0; x < width; x++, index++) {
	  char c = readChar();
	  
	  if (c > 0 && c < 256)
	    if ((cidx = ctable.getIndex(c)) != -1)
	      pixels[index] = cidx;
	    else {
	      pixels = null;
	      error("XpmFileInvalid");
	    }
	}
      }
    } else if (cpp == 2) {
      for (int y = 0; y < height; y++) {
	readNextString();
	for (int x = 0; x < width; x++, index++) {
	  char cc1 = readChar();
	  if (cc1 > 0 && cc1 < 256) {
	    char cc2 = readChar();
	    if (cc2 > 0 && cc2 < 256)
	      if ((cidx = ctable.getIndex(cc1, cc2)) != -1)
		pixels[index] = cidx;
	      else {
		pixels = null;
		error("XpmFileInvalid");
	      }
	  } else {
	    error("XpmFileInvalid");
	  }
	}
      }
    } else { /* cpp > 2 */
      for (int y = 0; y < height; y++) {
	readNextString();
	for (int x = 0; x < width; x++, index++) {
	  StringBuffer keybuf = new StringBuffer();
	  for (int a = 0; a < cpp; a++)
	    keybuf.append(readChar());
	  if ((cidx = ctable.getIndex(keybuf.toString())) != -1)
	    pixels[index] = cidx;
	  else {
	    pixels = null;
	    error("XpmFileInvalid");
	  }
	}
      }
    }
    /*
    for (int y=0;y<height;y++) {
      for (int x=0;x<width;x++) {
	System.out.print(pixels[y*width+x]);
      }
      System.out.println();
    }*/
    //return (XpmSuccess);
  }

  private void error(String msg) throws XpmParseError {
    //System.out.println(msg);
    //System.exit(1);
    throw new XpmParseError(msg);
  }

  private char readChar() throws XpmParseError {
    char c = (char)-1;
    try {
      c = (char)pbckrdr.read();
    } catch (IOException e) {
      error ("IOException");
    }

    //System.out.println("Read char -> " + c + "(" + (int)c + ")");
    return c;
  }

  private int readNextNumber() throws XpmParseError {
    String word = readNextWord();
    int i = Integer.parseInt(word);
    return i;
  }

  private String readNextWord() throws XpmParseError {
    String spaces = " \t\n\r\f"; // \v dosent exist...

    char c = readChar();
    /* skip whites */
    while (c != -1 && spaces.indexOf(c) > -1 && c != str_end)
      c = readChar();

    StringBuffer buf = new StringBuffer(c);
    /* grow buffer */
    while (c != -1 && spaces.indexOf(c) == -1 && c != str_end) {
      buf.append(c);
      c = readChar();
    }
    try {
      pbckrdr.unread(c);
    } catch (IOException e) {
    }
    //System.out.println("Current word-> " + buf.toString());
    return buf.toString();
  }

  private void readNextString() throws XpmParseError {
    char c;

    /* get to the end of the current string */
    if (str_end != 0)
      while ((c = readChar()) != str_end && c != -1);

    /*
     * then get to the beginning of the next string looking for possible
     * comment
     */
    if (str_bgn != 0) {
      while ((c = readChar()) != str_bgn && c != -1) 
	if (cmt_bgn != null && c == cmt_bgn.charAt(0))
	  parseComment();
    } else if (cmt_bgn != null) {
      while ((c = readChar()) == cmt_bgn.charAt(0))
	parseComment();
      try {
	pbckrdr.unread(c);
      } catch (IOException e) {
      }
    }
  }

  private void parseComment() throws XpmParseError {
    char c;
    StringBuffer buf = new StringBuffer(cmt_bgn.charAt(0));

    int idx = 0;
    do {
      c = readChar();
      buf.append(c);
      idx++; 
    } while (idx < cmt_bgn.length()
	     && c == cmt_bgn.charAt(idx) && c != -1);

    if (idx != cmt_bgn.length()) {
      /* this wasn't the beginning of a comment */
      /* put characters back in the order that we got them */
      char str[] = new char[idx];
      buf.getChars(0, idx-1, str, 0);
      try {
	pbckrdr.unread(str, 0, idx);
      } catch (IOException e) {
      }
      return;
    }
    buf = new StringBuffer(c);
    boolean notend = true;
    idx = 0;
    while (notend) {
      while (c != cmt_end.charAt(0) && c != -1) {
	c = readChar();
	buf.append(c);
      }

      do {
	c = readChar();
	buf.append(c);
	idx++;
      } while (idx < cmt_end.length()
	       && c == cmt_end.charAt(idx) && c != -1);

      if (idx == cmt_end.length()) {
	/* this is the end of the comment */
	notend = false;
	try {
	  pbckrdr.unread(c);
	} catch (IOException e) {
	}
      }
    }
    //System.out.println("Comment -> " + buf);
    comment = comment + buf.toString();
  }

}

class XpmData {
}

class XpmDataType {
  static int NATURAL = 0;
  static int C = 1;
  static int LISP = 2;

  static XpmDataType xpmDataTypes[] = {
    new XpmDataType("", "!", "\n", '\0', '\n', "", "", "", ""),
    new XpmDataType("C", "/*", "*/", '"', '"',
		    ",\n", "static char *", "[] = {\n", "};\n"),
    new XpmDataType("Lisp", ";", "\n", '"', '"',
		    "\n", "(setq ", " '(\n", "))\n"),
    new XpmDataType(null, null, null, (char)0, (char)0,
		    null, null, null, null)
  };

  String type;    /* key word */
  String cmt_bgn; /* beginning comments */
  String cmt_end; /* ending comments */
  char str_bgn;   /* beginning string */
  char str_end;   /* ending comments */
  String str_sep; /* strings separator */
  String dec;     /* data declaration string */
  String ass_bgn; /* string beginning assignment */
  String ass_end; /* string ending assignment */

  XpmDataType(String type, String cmt_bgn, String cmt_end,
	      char str_bgn, char str_end, String str_sep,
	      String dec, String ass_bgn, String ass_end) {
    this.type = type;
    this.cmt_bgn = cmt_bgn;
    this.cmt_end = cmt_end;
    this.str_bgn = str_bgn;
    this.str_end = str_end;
    this.str_sep = str_sep;
    this.dec = dec;
    this.ass_bgn = ass_bgn;
    this.ass_end = ass_end;
  }

  static int getTypeCode(String keyword) {
    for(int i = 0;i<3;i++)
      if (xpmDataTypes[i].type.equals(keyword)) return i;
    return 3;
  }

  static XpmDataType getDataType(int code) {
    return xpmDataTypes[code];
  }
}

class XpmColor {
  static int NKEYS = 5;

  static int SYMBOL = 0;
  static int MONO = 1;
  static int GRAY4 = 2;
  static int GRAY = 3;
  static int COLOR = 4;

  static String xpmColorKeys[] = {
    "s",				/* key #0: symbol */
    "m",				/* key #1: mono visual */
    "g4",				/* key #2: 4 grays visual */
    "g",				/* key #3: gray visual */
    "c",				/* key #4: color visual */
  };

  static String getColorKey(int key) {
    return xpmColorKeys[key];
  }

  String getColorString(int key) {
    if (key == 0)
      return symbolic;
    else if (key == 1)
      return m_color;
    else if (key == 2)
      return g4_color;
    else if (key == 3)
      return g_color;
    else if (key == 4)
      return c_color;
    else
      //error("XpmColorError");
      return null;
  }

  String getColorString(String key) {
    if (key.equals("s"))
      return symbolic;
    else if (key.equals("m"))
      return m_color;
    else if (key.equals("g4"))
      return g4_color;
    else if (key.equals("g"))
      return g_color;
    else if (key.equals("c"))
      return c_color;
    else
      //error("XpmColorError");
      return null;
  }

  void setColorString(String key, String color) {
    if (key.equals("s"))
      symbolic = color;
    else if (key.equals("m"))
      m_color = color;
    else if (key.equals("g4"))
      g4_color = color;
    else if (key.equals("g"))
      g_color = color;
    else if (key.equals("c"))
      c_color = color;
    else
      //error("XpmFileInvalid");
      ;
  }

  void setColorString(int key, String color) {
    if (key == 0)
      symbolic = color;
    else if (key == 1)
      m_color = color;
    else if (key == 2)
      g4_color = color;
    else if (key == 3)
      g_color = color;
    else if (key == 4)
      c_color = color;
    else
      //error("XpmFileInvalid");
      ;
  }

  int cidx; /* color index */

  String string; /* characters string */
  String symbolic;		/* symbolic name */
  String m_color;		/* monochrom default */
  String g4_color;		/* 4 level grayscale default */
  String g_color;		/* other level grayscale default */
  String c_color;		/* color default */

  public String toString() {
    StringBuffer buf = new StringBuffer("XpmColor[");
    buf.append(cidx).append("] (");
    buf.append(string).append("): s ").append(symbolic);
    buf.append(", m ").append(m_color);
    buf.append(", g4 ").append(g4_color);
    buf.append(", g ").append(g_color);
    buf.append(", c ").append(c_color);
    return buf.toString();
  }
}

class ColorTable {
  /***
   * for color indexing.
   * when cpp == 1 optimization.
   */
  XpmColor colidx1[];

  /***
   * for color indexing.
   * when cpp == 2 optimization.
   */
  XpmColor colidx2[][];

  /***
   * for color indexing.
   * cpp > 2 or not optimized access when cpp < 3.
   */
  Hashtable ctable;

  int ncolors;
  int cpp;
  private int cidx;

  /***
   * main color table
   */
  XpmColor colors[];

  ColorTable(int ncolors, int cpp) {
    this.ncolors = ncolors;
    this.cpp = cpp;
    if (cpp == 1)
      colidx1 = new XpmColor[256];
    else if (cpp == 2)
      colidx2 = new XpmColor[256][];
    else /* cpp > 2 */
      ctable = new Hashtable();
    colors = new XpmColor[ncolors];
    cidx = 0;
  }

  int getIndex(char c) {
    if (cpp != 1) return -1; /* error */
    XpmColor color = colidx1[c];
    if (color != null)
      return color.cidx-1;
    return -1;
  }

  int getIndex(char cc1, char cc2) {
    if (cpp != 2) return -1; /* error */
    XpmColor color = colidx2[cc1][cc2];
    if (color != null)
      return color.cidx-1;
    return -1;
  }

  int getIndex(String keystr) {
    if (cpp > 2) return -1; /* error */
    XpmColor color = (XpmColor)ctable.get(keystr);
    if (color != null)
      return color.cidx-1;
    return -1;
  }

  void addColor(XpmColor color) {
    color.cidx = cidx+1;
    colors[cidx] = color;
    cidx++;
    if (cpp == 1) {
      colidx1[color.string.charAt(0)] = color;
    } else if (cpp == 2) {
      char char1 = color.string.charAt(0);
      if (colidx2[char1] == null) { /* get new memory */
	colidx2[char1] = new XpmColor[256];
      }
      colidx2[char1][color.string.charAt(1)] = color;
    } else { /* cpp > 2 */
      ctable.put(color.string, color);
    }
  }

  XpmColor getColorOf(String keystr) {
    if (cpp == 1) {
	return colidx1[keystr.charAt(0)];
    } else if (cpp == 2) {
	return colidx2[keystr.charAt(0)][keystr.charAt(1)];
    }
    return (XpmColor)ctable.get(keystr);
  }

  XpmColor getColorAt(int index) {
    return colors[index];
  }

  public String toString() {
    StringBuffer buf = new StringBuffer("ColorTable {\n");
    for (int i=0; i < ncolors; i++) {
      XpmColor color = colors[i];
      buf.append("\t").append(color.toString()).append("\n");
    }
    buf.append("}");
    return buf.toString();
  }
}

class RGB {
  byte r, g, b;
  boolean trans = false;

  RGB(byte r, byte g, byte b) {
    this.r = r;
    this.g = g;
    this.b = b;
  }
  
  public String toString() {
    String rstr = Integer.toHexString((int)r);
    if (rstr.length() == 8)
      rstr = rstr.substring(5,7);
    else if (rstr.length() == 1)
      rstr = "0" + rstr;
    String gstr = Integer.toHexString((int)g);
    if (gstr.length() == 8)
      gstr = gstr.substring(5,7);
    else if (gstr.length() == 1)
      gstr = "0" + gstr;
    String bstr = Integer.toHexString((int)b);
    if (bstr.length() == 8)
      bstr = bstr.substring(5,7);
    else if (bstr.length() == 1)
      bstr = "0" + bstr;

    return "r: " + rstr
	+ " g: " + gstr
	+ " b: " + bstr;
  }
}

class XpmParseError extends Exception {
    XpmParseError() {
	super();
    }

    XpmParseError(String msg) {
	super(msg);
    }
}
