/
maps/
package mapmaker;

import java.awt.*;
import java.util.*;

import mapmaker.mapcmd.*;
import util.*;

public class MapParser {
  
  // some String constants used by mapToText and restoreMap
  static final char 
    IDCHAR = '#',
    DESCCHAR = '/';
  static final String 
    MAP_ID = IDCHAR + "MAP",
    ROOM_ID = IDCHAR + "ROOM",
    LINK_ID = IDCHAR + "LINK",
    END_ID = IDCHAR + "END";
  static final String
    BLOCKED_SIGN = "X",
    VIRTUAL_SIGN = "V";
  static final String
    COLOR_LINE = "C";
  
  /** stores the iterator for the text to parse
   */
  protected TextIterator tit;

  /** stores the dimension of the parsed map
   */
  protected Dimension mapDim;

  /** stores the command for creating the new map
   */
  protected CompositeCommand createCmd;

  // the stuff used to turn a map into a parsable text

  /** used by mapToText;
   * adds lines to output with DESCCHAR in front of each line
   */
  private void addDesc(Text desc, Text output) {
    if (desc == null)
      return;
    for (int i = 0; i < desc.getLineCount(); i++)
      output.append(DESCCHAR + desc.getLine(i));
  } // addDesc

  /** returns an array of strings that describe the given map
   * useful for saving a map as an ascii file
   */
  public Text mapToText(AreaMap map) {
    Room[] rooms = map.getRooms();
    Link[] links = map.getLinks();
    //int minSize = 4 + rooms.length * 2 + links.length * 3;
    Text output = new Text();
    output.append(MAP_ID);
    output.append(map.getSize().width + " " +
		  map.getSize().height);
    addDesc(map.getDesc(), output);
    // store rooms
    for (int i = 0; i < rooms.length; i++) {
      output.append(ROOM_ID);
      String outString = 
	rooms[i].getPos().x + " " + rooms[i].getPos().y;
      if (rooms[i] instanceof VirtualRoom)
	outString += " " + VIRTUAL_SIGN;
      output.append(outString);
      // store color
      Color color = rooms[i].getColor();
      if (color != null)
	output.append(COLOR_LINE + " " + color);
      // store description
      if (rooms[i] instanceof DescObj)
	addDesc(((DescObj)rooms[i]).getDesc(), output);
    }
    // store links
    for (int i = 0; i < links.length; i++) {
      output.append(LINK_ID);
      Room[] linkedRooms = links[i].getRooms();
      for (int nr = 0; nr < 2; nr++) {
	Point roomPos = linkedRooms[nr].getPos();
	String pos = roomPos.x + " " + roomPos.y;
	int exitDir = linkedRooms[nr].getExitDir(links[i]);
	String exit = Dir.dirToString(exitDir);
	if (linkedRooms[nr].exitBlocked(exitDir))
	  exit += " " + BLOCKED_SIGN;
	output.append(pos + " " + exit);
      }
    }
    output.append(END_ID);
    return output;
  } // mapToText

  // now the stuff used to parse a text into a map

  /** parses a String to int and throws an IllegalArgumentException
   * if format is incorrect; used by restoreMap
   * @param s String to be parsed
   * @param msg error message for thrown Exception
   */
  private int parseInt(String s, String msg) {
    try {
      return Integer.parseInt(s);
    }
    catch(NumberFormatException e) {
      throw new IllegalArgumentException(msg);
    }
  } // parseInt

  /** creates a map from the given input
   */
  public AreaMap createMap(Text input) {
    AreaMap map = new CMap(0, 0, new Observable());
    restoreMap(map, input);
    return map;
  } // createMap

