/*
Tiamat - a role-playing game framework
AdventureFrame.java - a frame for an adventure style game
 
 Copyright  (C) 2006 Hugues Johnson
 
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
the GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package com.huguesjohnson.tiamat.graphics;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.awt.image.BufferedImage;

import com.huguesjohnson.ImageLoader;
import com.huguesjohnson.tiamat.Event;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.WindowConstants;
import javax.swing.border.LineBorder;

/** AdventureFrame - a frame for an adventure style game
 * @author Hugues Johnson
 */
public abstract class AdventureFrame extends JFrame{

//begin protected members (not part of the Tiamat framework specification)
    //how much text should scroll by
    public final static int VSCROLL=100;
    protected final static int DEFAULT_MAX_SELECTIONS=10;
	protected int maxSelections;
    private JRadioButton[] menu;
    private ButtonGroup buttonGroupSelections;
    private JPanel panelImage;
    private JPanel panelMenu;
    private JScrollPane scrollPaneText;
    private JTextArea textArea;    
//end protected members (not part of the Tiamat framework specification)
	
//begin public properties
	//The full path to the background image.
	private String bgImagePath;
	/** Sets the full path to the background image.
	 * Ths method just calls setBgImagePath(bgImagePath,true).
	 * @param bgImagePath The full path to the background image.
	 */
	public void setBgImagePath(String bgImagePath){
		this.setBgImagePath(bgImagePath,true);
	}
	/** Returns the full path to the background image.
	 * @return The full path to the background image.
	 */
	protected String getBgImagePath(){
		return(this.bgImagePath);
	}
	//objects to be drawn in the object plane
	private ImageData[] objects;
	/** Sets the objects to be drawn in the object plane.
	 * Ths method just calls setObjects(objects,true).
	 * @param objects The objects to be drawn in the object plane.
	 */
	public void setObjects(ImageData[] objects){
		this.setObjects(objects,true);
	}
	/** Returns the objects to be drawn in the object plane.
	 * @return The objects to be drawn in the object plane.
	 */
	protected ImageData[] getObjects(){
		return(this.objects);
	}
	//objects to be drawn in the overlay plane
	private ImageData[] overlays;
	/** Sets the objects to be drawn in the overlay plane.
	 * Ths method just calls setOverlays(overlays,true).
	 * @param overlays The objects to be drawn in the overlay plane.
	 */
	public void setOverlays(ImageData[] overlays){
		this.setOverlays(overlays,true);
	}
	/** Returns the objects to be drawn in the overlay plane.
	 * @return The objects to be drawn in the overlay plane.
	 */
	protected ImageData[] getOverlays(){
		return(this.overlays);
	}
	//the Theme to apply to the frame
	private Theme theme;
	/** Sets the Theme to apply to the frame.
	 * @param theme The Theme to apply to the frame.
	 */
	public void setTheme(Theme theme){
		this.theme=theme;
		this.applyThemeAndFont(this);
	}
	//the Font to use for text
	private Font font;
	/** Sets the Font to use for text.
	 * @param font The Font to apply to the frame.
	 */
	public void setFont(Font font){
		this.font=font;
		this.applyThemeAndFont(this);
	}
	//the selections that appear in the menu
	private Selection[] selections;
	/** Sets the selections that appear in the menu.
	 * @param selections The new selections for the menu.
	 */
	public void setSelections(Selection[] selections){
		int length=selections.length;
		this.selections=new Selection[length];
		System.arraycopy(selections,0,this.selections,0,length);
		this.redrawSelections();
	}
	/** Returns the selections that appear in the menu.
	 * @return The selections that appear in the menu.
	 */
	public Selection[] getSelections(){
		return(this.selections);
	}
	//the text drawn in the dialog region
    /** Sets the text in the lower text pane.
     * @param text The text to draw in the lower text pane.
     */
    public void setText(String text){
        //draw the text
        this.textArea.setText(text);
        //scroll to the top
        this.textArea.setCaretPosition(0);
        this.scrollPaneText.getViewport().setViewPosition(new Point(0,0));
    }
    /** Returns the text in the lower text pane.
     * @return The text in the lower text pane.
     */
    public String getText(){
    	return(this.textArea.getText());
    }
//end public properties
	
//begin constructors
	/** Create a new instance of AdventureFrame.
	 * @param title The title of the frame.
	 * @param theme The Theme to apply to the frame.
	 * @param font The Font to apply to the frame.
	 */
	public AdventureFrame(String title,Theme theme,Font font){
		this.theme=theme;
		this.font=font;
		this.maxSelections=DEFAULT_MAX_SELECTIONS;
		this.initComponents();
		this.setTitle(title);
        this.applyThemeAndFont(this);
        this.centerWindow();
        this.initMenu();
	}
	/** Create a new instance of AdventureFrame.
	 * @param title The title of the frame.
	 * @param theme The Theme to apply to the frame.
	 * @param font The Font to apply to the frame.
	 * @param maxSelections the maximum number of items the menu can hold.
	 */
	public AdventureFrame(String title,Theme theme,Font font,int maxSelections){
		this.theme=theme;
		this.font=font;
		this.maxSelections=maxSelections;
        this.initComponents();
		this.setTitle(title);
        this.applyThemeAndFont(this);
        this.centerWindow();
        this.initMenu();
	}
//end constructors
	
//begin public methods
	/** Sends an Event back to the implementing class.
	 * @param event The Event invoked by the user.
	 */
	public abstract void postEvent(Event event);
    
