import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Toolkit;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.BufferedWriter;
import java.io.FileWriter;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.UIManager;

/**
 * MaskBuilder - used to create masks for 64 by 32 tiles for RPGX
 * 
 * @author Scott Douglas (scott@scottdouglas.net)
 */
public class MaskBuilder extends JFrame implements ActionListener {
	public static final int TILE_WIDTH = 64;
	public static final int TILE_HEIGHT = 32;
	
	private Image bgImage;
	private boolean[][] walkon;
	private int clickDownRow;
	private int clickDownColumn;
	private int mouseDraggedRow;
	private int mouseDraggedColumn;
	private String type;
	private JPanel maskPanel;
	
	public MaskBuilder() {
		type = "on";
		bgImage = null;
		clickDownRow = -1;
		clickDownColumn = -1;
		mouseDraggedRow = -1;
		mouseDraggedColumn = -1;
		walkon = new boolean[ TILE_WIDTH ][ TILE_HEIGHT ];
		for ( int i = 0; i < TILE_WIDTH; i++ ) {
			for ( int k = 0; k < TILE_HEIGHT; k++ ) {
				walkon[ i ][ k ] = true;
			}
		}
		initializeGUI();
	}
	
	private void initializeGUI() {
		setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		setTitle( "Mask Builder" );
		
		// Create menubar
		JMenuBar menuBar = new JMenuBar();
		
		JMenu fileMenu = new JMenu( "File" );
		JMenuItem loadItem = new JMenuItem( "Load Image..." );
		loadItem.addActionListener( this );
		loadItem.setActionCommand( "loadimage" );
		JMenuItem outputItem = new JMenuItem( "Output Mask" );
		outputItem.addActionListener( this );
		outputItem.setActionCommand( "output" );
		JMenuItem quitItem = new JMenuItem( "Quit" );
		quitItem.addActionListener( this );
		quitItem.setActionCommand( "quit" );
		
		menuBar.add( fileMenu );
		fileMenu.add( loadItem );
		fileMenu.addSeparator();
		fileMenu.add( outputItem );
		fileMenu.addSeparator();
		fileMenu.add( quitItem );
		setJMenuBar( menuBar );
		
		// Create panel
		maskPanel = new JPanel() {
			public void paint( Graphics g ) {
				super.paint( g );
				
				// Draw the background
				Graphics2D g2d = ( Graphics2D ) g;
				Composite old = g2d.getComposite();
				AlphaComposite alc = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.5f );
				g2d.setComposite( alc ); 
				if ( bgImage != null ) {
					g.drawImage( MaskBuilder.this.bgImage, 
							     0, 
							     0, 
							     getWidth(), 
							     getHeight(), 
							     Color.black, 
							     this );
				}
				g2d.setComposite( old );
				
				// Draw lines
				for ( int column = 0; column <= TILE_WIDTH; column++ ) {
					double ratio = ( double ) column / TILE_WIDTH;
					g.drawLine( ( int ) ( ratio * getWidth() ), 
								0, 
								( int ) ( ratio * getWidth() ), 
								getHeight() );
				}
				
				for ( int row = 0; row <= TILE_HEIGHT; row++ ) {
					double ratio = ( double ) row / TILE_HEIGHT;
					g.drawLine( 0, 
							    ( int ) ( ratio * getHeight() ), 
							    getWidth(), 
							    ( int ) ( ratio * getHeight() ) );
				}
				
				// Draw active tiles
				g2d.setComposite( alc ); 
				 
				for ( int column = 0; column <= TILE_WIDTH; column++ ) {
					for ( int row = 0; row <= TILE_HEIGHT; row++ ) {
						// If you can't walk on it, color it
						if ( column < TILE_WIDTH && row < TILE_HEIGHT && MaskBuilder.this.walkon[ column ][ row ] == false ) {
							double columnRatio = ( double ) column / TILE_WIDTH;
							double rowRatio = ( double ) row / TILE_HEIGHT;
							int columnWidth = ( int ) ( ( double ) getWidth() / TILE_WIDTH );
							int rowHeight = ( int ) ( ( double ) getHeight() / TILE_HEIGHT );
							
							int x1 = ( int ) ( columnRatio * getWidth() );
							int y1 = ( int ) ( rowRatio * getHeight() );

							g2d.fillRect( x1, y1, columnWidth + 1, rowHeight + 1 );
						}
					}
				}
				
				// Draw tiles about to change
				if ( clickDownRow != -1 && clickDownColumn != -1 ) {
					g.setColor( Color.red );
					int currentColumn = fitColumnInWindow( mouseDraggedColumn );
					int currentRow = fitRowInWindow( mouseDraggedRow );
					int initialColumn = fitColumnInWindow( clickDownColumn );
					int initialRow = fitRowInWindow( clickDownRow );
					if ( initialColumn > currentColumn ) {
						int temp = initialColumn;
						initialColumn = currentColumn;
						currentColumn = temp;
					}
					if ( initialRow > currentRow ) {
						int temp = initialRow;
						initialRow = currentRow;
						currentRow = temp;						
					}
					for ( int i = initialColumn; i <= currentColumn; i++ ) {
						for ( int j = initialRow; j <= currentRow; j++ ) {
							double columnRatio = ( double ) i / TILE_WIDTH;
							double rowRatio = ( double ) j / TILE_HEIGHT;
							int columnWidth = ( int ) ( ( double ) getWidth() / TILE_WIDTH );
							int rowHeight = ( int ) ( ( double ) getHeight() / TILE_HEIGHT );
								
							int x1 = ( int ) ( columnRatio * getWidth() );
							int y1 = ( int ) ( rowRatio * getHeight() );
	
							g2d.fillRect( x1, y1, columnWidth + 1, rowHeight + 1 );						
						}
					}
				}	
			}
		};
		
