/*==============================================================*
 * Production Title: PSX/GB Map editor version 0.1			  	*
 *--------------------------------------------------------------*
 *  Date started...: 5/6/99		*  Code:  Andrew Partington   	*
 *  Date finished..: 27/8/99	*							  	*
 *--------------------------------------------------------------*
 *																*
 *  Info: This is a util to help you design maps for 2d games.	*
 *  	Emphasis on PSX formats, GB format to be added later.	*	
 *																*
 *==============================================================*/
 
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import java.lang.*;


public class MapEditor extends Frame implements ActionListener, ItemListener, AdjustmentListener
 {
 
 	static BlockSelector tiles;									//Window for tile blocks
 
 	Menu fileMenu,fileOpen,mapMenu,helpMenu,mapTileMenu,mapFormatMenu;
	MenuItem fileOpenTiles,fileOpenMap,fileMapSave,fileSave,fileExit,mapSize,helpAbout;
	CheckboxMenuItem mapTile8x8,mapTile16x16,mapTile32x32,mapFormatPSX,mapFormatGB;
	MenuBar menubar;

	static int mapTileSize;					//size of the selected block
	static BlockCanvas mapImage;			//Canvas to draw the map on
	int[][] mapData;						//(GsBG format) The array of map blocks	

	Dialog sizeDialog;						//Dialogue box to input x and y size of map
	Dialog aboutDialog;						//Dialogue for about box
	Button aboutButton;	
	Label aboutLabel1,aboutLabel2,aboutLabel3,aboutLabel4;
	Panel aboutPanel;
	
	Label ld1,ld2,ld3,ld4;					//Labels for dialogue box
	TextField td1,td2,td3,td4;				//TextAreas to type stuff into
	Button tb1,tb2;							//Buttons for size dialogue box
	Panel sizePanel;
	
	BlockContainer[][] mapComponents;		//The internal representation of the blocks on the map

	static int noBlocksX,noBlocksY;			//Number of blocks across and down the screen
	
	static ScrollPane mainPanel;			//The scrollbar panel that the canvas goes on
	Adjustable hadj,vadj;					//The state of the scrollbars on the scrollpane
	
	int fillStartX,fillStartY;
	int fillStopX,fillStopY;
	boolean mousePressed;


/*================================================*
 *												  *
 *	Constructor method for map editor	          *
 *												  *
 *================================================*/
	 
 	public MapEditor()
	{

		super("TileMaster 2D Map Editor prototype");
		addWindowListener(new MapEditor.WindowEventHandler());

		noBlocksX=20;					//some default map size 
		noBlocksY=16;					// this gives us 320*256 using 16*16 blocks,						
										// 640*512 using 32*32, and 160*128 using 8*8

		mapComponents=new BlockContainer[noBlocksX][noBlocksY];

		sizeDialog=new Dialog(this,"Set map size");
		sizeDialog.setSize(64,48);
		
		sizePanel=new Panel();
		sizePanel.setLayout(new GridLayout(3,2));
	//	sizePanel.setSize(128,48);
		
		ld1=new Label("Map width (blocks):");
		ld2=new Label("Map height (blocks):");
		td1=new TextField(5);
		td2=new TextField(5);
		tb1=new Button("OK");
		tb2=new Button("Cancel");
	
		tb1.addActionListener(this);					//add listeners to the size dialog
		tb2.addActionListener(this);					//  buttons and textfields
		td1.addActionListener(this);
		td2.addActionListener(this);


/*-----------------------------------*
 	
	Set up the about dialogue box

 *-----------------------------------*/

		aboutDialog=new Dialog(this,"About");
		aboutButton=new Button("OK");
		aboutButton.addActionListener(this);
		aboutPanel=new Panel();
		aboutPanel.setLayout(new GridLayout(5,1));
		aboutDialog.setLayout(new BorderLayout());
		aboutLabel1=new Label("TileMaster 2D Map editor",Label.CENTER);
		aboutLabel2=new Label("(c)1999 Andrew Partington",Label.CENTER);
		aboutLabel3=new Label("E-mail:  a.partington@yaroze41.freeserve.co.uk",Label.CENTER);
		aboutLabel4=new Label("See TMASTER.DOC for the instructions!",Label.CENTER);
		aboutPanel.add(aboutLabel1);
		aboutPanel.add(aboutLabel2);
		aboutPanel.add(aboutLabel3);
		aboutPanel.add(aboutLabel4);
		aboutPanel.add(aboutButton);
		aboutDialog.add("Center",aboutPanel);
		aboutDialog.setSize(264,140);
		
		sizePanel.add(ld1);
		sizePanel.add(td1);
		sizePanel.add(ld2);
		sizePanel.add(td2);
		sizePanel.add(tb1);
		sizePanel.add(tb2);
	
		// sizePanel.pack();
		
		sizeDialog.add(sizePanel);
		
		sizeDialog.pack();
			
		menubar=new MenuBar();
		
		mapImage=new BlockCanvas();							//Initialise the custom canvas
		mapImage.addMouseListener(new MouseEventHandler());
		mapImage.addMouseMotionListener(new MouseMotionHandler());
			
	
	/**
	 *
	 *		Set up file menu items
	 *
	 **/
		
		fileMenu=new Menu("File");
		fileOpen=new Menu("Open...");
		fileOpenTiles=new MenuItem("Tile graphics");
		fileOpenMap=new MenuItem("Level map");
		
		
		fileSave=new MenuItem("Export map data");
		fileMapSave=new MenuItem("Save map");
		fileExit=new MenuItem("Exit");
		
		
		fileOpen.add(fileOpenTiles);
		fileOpen.add(fileOpenMap);
		
		
		fileMenu.add(fileOpen);
		fileMenu.addSeparator();
		fileMenu.add(fileSave);
		fileMenu.add(fileMapSave);
		fileMenu.addSeparator();
		fileMenu.add(fileExit);
		
		
	/**
	 *
	 *		Set up map menu items
	 **/
		
		mapMenu=new Menu("Map");
		mapSize=new MenuItem("Set size");
		mapMenu.add(mapSize);
		
		mapTileMenu=new Menu("Tile size...");
		mapTile8x8=new CheckboxMenuItem("8 x 8");
		mapTile16x16=new CheckboxMenuItem("16 x 16");
		mapTile32x32=new CheckboxMenuItem("32 x 32");
		
		mapFormatMenu=new Menu("Map format...");
		mapFormatPSX=new CheckboxMenuItem("PSX GsBG");
		mapFormatGB=new CheckboxMenuItem("PSX Sprite");
		
		mapTileMenu.add(mapTile8x8);
		mapTileMenu.add(mapTile16x16);
		mapTileMenu.add(mapTile32x32);
		mapMenu.add(mapTileMenu);
		mapMenu.addSeparator();
		
		mapFormatMenu.add(mapFormatPSX);
		mapFormatMenu.add(mapFormatGB);
		mapMenu.add(mapFormatMenu);
		
	/**
	 *
	 *		Set up help menu items
	 *
	 **/
		
		helpMenu=new Menu("Help");
		helpAbout=new MenuItem("About");
		helpMenu.add(helpAbout);
		
		
/*-------------------------------------------------------*
		
	Attach action and item listeners to the menu items
		 
 *-------------------------------------------------------*/
		
		fileOpenTiles.addActionListener(this);
		fileOpenMap.addActionListener(this);
		fileSave.addActionListener(this);
		fileMapSave.addActionListener(this);
		fileExit.addActionListener(this);
		mapSize.addActionListener(this);
		
		helpAbout.addActionListener(this);
	
		mapTile8x8.addItemListener(this);
		mapTile16x16.addItemListener(this);
		mapTile32x32.addItemListener(this);
		mapFormatPSX.addItemListener(this);
		mapFormatGB.addItemListener(this);
	
		mapTile8x8.setState(false);
		mapTile16x16.setState(true);
		mapTile32x32.setState(false);
	
		mapFormatPSX.setState(true);
		mapFormatGB.setState(false);
		
		mapTileSize=BlockSelector.TILE16x16;
		
/*--------------------------------------------*
			
	Put the main map editor window together
	     
 *--------------------------------------------*/
		
		menubar.add(fileMenu);
		menubar.add(mapMenu);
		menubar.add(helpMenu);
		
		this.setMenuBar(menubar);

		mainPanel=new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
		hadj=mainPanel.getHAdjustable();				//Get scrollbar event components
		vadj=mainPanel.getVAdjustable();				//  from the scrollpane
		hadj.addAdjustmentListener(this);				//Set the adjustment listener
		vadj.addAdjustmentListener(this);				//  for the scrollpane

		hadj.setBlockIncrement(8);
		vadj.setBlockIncrement(8);
	
		mainPanel.setSize((noBlocksX<<mapTileSize)+mainPanel.getVScrollbarWidth(),(noBlocksY<<mapTileSize)+mainPanel.getHScrollbarHeight());
		this.setSize((noBlocksX<<mapTileSize)+mainPanel.getVScrollbarWidth()+16,(noBlocksY<<mapTileSize)+mainPanel.getHScrollbarHeight()+16);
		mapImage.setSize((noBlocksX<<mapTileSize)+mainPanel.getVScrollbarWidth(),(noBlocksY<<mapTileSize)+mainPanel.getHScrollbarHeight());
		
		mainPanel.add(mapImage);	
		this.add("Center",mainPanel);
		
		mainPanel.doLayout();
		
	
		this.pack();
		
		show();
	
	}
	
	
/*=================================================*
 * Main program execution routine						*
 *=================================================*/
	 
 	public static void main(String[]args)
	{
	
		MapEditor mapedit=new MapEditor();
		tiles=new BlockSelector();
	
		tiles.setBlockSize(BlockSelector.TILE16x16);
		mapTileSize=BlockSelector.TILE16x16;	
		mapImage.setBlockSize(mapTileSize);		
		
	}
	
	
/**
 *
 *	Export map blocks as a C source file, representing a 2D array
 *
 **/
	
	public void saveMapData(Object map)
	{
		BlockContainer[][] bc=(BlockContainer[][])map;
		byte[] strbytes;
		Vector text=new Vector();
		String textline=null;
		StringTokenizer s1;
		int xsize,ysize;
		
		FileDialog saveDialog=new FileDialog(this,"Save map data",FileDialog.SAVE);
		saveDialog.show();
		
		textline=saveDialog.getFile();
		s1=new StringTokenizer(textline,".");
		
		
	/*-----------------------------------------*
	 *										   *
	 *	Write the map data as a 2D array in C  *
	 *										   *
	 *-----------------------------------------*/
		
		textline=("static u_short "+s1.nextToken()+"["+noBlocksY+"]["+noBlocksX+"]= {\r\n");
		
		text.addElement(textline);							//Add the first line to the vector
	
		for(ysize=0;ysize<(noBlocksY-1);ysize++)			//number of lines in 2D array
		{
			if(bc[0][ysize]!=null)							//Do first item seperately from rest
			{
				textline=new String("    {"+bc[0][ysize].blockNumber);	
			}
			else textline=new String("    {0xffff");
			
			for(xsize=1;xsize<noBlocksX;xsize++)
			{
				textline+=(",");								//Put in the field seperator
				
				if(bc[xsize][ysize]!=null)
				{
					textline+=(bc[xsize][ysize].blockNumber);	//Write out the block number,
				}												// or 0xffff for a blank block
				else textline+=("0xffff");
			}
			
			textline+=("},\r\n");					//because not the last line, put a comma at the end
			text.addElement(textline);
		}
		
		// do last lines of the file
		
		if(bc[0][noBlocksY-1]!=null)
		{
			textline=new String("    {"+bc[0][ysize].blockNumber);
		}
		else textline=new String("    {0xffff");
			
		for(xsize=1;xsize<noBlocksX;xsize++)
		{
			textline+=(",");
			
			if(bc[xsize][noBlocksY-1]!=null)
			{
				textline+=(bc[xsize][noBlocksY-1].blockNumber);
			}
			else textline+=("0xffff");
		}
		textline+=("}\r\n};");								//the very last things in the file
		text.addElement(textline);
	
	
	/*---------------------------------------------------*
	 *													 *
	 *	Write out all the strings stored in the vector   *
	 * 													 *
	 *---------------------------------------------------*/
	
		Enumeration e=text.elements();
		
		try
		{
			FileOutputStream mapFile=new FileOutputStream(saveDialog.getFile());
	
			while(e.hasMoreElements())
			{
				textline=(String)e.nextElement();
				strbytes=textline.getBytes();	
				mapFile.write(strbytes);
			}
			
			mapFile.close();
		}
	
		catch(IOException ex)
		{
			System.out.println("no can dae, laddie");
		}
	}
	
/**
 *
 *  Routine:  Save map
 *
 **/

	public void SaveMap()
	{
	//	int xtmp,ytmp;				//intermediate values for x and y size of map
		int[] tileData;
		Image img;
		int block_id;
		BlockContainer bc=new BlockContainer();
	
		FileDialog fd=new FileDialog(this,"Save Level map",FileDialog.SAVE);
		fd.show();
		if((fd.getDirectory()!=null)&&(fd.getFile()!=null))
		{
			try
			{
				FileOutputStream fileOut=new FileOutputStream(fd.getFile());
				ObjectOutputStream mapDataFile=new ObjectOutputStream(fileOut);
				
				mapData=(int[][])mapImage.getRawMapData();
			
				mapDataFile.writeShort(noBlocksX);				//put the size of the map array
				mapDataFile.writeShort(noBlocksY);				//  at the front of the file
			
				for(int y=0;y<noBlocksY;y++)
				{
					for(int x=0;x<noBlocksX;x++)
					{
						mapDataFile.writeShort(mapData[x][y]);	
					}
				}
				
				mapDataFile.flush();
				mapDataFile.close();
			}
			
			catch(IOException ex)
			{
				System.out.println("cant save map data");
			}
		}
	}


/*=========================================*
 *
 * Routine:  Load map
 *
 *=========================================*/

	public void LoadMap()
	{	
		int[] tileData;
		Image img;
		int block_id;
		BlockContainer bc;
	
		FileDialog fd=new FileDialog(this,"Load Level map",FileDialog.LOAD);
		fd.show();
		if((fd.getDirectory()!=null)&&(fd.getFile()!=null))
		{
			try
			{
				FileInputStream fileIn=new FileInputStream(fd.getFile());
				ObjectInputStream mapDataFile=new ObjectInputStream(fileIn);	//wrapper for byte stream
					
				noBlocksX=mapDataFile.readShort();				//restore the old X and Y sizes
				noBlocksY=mapDataFile.readShort();				
				
				mapImage.changeMapSize(noBlocksX,noBlocksY);

				this.setSize(noBlocksX<<mapTileSize,noBlocksY<<mapTileSize);
				mapImage.setSize(noBlocksX<<mapTileSize,noBlocksY<<mapTileSize);
				mainPanel.setSize(noBlocksX<<mapTileSize,noBlocksY<<mapTileSize);
					
				mainPanel.doLayout();
				
				this.pack();
					
		/*---------------------------------------------------*		
		 *	look up each block ID, and put the block in the  *
		 *	in the right place in the BlockContainer array   *
		 *---------------------------------------------------*/
					
				for(int y=0;y<noBlocksY;y++)
				{
					for(int x=0;x<noBlocksX;x++)
					{
						block_id=mapDataFile.readShort();
						img=tiles.locateBlock(block_id);
			
						if(img!=null)
						{
							bc=new BlockContainer(img,x<<mapTileSize,y<<mapTileSize,block_id);							
							mapImage.putTile(bc,false);
						}
					
					}
				}
							
				mapDataFile.close();		
			}
			
			catch(IOException ex)
			{
				System.out.println("cant load map data");
			}
				
		}
		
	}

	
/**
 *	
 *	main paint routine - executed on window resize
 *
 **/
	
	public void paint(Graphics g)
	{
			mapImage.paintAll(mapImage.getGraphics());
	}

	
/*======================================================*
 *														*
 * AdjustmentListener() implementation				    *
 *														*
 *======================================================*/

	public void adjustmentValueChanged(AdjustmentEvent e)
	{
		//mapImage.paintAll(mapImage.getGraphics());
		
		mapImage.repaint();
	}



/*======================================================*
 *														*
 *	ActionListener() implementation						*
 *														*
 *======================================================*/
	
	public void actionPerformed(ActionEvent e) 
	{
	
		int xtmp,ytmp;				//intermediate values for x and y size of map
/**
 *
 *	File load
 *
 **/
	
		if(e.getSource()==fileOpenTiles)
		{
			FileDialog fd=new FileDialog(this,"Open tile graphics",FileDialog.LOAD);
			fd.show();
			
			if((fd.getDirectory()!=null)&&(fd.getFile()!=null))
			{
				tiles.openBlockGFX(fd.getDirectory()+fd.getFile());
			}
		}


		
/**
 *
 *	Load map (intermediate representation)
 *	
 **/

	if(e.getSource()==fileOpenMap)
	{
		LoadMap();
	}



		
/**
 *
 *	Save map (intermediate representation)
 *	
 *	mapimage.getmapdata()
 *
 **/

	if(e.getSource()==fileMapSave)
	{
		SaveMap();
	}

/**
 *
 *	Export map
 *		
 **/
		
		if(e.getSource()==fileSave)
		{
			saveMapData(mapImage.getMapData());
		}
	
	
		if (e.getSource()==fileExit)
		{
			
			tiles.dispose();	
			System.exit(0);
		
		}
		

/**
 *
 *	Show map size dialogue
 *
 **/
		
		if (e.getSource()==mapSize)
		{
			td1.setText(Integer.toString(noBlocksX));
			td2.setText(Integer.toString(noBlocksY));
			sizeDialog.show();
			
		}

/**
 *
 *	show about box
 *
 **/

		if(e.getSource()==helpAbout)
		{
			aboutDialog.show();
		}
		
		
		/*
		
			OK button on size dialogue box
		
		 */
		
		if(e.getSource()==tb1)
		{
			try
			{
				xtmp=Integer.parseInt(td1.getText());
				ytmp=Integer.parseInt(td2.getText());
			
				if(xtmp<=0)
				{
					td1.setText(null);
				}
				
				if(ytmp<=0)
				{
					td2.setText(null);
				}
				
				if(((td1.getText()!=null)&&(td2.getText()!=null)) && ((xtmp>0)&&(ytmp>0)))
				{
					noBlocksX=xtmp;
					noBlocksY=ytmp;
					
					mapImage.changeMapSize(noBlocksX,noBlocksY);
						
					sizeDialog.dispose();
		
					this.setSize(noBlocksX<<mapTileSize,noBlocksY<<mapTileSize);
					mapImage.setSize(noBlocksX<<mapTileSize,noBlocksY<<mapTileSize);
					mainPanel.setSize(noBlocksX<<mapTileSize,noBlocksY<<mapTileSize);
					
					mainPanel.doLayout();
				
					this.pack();
				}
			}
			
			catch(NumberFormatException ex)
			{
				td1.setText(null);
				td2.setText(null);
			}
			
		}
		
		
		/*
		
			Cancel button on size dialogue box
		 */
		
		if(e.getSource()==tb2)
		{

			sizeDialog.dispose();
		}
		
		
		
		if(e.getSource()==aboutButton)
		{
			aboutDialog.dispose();
		}
		
		
		
		//block width textfield
		
		if(e.getSource()==td1)
		{
			try
			{
				xtmp=Integer.parseInt(td1.getText());
					
				if(xtmp<=0)
				{
					td1.setText(null);
				}
						
				if(td1.getText()!=null)
				{
					noBlocksX=xtmp;
				}
			}
				
			catch(NumberFormatException ex)
			{
				td1.setText(null);
			}

		}
		
		//block height textfield
		
		if(e.getSource()==td2)
		{
			try
			{
				ytmp=Integer.parseInt(td2.getText());
					
				if(ytmp<=0)
				{
					td2.setText(null);
				}
						
				if(td2.getText()!=null)
				{
					noBlocksY=ytmp;
				}
			}
				
			catch(NumberFormatException ex)
			{
				td2.setText(null);
			}

		}
	}
	
	
	
	
/*===========================================================*
 *	
 *	ItemEvent() implementation
 *	
 *	Handle changes to the checkboxes and everything else
 *	that uses ItemEvents to determine changes
 *	
 *===========================================================*/
	
	public void itemStateChanged(ItemEvent e)
	{
	
		if(e.getSource()==mapTile8x8)
		{
			mapTile8x8.setState(true);
			mapTile16x16.setState(false);
			mapTile32x32.setState(false);
	
			tiles.setBlockSize(BlockSelector.TILE8x8);
			mapTileSize=BlockSelector.TILE8x8;	
			
		}
	
		if(e.getSource()==mapTile16x16)
		{
			mapTile8x8.setState(false);
			mapTile16x16.setState(true);
			mapTile32x32.setState(false);
	
			tiles.setBlockSize(BlockSelector.TILE16x16);
			mapTileSize=BlockSelector.TILE16x16;					
		}
		
		if(e.getSource()==mapTile32x32)
		{
			mapTile8x8.setState(false);
			mapTile16x16.setState(false);
			mapTile32x32.setState(true);
			
			tiles.setBlockSize(BlockSelector.TILE32x32);
			mapTileSize=BlockSelector.TILE32x32;	
		}
		
		if(e.getSource()==mapFormatPSX)
		{
			mapFormatPSX.setState(true);
			mapFormatGB.setState(false);
		}
		
		if(e.getSource()==mapFormatGB)
		{
			mapFormatPSX.setState(false);
			mapFormatGB.setState(true);
		}
		
		mapImage.setBlockSize(mapTileSize);				//set block size in custom canvas
		
	
	//	etc...
	}
	


/**
 *
 *  Window event handler class
 *
 **/
	
	class WindowEventHandler extends WindowAdapter 
	{
	
	  	public void windowOpened(WindowEvent e) 
		{
		//	mainPanel.doLayout();
		}
	
	  
		public void windowDeiconified(WindowEvent e)
		{
		//	mainPanel.doLayout();
		}
	  
	  
		public void windowClosing(WindowEvent e)
		{
		//	quit=true;
			tiles.dispose();
			System.exit(0);
		}
		
		
	}



/**
 *	
 *	Mouse event handler class    
 *
 **/
 
	class MouseEventHandler extends MouseAdapter
	{
		
		public void mouseEntered(MouseEvent e)
		{
			setCursor(new Cursor(CROSSHAIR_CURSOR));
		}
	
		public void mouseExited(MouseEvent e)
		{
			setCursor(new Cursor(DEFAULT_CURSOR));
		}
		
	

/*===========================================================*
 * Mouse button released handler							 *
 *															 *
 *	On mouse release, put a single block down.  			 *
 *  Inserts the current BlockContainer into the block array. *
 *	Hold down CTRL while releasing button to delete block	 *
 *															 *
 *===========================================================*/
		
		public void mouseReleased(MouseEvent e)
		{	
			Image img;							
			BlockContainer bc=null;
		
			int startX=e.getX()>>mapTileSize;			//get X and Y coords of mouse pointer 
			int startY=e.getY()>>mapTileSize;			//  to nearest integral block size

			img=tiles.locateBlock(tiles.getBlockID());
			bc=new BlockContainer(img,startX<<mapTileSize,startY<<mapTileSize,tiles.getBlockID());
			
			mapImage.putTile(bc,e.isControlDown());			//Put the new BlockContainer in the
														//  array in the map image object for display
		}
	}



/**
 *
 * Mouse motion handler class (moved/dragged handler)
 *
 **/

	class MouseMotionHandler extends MouseMotionAdapter
 	{ 
	 	public void mouseMoved(MouseEvent e)
		{
	
		}
			
		public void mouseDragged(MouseEvent e)
		{
			Image img;
			BlockContainer bc=null;
						
			int startX=e.getX()>>mapTileSize;			//get X and Y coords to nearest 
			int startY=e.getY()>>mapTileSize;			//  integral block size
	
			img=tiles.locateBlock(tiles.getBlockID());
			bc=new BlockContainer(img,startX<<mapTileSize,startY<<mapTileSize,tiles.getBlockID());
							
			mapImage.putTile(bc,e.isControlDown());
		}
	}

}