/*
Tiamat - a role-playing game framework
Sprite.java - animated sprite
 
 Copyright  (C) 2004-2005 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 com.huguesjohnson.ImageLoader;
import com.huguesjohnson.PathResolver;
import com.huguesjohnson.serialization.Formatter;
import com.huguesjohnson.serialization.Storable;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;

/** class Sprite - animated sprite
 * @author Hugues Johnson
 */
public class Sprite extends Object implements Drawable,Storable{
    
/* begin public constants */
    /**
     * DIRECTION constants specify the valid directions a Sprite can travel. 
     * Constant for the up direction.
     */
    public final static int DIRECTION_UP=0;
    /**
     * DIRECTION constants specify the valid directions a Sprite can travel. 
     * Constant for the down direction.
     */
    public final static int DIRECTION_DOWN=1;
    /**
     * DIRECTION constants specify the valid directions a Sprite can travel. 
     * Constant for the left direction.
     */
    public final static int DIRECTION_LEFT=2;
    /**
     * DIRECTION constants specify the valid directions a Sprite can travel. 
     * Constant for the right direction.
     */
    public final static int DIRECTION_RIGHT=3;
    private final static int MAX_DIRECTION=DIRECTION_RIGHT+1;
    private final static boolean DEFAULT_MOVABLE=true;
/* end public constants */
    
/* begin public properties */
    //what direction the sprite is facing, see direction constants
    private int direction;
    /**
     * Returns the direction the sprite is facing, see DIRECTION constants.
     * @return The direction the sprite is facing.
     */
    public int getDirection(){ return(this.direction); }
    /**
     * Sets the direction the sprite is facing, see DIRECTION constants.
     * @param direction The new direction the sprite is facing.
     */
    public void setDirection(int direction){ 
        if(direction!=this.direction){
            this.direction=direction; 
            this.stepIndex=0;
        }
    }
    //what step (frame) of animation is this sprite in
    private int stepIndex;
    /** 
     * Returns the step (frame) of animation is this sprite in.
     * @return The step (frame) of animation is this sprite in.
     */
    public int getStepIndex(){ return(this.stepIndex); }
    /** 
     * Sets the step (frame) of animation is this sprite in.
     * @param stepIndex The new step (frame) of animation is this sprite in.
     */
    public void setStepIndex(int stepIndex){ this.stepIndex=stepIndex; }
    //x-coordinate of this sprite
    private int x;
    /**
     * Returns the x-coordinate of this Sprite.
     * @return The x-coordinate of this Sprite.
     */
    public int getX(){ return(this.x); }
    /**
     * Sets the x-coordinate of this Sprite.
     * @param x The new x-coordinate of this Sprite.
     */
    public void setX(int x){ this.x=x; }
    //y-coordinate of this sprite
    private int y;
    /**
     * Returns the y-coordinate of this Sprite.
     * @return The y-coordinate of this Sprite.
     */
    public int getY(){ return(this.y); }
    /**
     * Sets the y-coordinate of this Sprite.
     * @param y The new y-coordinate of this Sprite.
     */
    public void setY(int y){ this.y=y; }
    //whether this sprite can be moved
    private boolean movable;
    /**
     * Returns whether this Sprite can be moved.
     * @return True if this Spire can be moved.
     */
    public boolean getMovable(){ return(this.movable); }
    /**
     * Sets whether this Sprite can be moved.
     * @param movable Whether this Sprite can be moved.
     */
    public void setMovable(boolean movable){ this.movable=movable; }
    //# of directions x # of steps in each movement animation, [n][0]=standing still image for direction n
    private transient BufferedImage[][] images;
    private String[][] imagePaths; //needed to save sprite, relative to baseImagePath
    /**
     * Returns the paths to the images that comprise this Sprite. Relative to baseImagePath.
     * @return The paths to the images that comprise this Sprite. Relative to baseImagePath.
     */
    public String[][] getImagePaths(){ return(this.imagePaths); }
    /**
     * Returns the absolute paths to the images that comprise this Sprite.
     * @return The absolute paths to the images that comprise this Sprite.
     */
    public String[][] getImagePathsAbsolute(){
        String[][] absolutePaths;
        int length1=this.imagePaths.length;
        absolutePaths=new String[length1][];
        for(int index1=0;index1<length1;index1++){
            int length2=this.imagePaths[index1].length;
            absolutePaths[index1]=new String[length2];
            for(int index2=0;index2<length2;index2++){
                absolutePaths[index1][index2]=new String(PathResolver.getAbsolutePath(this.absoluteDir,this.imagePaths[index1][index2]));
            }
        }        
        return(absolutePaths); 
    }
    /**
     * Returns the current image (frame) of animation for this Sprite.
     * @return The current image (frame) of animation for this Sprite.
     */
    public BufferedImage getCurrentImage(){ return(this.images[this.direction][this.stepIndex]); }
    /**
     * Returns an image (frame) of animation.
     * @param direction The direction of the image.
     * @param step The step index of the image.
     * @return The image at [direction]x[step].
     */
    public BufferedImage getImage(int direction,int step){ return(this.images[direction][step]); }
    /**
     * Sets an image (frame) of animation.
     * @param direction The direction of the image.
     * @param step The step index of the image.
     * @param imagePath The path to the image file, relative to baseImagePath.
     */
    public void setImage(int direction,int step,String imagePath){ 
        this.imagePaths[direction][step]=imagePath; 
        this.images[direction][step]=ImageLoader.loadBufferedImage(this.absoluteDir+imagePath);
    }
    //absolute path to the directory where images are stored
    private transient String absoluteDir;
    /**
     * Sets the absolute path to the directory where images are stored.
     * @param absoluteDir The absolute path to the directory where images are stored.
     */
    public void setAbsoluteDir(String absoluteDir){
        if(absoluteDir.endsWith(File.separator)){
            this.absoluteDir=absoluteDir;
        } else{
            this.absoluteDir=absoluteDir+File.separator;
        }
    }
    //reference to another sprite that follows this sprite
    private transient Sprite follower;
    /**
     * A follower is a reference to another Sprite that follows this Sprite.
     * @return The follower of this Sprite.
     */
    public Sprite getFollower(){ return(this.follower); }
    /**
     * A follower is a reference to another Sprite that follows this Sprite.
     * @param sprite The new follower to this Sprite.
     */
    public void setFollower(Sprite sprite){ this.follower=sprite; }
    //where to draw stuff
    private transient Component canvas;
    /**
     * Returns the canvas where this Sprite is drawn.
     * @return The canvas where this Sprite is drawn.
     */
    public Component getCanvas(){ return(this.canvas); }
    /**
     * Sets the canvas where this Sprite is drawn.
     * @param canvas The canvas where this Sprite is drawn.
     */
    public void setCanvas(Component canvas){ this.canvas=canvas; this.loadImages(); }
    //whether this sprite is currently moving
    private transient boolean isMoving;
    /**
     * Sets whether this sprite is currently moving.
     * @param isMoving Whether this sprite is currently moving.
     */
    public void setIsMoving(boolean isMoving){ this.isMoving=isMoving; }
    /**
     * Returns whether this sprite is currently moving.
     * @return Whether this sprite is currently moving.
     */
    public boolean getIsMoving(){ return(this.isMoving); }
/* end public properties */
    
/* begin constructors */
    /**
     * Creates a new instance of Sprite.
     * @param direction The new direction the sprite is facing.
     * @param x The new x-coordinate of this Sprite.
     * @param y The new y-coordinate of this Sprite.
     * @param absoluteDir The absolute path to the directory where images are stored.
     * @param imagePaths The paths to the images that comprise this Sprite. Relative to baseImagePath.
     * @param canvas The canvas where this Sprite is drawn.
     */
    public Sprite(int direction,int x,int y,String absoluteDir,String[][] imagePaths,Component canvas){
        this.canvas=canvas;
        this.stepIndex=0;
        this.direction=direction;
        this.x=x;
        this.y=y;
        this.follower=null;
        this.movable=DEFAULT_MOVABLE;
        this.setAbsoluteDir(absoluteDir);
        this.importImageData(imagePaths);
    }
/* end constructors */
    
/* begin public methods */
    /** 
     * Imports image data, returns success of the operation.
     * @param imagePaths The paths to the images that comprise this Sprite. Relative to baseImagePath.
     * @return The success of the import operation.
     */
    public boolean importImageData(String[][] imagePaths){
        int length=imagePaths.length;
        this.images=new BufferedImage[length][];
        this.imagePaths=new String[length][];
        for(int index=0;index<length;index++){
            int length1=imagePaths[index].length;
            this.images[index]=new BufferedImage[length1];
            this.imagePaths[index]=new String[length1];
            for(int index1=0;index1<length1;index1++){
                this.imagePaths[index][index1]=imagePaths[index][index1];
            }
        }
        return(this.loadImages());
    }
    /** 
     * Moves the sprite in the specified direction.
     * @param direction The direction to move the Sprite.
     */
    public void move(int direction){
        /* change the step index and direction */
        if(direction!=this.direction){
            this.direction=direction;
            this.stepIndex=0;
        } else{
            this.stepIndex=((this.stepIndex+1)%(this.images[this.direction].length));
        }
        /* change (x,y) */
        switch(direction){
            case DIRECTION_UP:{ this.y--; break; }
            case DIRECTION_DOWN:{ this.y++; break; }
            case DIRECTION_LEFT:{ this.x--; break; }
            case DIRECTION_RIGHT:{ this.x++; break; }
        }
    }
    /* (non-Javadoc)
     * @see com.huguesjohnson.tiamat.graphics.Drawable#draw()
     */
    public void draw(){ 
        Graphics2D g2=(Graphics2D)this.canvas.getGraphics();
        BufferedImage currentImage=this.getCurrentImage();
        int width=currentImage.getWidth(this.canvas);
        int height=currentImage.getHeight(this.canvas);
        g2.drawImage(currentImage,this.x*width,this.y*height,this.canvas);
        if(this.follower!=null){
            this.follower.draw();
        }
    }
    /* (non-Javadoc)
     * @see com.huguesjohnson.tiamat.graphics.Drawable#draw(int, int)
     */
    public void draw(int x,int y){ 
        Graphics2D g2=(Graphics2D)this.canvas.getGraphics();
        BufferedImage currentImage=this.getCurrentImage();
        int width=currentImage.getWidth(this.canvas);
        int height=currentImage.getHeight(this.canvas);
        g2.drawImage(currentImage,x*width,y*height,this.canvas);
        if(this.follower!=null){
            this.follower.draw(x,y);
        }
    }
    /* (non-Javadoc)
     * @see com.huguesjohnson.tiamat.graphics.Drawable#draw(int, int, int, int)
     */
    public void draw(int x1,int y1,int x2,int y2){ 
        Graphics2D g2=(Graphics2D)this.canvas.getGraphics();
        BufferedImage currentImage=this.getCurrentImage();
        int width=x2-x1;
        int height=y2-y1;
        g2.drawImage(currentImage,this.x*width,this.y*height,this.canvas);
        if(this.follower!=null){
            this.follower.draw(x1,y1,x2,y2);
        }
    }
    /** 
     * Returns a string representation of the object.
     * @return A string representation of the object.
     */
    public String toString(){
        StringBuffer tostring=new StringBuffer(this.getClass().getName());
        tostring.append("\n  direction="+this.direction);
        tostring.append("\n  stepIndex="+this.stepIndex);
        tostring.append("\n  x="+this.x);
        tostring.append("\n  y="+this.y);
        tostring.append("\n  y="+this.y);
        tostring.append("\n  absoluteDir="+this.absoluteDir);
        tostring.append("\n  imagePaths:");
        int length=this.imagePaths.length;
        for(int index=0;index<length;index++){
            int length1=this.imagePaths[index].length;
            for(int index1=0;index1<length1;index1++){
                tostring.append("\n    ["+index+"]["+index1+"]="+this.imagePaths[index][index1]);
            }
        }
        return(tostring.toString());
    }
    /** The onDeserialize event should be fired after an object is de-serialized.
     * This allows objects to load transient members, set their working directory, and load Storable members if needed.
     * @param formatter the formatter to use to load any storable members
     * @param absoluteDir the working directory since all paths stored in the object should be relative
     */
    public void onDeserialize(Formatter formatter,String absoluteDir){
        this.setAbsoluteDir(absoluteDir);
        this.loadImages();
        this.setIsMoving(false);
    }
/* end public methods */
    
/* begin private methods */
    private boolean loadImages(){
        int index=0;
        int index1=0;
        boolean success=true;
        int length=this.imagePaths.length;
        if(this.images==null){
            this.images=new BufferedImage[length][];
        }
        for(index=0;index<length;index++){
            int length1=this.imagePaths[index].length;
            if(this.images[index]==null){
                this.images[index]=new BufferedImage[length1];
            }
            for(index1=0;index1<length1;index1++){
                this.images[index][index1]=ImageLoader.loadBufferedImage(this.absoluteDir+this.imagePaths[index][index1]);
                if(this.images[index][index1]==null){
                    success=false;
                }
            }
        }
        return(success);
    }
/* end private methods */
}