		// Provide user input
		maskPanel.addMouseListener( 
			new MouseAdapter() {
				public void mousePressed( MouseEvent e ) {
					clickDownRow = convertToRow( e.getY() );
					clickDownColumn = convertToColumn( e.getX() );	
				}
				
				public void mouseReleased( MouseEvent e ) {
					int column = convertToColumn( e.getX() );
					int row = convertToRow( e.getY() );
					
					column = fitColumnInWindow( column );
					row = fitRowInWindow( row );
					int downColumn = fitColumnInWindow( clickDownColumn );
					int downRow = fitRowInWindow( clickDownRow );
					
					if ( downColumn > column ) {
						int temp = downColumn;
						downColumn = column;
						column = temp;
					}
					if ( downRow > row ) {
						int temp = downRow;
						downRow = row;
						row = temp;						
					}
					for ( int i = downRow; i <= row; i++ ) {
						for ( int j = downColumn; j <= column; j++ ) {
							if ( type.equals( "invert" ) ) {
								MaskBuilder.this.walkon[ j ][ i ] = !MaskBuilder.this.walkon[ j ][ i ];
							} else if ( type.equals( "on" ) ) {
								MaskBuilder.this.walkon[ j ][ i ] = false;
							} else if ( type.equals( "off" ) ) {
								MaskBuilder.this.walkon[ j ][ i ] = true;
							}
						}
					}
					
					clickDownRow = -1;
					clickDownColumn = -1;
					
					repaint();
				}
			} 
		);
		maskPanel.addMouseMotionListener( 
			new MouseMotionAdapter() {
				public void mouseDragged( MouseEvent e ) {
					mouseDraggedRow = convertToRow( e.getY() );
					mouseDraggedColumn = convertToColumn( e.getX() );	
					repaint();					
				}
			} 
		);
		Dimension size = new Dimension( 640, 320 );
		maskPanel.setPreferredSize( size );
		maskPanel.setMaximumSize( size );
		maskPanel.setMinimumSize( size );