	/** Event that fires when the frame is closed.
     * Override this if using this frame from an external application. 
     */
    public abstract void onFrameClose();
	
    /** Sets background image.
	 * The default setImagePath method calls this passing true for redraw parameter.
	 * @param bgImagePath The path to the new background image.
	 * @param redraw Whether to redraw the image section of the frame.
	 */
	public void setBgImagePath(String bgImagePath,boolean redraw){
		this.bgImagePath=new String(bgImagePath);
		if(redraw){
			this.redrawImages();
		}
	}
	
    /** Redraw the images.
     */
    public void redrawImages(){
    	//TODO - there's probably a better way to this
    	Graphics2D g2=(Graphics2D)this.panelImage.getGraphics();
    	//draw the background
    	BufferedImage image=ImageLoader.loadBufferedImage(this.bgImagePath);
    	if(image!=null){
    		g2.drawImage(image,0,0,this.panelImage);
    	}
    	//draw the objects
    	if(this.objects!=null){
    		int length=this.objects.length;
    		for(int index=0;index<length;index++){
    	    	image=ImageLoader.loadBufferedImage(this.objects[index].getImagePath());
    	    	if(image!=null){
    	    		g2.drawImage(image,this.objects[index].getX(),this.objects[index].getY(),this.panelImage);
    	    	}
    		}
    	}
    	//draw the overlays
    	if(this.overlays!=null){
    		int length=this.overlays.length;
    		for(int index=0;index<length;index++){
    	    	image=ImageLoader.loadBufferedImage(this.overlays[index].getImagePath());
    	    	if(image!=null){
    	    		g2.drawImage(image,this.overlays[index].getX(),this.overlays[index].getY(),this.panelImage);
    	    	}
    		}
    	}
    }	
	
	/** Sets the object images.
	 * The default setObjects method calls this passing true for redraw parameter.
	 * @param objects The new objects to draw.
	 * @param redraw Whether to redraw the image section of the frame.
	 */
	public void setObjects(ImageData[] objects,boolean redraw){
		int length=objects.length;
		this.objects=new ImageData[length];
		System.arraycopy(objects,0,this.objects,0,length);
		if(redraw){
			this.redrawImages();
		}
	}
	