  /** restores a map's data from an array of strings
   * in a format as created by mapToText;
   * throws IllegalArgumentException if format is incorrect
   * but leaves map unchanged
   */
  public void restoreMap(AreaMap map, Text input) {
    createCmd = new CompositeCommand();
    tit = new TextIterator(input);

    // read in map size & description
    if (input.getLineCount() < 3)
      throw new IllegalArgumentException("input too small, check aborted");
    // check if input starts with MAP_ID
    if (!tit.next().equals(MAP_ID))
      throw new IllegalArgumentException(MAP_ID + " missing");
    // read size
    String[] sizeStrings = StringUtil.splitString(tit.next());
    if (sizeStrings.length != 2)
      throw new IllegalArgumentException("wrong number of size parameters");
    int sizeX = parseInt(sizeStrings[0], "size format error in x");
    int sizeY = parseInt(sizeStrings[1], "size format error in y");
    if (sizeX < 0 || sizeY < 0)
      throw new IllegalArgumentException("negative size value");
    mapDim = new Dimension(sizeX, sizeY);
    // read map description
    createCmd.add(new CmdSetDesc(parseDesc()));

    // read in rooms & links
    while (tit.hasNext() && !tit.peek().equals(END_ID)) {
      // check wether room or link input
      if (tit.peek().equals(ROOM_ID)) {
	tit.jump();
	parseRoom();
      } // if room
      else
	if (tit.peek().equals(LINK_ID)) {
	  tit.jump();
	  parseLink();
	  } // if link
	else
	  throw new IllegalArgumentException("unexpected String");
    }
    if (!tit.hasNext())
      throw new IllegalArgumentException(END_ID + " missing");

    // now finally execute the whole stuff :)
    AreaMap bufferMap = new CMap(mapDim.width, mapDim.height,
			     new MyObservable());
    bufferMap.execute(createCmd);
    map.deepCopyFrom(bufferMap);
  } // restoreMap

  /** parses a single room from the text given and adds a command
   * that will create it to createCmd;
   * assumes that the ROOM_ID has come before
   */
  protected void parseRoom() {
    String[] posStrings = StringUtil.splitString(tit.next());
    if (!(posStrings.length == 2 || posStrings.length == 3)) {
      String msg = "wrong number of room position parameters";
      throw new IllegalArgumentException(msg);
    }
    String msg = "pos format error";
    int posX = parseInt(posStrings[0], msg);
    int posY = parseInt(posStrings[1], msg);
    Point pos = new Point(posX, posY);
    if (!AwtUtil.contains(mapDim, pos))
      throw new IllegalArgumentException("room position out of range ");

    // create room
    if (posStrings.length == 2)
      createCmd.add(new CmdNewRoom(pos, MapFactory.type_DescRoom));
    else if (posStrings[2].equals(VIRTUAL_SIGN))
      createCmd.add(new CmdNewRoom(pos, MapFactory.type_VirtualRoom));
    else
      throw new IllegalArgumentException("illegal room type");

    // parse color
    String[] colorStrings = StringUtil.splitString(tit.peek());
    if (colorStrings.length > 0 && colorStrings[0].equals(COLOR_LINE)) {
      tit.jump(); // color line found => advance to next
      if (colorStrings.length != 2)
	throw new IllegalArgumentException("wrong number of color parameters");
      NamedColor color = NamedColor.parseName(colorStrings[1]);
      createCmd.add(new RCmdSetColor(pos, color));
    }

    // parse room description
    Text desc = parseDesc();
    createCmd.add(new RCmdSetRoomDesc(pos, desc));
      
  } // parseRoom
  
  /** parses a single link from the text given and adds a command
   * that will create it to createCmd;
   * assumes that the LINK_ID has come before
   */
  protected void parseLink() {
    Point[] pos = new Point[2];
    int[] exits = new int[2];
    boolean[] blocked = new boolean[2];
    for (int i = 0; i < 2; i++) {
      String[] linkStrings = StringUtil.splitString(tit.next());
      // check if blocked
      if (linkStrings.length == 4 && 
	  linkStrings[3].equals(BLOCKED_SIGN))
	blocked[i] = true;
      else {
	blocked[i] = false;
	if (linkStrings.length != 3)
	  throw new IllegalArgumentException("link error");
      }
      // get position of room linked
      String msg = "pos format error in line ";
      int posX = parseInt(linkStrings[0], msg);
      int posY = parseInt(linkStrings[1], msg);
      pos[i] = new Point(posX, posY);
      // get exit direction
      exits[i] = Dir.parseString(linkStrings[2]);
    } // for
    createCmd.add(new CmdLinkRooms(pos[0], exits[0], pos[1], exits[1]));
    for (int i = 0; i < 2; i++)
      if (blocked[i])
	createCmd.add(new RCmdSetExitBlocked(pos[i], exits[i], true));
  } // parseLink
  
  protected Text parseDesc() {
    Text desc = new Text();
    while (tit.peek().charAt(0) == DESCCHAR)
      desc.append(tit.next().substring(1));
    return desc;
  } // parseDesc

} // MapParser