		// Create selection type panel
		JPanel typePanel = new JPanel( new GridLayout( 1, 3 ) );
		ButtonGroup group = new ButtonGroup();
		JRadioButton offButton = new JRadioButton( "Off" );
		offButton.setActionCommand( "off" );
		offButton.addActionListener( this );
		offButton.setHorizontalAlignment( AbstractButton.CENTER );
		JRadioButton onButton = new JRadioButton( "On" );
		onButton.setActionCommand( "on" );
		onButton.addActionListener( this );
		onButton.setHorizontalAlignment( AbstractButton.CENTER );
		JRadioButton invertButton = new JRadioButton( "Invert" );
		invertButton.setActionCommand( "invert" );
		invertButton.addActionListener( this );
		invertButton.setHorizontalAlignment( AbstractButton.CENTER );		
		group.add( offButton );
		group.add( onButton );
		group.add( invertButton );
		typePanel.add( onButton );
		typePanel.add( offButton );
		typePanel.add( invertButton );
		typePanel.setBorder( BorderFactory.createLineBorder( Color.black ) );
		onButton.setSelected( true );	
		
		JPanel holder = new JPanel( new BorderLayout() );
		holder.add( typePanel, BorderLayout.SOUTH );
		holder.add( maskPanel, BorderLayout.CENTER );
		setContentPane( holder );
		
		pack();
		setVisible( true );
	}
	
	public void actionPerformed( ActionEvent event ) {
		String command = event.getActionCommand();
		
		if ( command.equals( "quit" ) ) {
			System.exit( 0 );
		} else if ( command.equals( "loadimage" ) ) {
			JFileChooser fileChooser = new JFileChooser();
			int returnVal = fileChooser.showOpenDialog( this );
			if( returnVal == JFileChooser.APPROVE_OPTION ) {
				// Try to load it as an image
				bgImage = Toolkit.getDefaultToolkit().createImage( fileChooser.getSelectedFile().getAbsolutePath() );
				repaint();
			}
		} else if ( command.equals( "output" ) ) {
			try {
				BufferedWriter output = new BufferedWriter( new FileWriter( "mask.txt" ) );
				
				for ( int row = 0; row < TILE_HEIGHT; row++ ) {
					for ( int column = 0; column < TILE_WIDTH; column++ ) {
						if ( walkon[ column ][ row ] ) {
							output.write( "1" );	
						} else {
							output.write( "0" );
						}
					}
					if ( row != TILE_HEIGHT - 1 ) {
						output.write( '\n' );
					}
				}
				
				output.close();
			} catch ( Exception e ) {
				JOptionPane.showMessageDialog( this, "Could not save mask to file.\nPlease try again later.", "Error:", JOptionPane.ERROR_MESSAGE );
			}
		} else if ( command.equals( "on" ) ) {
			type = "on";
		} else if ( command.equals( "off" ) ) {
			type = "off";
		} else if ( command.equals( "invert" ) ) {
			type = "invert";
		}
	}
	
	private int convertToColumn( int x ) {
		double columnWidth = ( double ) maskPanel.getWidth() / TILE_WIDTH;
		return ( int ) ( x / columnWidth );
	}
	
	private int convertToRow( int y ) {
		double rowHeight = ( double ) maskPanel.getHeight() / TILE_HEIGHT;
		return ( int ) ( y / rowHeight );
	}
	
	private int fitColumnInWindow( int column ) {
		if ( column < 0 ) {
			column = 0;
		} else if ( column >= TILE_WIDTH ) {
			column = TILE_WIDTH - 1; 
		}
		
		return column;
	}
	
	private int fitRowInWindow( int row ) {
		if ( row < 0 ) {
			row = 0;
		} else if ( row >= TILE_HEIGHT ) {
			row = TILE_HEIGHT - 1; 
		}
		
		return row;		
	}
	
	public static void main( String[] args ) {
		try {
			UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName());
		} catch ( Exception e ) {
			// Don't do anything...o well..not a big deal
		}
		new MaskBuilder();
	}
}