	/** Sets the overlay images.
	 * The default setOverlays method calls this passing true for redraw parameter.
	 * @param overlays The new objects to draw.
	 * @param redraw Whether to redraw the image section of the frame.
	 */
	public void setOverlays(ImageData[] overlays,boolean redraw){
		int length=overlays.length;
		this.overlays=new ImageData[length];
		System.arraycopy(overlays,0,this.overlays,0,length);
		if(redraw){
			this.redrawImages();
		}
	}
//end public methods
	
//begin private methods (UI methods/not part of the Tiamat framework specification)
	/** Applies the font and theme to the specified component and its child components.
	 * @param component The component to apply the theme to.
	 */
	private void applyThemeAndFont(Component component){
        component.setBackground(this.theme.getColorScheme().getBackColor());
        component.setForeground(this.theme.getColorScheme().getForeColor());
        component.setFont(this.font);
        //if component is a container, call applyTheme for its children
        if(component instanceof Container){
            Container container=(Container)component;
            int count=container.getComponentCount();
            if(count>0){
                Component[] children=container.getComponents();
                for(int index=0;index<count;index++){
                    this.applyThemeAndFont(children[index]);
                }
            }
            //set the border colors, not the best way to do this
            if(container instanceof JPanel){
                JPanel panel=(JPanel)container;
                //replace the border for anything with a border
                if(panel.getBorder()!=null){
                    panel.setBorder(new LineBorder(this.theme.getColorScheme().getBorderColor(),4,true));
                }
            } else if(container instanceof JScrollPane){
                JScrollPane scrollPane=(JScrollPane)container;
                //replace the border for anything with a border
                if(scrollPane.getBorder()!=null){
                    scrollPane.setBorder(new LineBorder(this.theme.getColorScheme().getBorderColor(),4,true));
                }
            }
        }
        //if component is a JRadioButton, set the button images
        if(component instanceof JRadioButton){
            JRadioButton radioButton=(JRadioButton)component;
            BufferedImage image=this.theme.getRadioButtonImage();
            if(image!=null){
                radioButton.setIcon(new ImageIcon(image));
            }
            image=this.theme.getRadioButtonSelectedImage();
            if(image!=null){            
                radioButton.setSelectedIcon(new ImageIcon(image));
            }
        } else if(component instanceof JTextArea){
            JTextArea textArea=(JTextArea)component;
            textArea.setDisabledTextColor(this.theme.getColorScheme().getForeColor());
        }
	}
    
	/** Clears all items from the menu.
	 */
	private void clearSelections(){
		int length=this.menu.length;
		for(int index=0;index<length;index++){
            this.menu[index].setVisible(false);
        }
     }

	/** Redraws the menu with the current Selections.
	 */
	private void redrawSelections(){
		this.clearSelections();
		Selection[] selections=this.getSelections();
        if(selections!=null){
           int length=selections.length;
           if(length>0){
               this.selections=new Selection[length];
               System.arraycopy(selections,0,this.selections,0,length);
               for(int index=0;index<length;index++){
                   this.menu[index].setText(selections[index].getCaption());
                   this.menu[index].setVisible(true);
               }
               this.menu[0].setSelected(true);
               this.menu[0].requestFocus();
           }
        }
	}
	
	/** Initializes the menu.
	 */
    private void initMenu(){
        int length=this.maxSelections;
        this.selections=new Selection[length];
        this.menu=new JRadioButton[length];
        double maxHeight=(this.panelMenu.getMaximumSize().getHeight()/length)*.9;
        double maxWidth=this.panelMenu.getMaximumSize().getWidth()*.9;
        for(int index=0;index<length;index++){
            this.menu[index]=new JRadioButton("");
            this.menu[index].setMaximumSize(new Dimension((int)maxWidth,(int)maxHeight));
            this.menu[index].setMinimumSize(new Dimension((int)maxWidth,(int)maxHeight));
            this.menu[index].setPreferredSize(new Dimension((int)maxWidth,(int)maxHeight));
            this.menu[index].setBorderPainted(false);
            this.menu[index].setFocusPainted(false);
            this.menu[index].addKeyListener(new KeyListener(){
                public void keyPressed(KeyEvent keyEvent){ handleKeyEvent(keyEvent.getKeyCode()); }
                public void keyReleased(KeyEvent keyEvent){ }
                public void keyTyped(KeyEvent keyEvent){ }
            });
            this.menu[index].setBackground(this.theme.getColorScheme().getBackColor());
            this.menu[index].setForeground(this.theme.getColorScheme().getForeColor());
            this.menu[index].setFont(this.font);
            BufferedImage image=this.theme.getRadioButtonImage();
            if(image!=null){
                this.menu[index].setIcon(new ImageIcon(image));
            }
            image=this.theme.getRadioButtonSelectedImage();
            if(image!=null){            
                this.menu[index].setSelectedIcon(new ImageIcon(image));
            }
            this.buttonGroupSelections.add(this.menu[index]);
            this.panelMenu.add(this.menu[index]);            
        }
        this.menu[0].setSelected(true);
    }
	
    /** Returns the index of the selected item in the menu.
     * @return The index of the selected item in the menu
     */
    private int getSelectionIndex(){
        int selectionIndex=-1;
        if(this.menu!=null){
            int length=this.menu.length;
            int index=0;
            while((index<length)&&(selectionIndex==-1)){
                if(this.menu[index].isSelected()){
                    selectionIndex=index;
                } else{
                    index++;
                }
            }
        }
        return(selectionIndex);
    }        
    
    /** Handles key events.
     * @param keyCode The code for the key that was pressed.
     */
    public void handleKeyEvent(int keyCode){
        switch(keyCode){
            case KeyEvent.VK_PAGE_UP:{
                //scroll text up
                JViewport viewport=this.scrollPaneText.getViewport();
                Point currentView=viewport.getViewPosition();
                int cx=(int)currentView.getX();
                int cy=(int)currentView.getY();
                int nx=cx;
                int ny=cy-VSCROLL;
                if(ny<0){ 
                    ny=0; 
                }
                if(ny<cy){
                    Point viewPosition=new Point(nx,ny);
                    viewport.setViewPosition(viewPosition);
                }
                break;                
            }
            case KeyEvent.VK_PAGE_DOWN:{
                //scroll text down
                JViewport viewport=this.scrollPaneText.getViewport();
                Point currentView=viewport.getViewPosition();
                int cx=(int)currentView.getX();
                int cy=(int)currentView.getY();
                int nx=cx;
                int ny=cy+VSCROLL;
                int textAreaHeight=(int)this.textArea.getSize().getHeight();
                int scrollPaneHeight=(int)this.scrollPaneText.getSize().getHeight();
                int bottom=this.scrollPaneText.getInsets().bottom;
                int top=this.scrollPaneText.getInsets().top;
                scrollPaneHeight-=(top+bottom);
                int maxy=textAreaHeight-scrollPaneHeight;
                if(ny>maxy){ 
                    ny=maxy; 
                }
                if(ny>cy){
                    Point viewPosition=new Point(nx,ny);
                    viewport.setViewPosition(viewPosition);
                }
                break;
            }
            case KeyEvent.VK_UP:{
                int selectedIndex=this.getSelectionIndex();
                if(selectedIndex>0){
                    this.menu[selectedIndex-1].setSelected(true);
                } else { //scroll back to the bottom
                    int last=this.menu.length-1;
                    boolean found=false;
                    while((last>selectedIndex)&&(!found)){
                        if(this.menu[last].isVisible()){
                            this.menu[last].setSelected(true);
                            found=true;
                        }
                        last--;
                    }
                }
                break;
            }
            case KeyEvent.VK_DOWN:{
                int length=this.menu.length-1;
                int selectedIndex=this.getSelectionIndex();
                if((selectedIndex<length)&&(this.menu[selectedIndex+1].isVisible())){
                    this.menu[selectedIndex+1].setSelected(true);
                } else{ //scroll back to the top
                    if(this.menu[0].isVisible()){
                        this.menu[0].setSelected(true);
                    }
                }
                break;
            }
            case KeyEvent.VK_ENTER:{
                int selectedIndex=this.getSelectionIndex();
                if((selectedIndex>=0)&&(selectedIndex<this.selections.length)){
                    this.postEvent(this.selections[selectedIndex].getEvent());
                }
                break;
            }
        }
    }
    
    /** Initializes the frame.
     */
    private void initComponents(){
        //set the layout
    	this.getContentPane().setLayout(new BoxLayout(getContentPane(),BoxLayout.Y_AXIS));
    	this.setResizable(false);
    	//setup window listeners
    	this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        this.addKeyListener(new KeyAdapter() {
            public void keyTyped(KeyEvent evt){
                handleKeyEvent(evt.getKeyCode());
            }
        });
        this.addWindowListener(new WindowAdapter(){
        	public void windowClosing(WindowEvent evt){
        		onFrameClose();
            }
        });
        this.addWindowFocusListener(new WindowFocusListener(){
            @SuppressWarnings("synthetic-access")
			public void windowGainedFocus(WindowEvent evt){
                redrawImages();
            }
            public void windowLostFocus(WindowEvent evt){ }
        });

        //setup the top panel
        JPanel panelTop=new JPanel();
        panelTop.setLayout(new BoxLayout(panelTop,BoxLayout.X_AXIS));
        //add the image panel
        this.panelImage=new JPanel();
        this.panelImage.setMaximumSize(new java.awt.Dimension(260,196));
        this.panelImage.setMinimumSize(new java.awt.Dimension(260,196));
        this.panelImage.setPreferredSize(new java.awt.Dimension(260,196));
        panelTop.add(this.panelImage);
        //add a vertical spacer
        JPanel panelVSpacer=new JPanel();
        panelVSpacer.setMaximumSize(new Dimension(4,4));
        panelVSpacer.setMinimumSize(new Dimension(4,4));
        panelVSpacer.setPreferredSize(new Dimension(4,4));
        panelTop.add(panelVSpacer);
        //add the menu
        this.panelMenu=new JPanel();
        this.panelMenu.setLayout(new BoxLayout(this.panelMenu,BoxLayout.Y_AXIS));
        this.panelMenu.setBorder(new LineBorder(new Color(0,0,0),4, true));
        this.panelMenu.setMaximumSize(new Dimension(160,196));
        this.panelMenu.setMinimumSize(new Dimension(160,196));
        this.panelMenu.setPreferredSize(new Dimension(160,196));
        panelTop.add(this.panelMenu);
        this.getContentPane().add(panelTop);

        //add a horizontal spacer
        JPanel panelHSpacer=new JPanel();
        panelHSpacer.setMaximumSize(new Dimension(4,4));
        panelHSpacer.setMinimumSize(new Dimension(4,4));
        panelHSpacer.setPreferredSize(new Dimension(4,4));
        this.getContentPane().add(panelHSpacer);

        //add the bottom panel
        JPanel panelBottom=new JPanel();
        panelBottom.setLayout(new BoxLayout(panelBottom,BoxLayout.X_AXIS));
        //add the text area
        this.scrollPaneText=new JScrollPane();
        this.scrollPaneText.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        this.scrollPaneText.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
        this.scrollPaneText.setFocusable(false);
        this.scrollPaneText.setMinimumSize(new Dimension(24,120));
        this.scrollPaneText.setPreferredSize(new Dimension(4,120));
        this.scrollPaneText.setRequestFocusEnabled(false);
        this.scrollPaneText.setWheelScrollingEnabled(false);
        this.scrollPaneText.setEnabled(false);
        this.scrollPaneText.addKeyListener(new KeyAdapter(){
        	public void keyTyped(KeyEvent evt){
                handleKeyEvent(evt.getKeyCode());
        	}
        });
        this.textArea=new JTextArea();
        this.textArea.setEditable(false);
        this.textArea.setLineWrap(true);
        this.textArea.setWrapStyleWord(true);
        this.textArea.setFocusable(false);
        this.textArea.setMaximumSize(new Dimension(2147483647,120));
        this.textArea.setRequestFocusEnabled(false);
        this.textArea.setAutoscrolls(false);
        this.textArea.setEnabled(false);
        this.textArea.addKeyListener(new KeyAdapter(){
            public void keyTyped(KeyEvent evt){
                handleKeyEvent(evt.getKeyCode());
            }
        });
        this.scrollPaneText.setViewportView(this.textArea);
        panelBottom.add(this.scrollPaneText);
        this.getContentPane().add(panelBottom);
        
        //pack
        this.pack();
    }
    

    
    /** Centers the window on the screen.
     */
    private void centerWindow(){
        Dimension screenDimension=Toolkit.getDefaultToolkit().getScreenSize();
        Rectangle windowDimension=this.getBounds();
        this.setLocation((screenDimension.width-windowDimension.width)/2,(screenDimension.height-windowDimension.height)/2);
   }	
//end private methods (UI methods/not part of the Tiamat framework specification)
    
//begin public overrides (UI methods/not part of the Tiamat framework specification)
    /* (non-Javadoc)
     * @see java.awt.Component#paint(java.awt.Graphics)
     */
    public void paint(Graphics g){
        super.paint(g);
        this.redrawImages();
     }
//end public overrides (UI methods/not part of the Tiamat framework specification)
}