/*
Tiamat - a role-playing game framework
TileMap.java - a map composed of fixed-size tiles
 
 Copyright  (C) 2004 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 java.awt.Component;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

/** TileMap - a map composed of fixed-size tiles
 * @author Hugues Johnson
 */
public class TileMap extends Map{
    
/* begin public properties */
    //set of tiles for this map
    private String[] tilePaths; //relative paths, needed to save location of tiles
    private transient BufferedImage[] tileSet;
    /** Returns the tile set for this map. 
     * @return The tile set for this map.
     */
    public BufferedImage[] getTileSet(){ return(this.tileSet); }
    /** Returns the tile at the specified index. 
     * @param index The index of the time to return.    
     * @return The tile at the specified index.
     */
    public BufferedImage getTile(int index){ return(this.tileSet[index]); }
    //data for this map
    private TileMapData[][] mapData;
    /** Returns the map data at the specified (x,y) co-ordinate.
     * @param x The x co-ordinate.
     * @param y The y co-ordinate.
     * @return The map data at (x,y).
     */
    public TileMapData getMapData(int x,int y){ return(this.mapData[x][y]); }
    /** Sets the map data at the specified (x,y) co-ordinate.
     * @param x The x co-ordinate.
     * @param y The y co-ordinate.
     * @param mapData The data to set at (x,y).
     */
    public void setMapData(int x,int y,TileMapData mapData){ this.mapData[x][y]=mapData; }
    /** Override of setCanvas to reload images when canvas changes. 
     * @param canvas The new canvas for this map.
     */
    public void setCanvas(Component canvas){ 
        super.setCanvas(canvas);
        this.loadImages();
    }
/* end public properties */
        
/* begin constructors */
    /** Default constructor, creates an empty map. 
     */
    public TileMap(){ }
    /** Create a map with the specified width, height, and canvas. 
     * @param width The width for this map.
     * @param height The height for this map.
     * @param canvas The canvas for this map.
     */
    public TileMap(int width,int height,Component canvas){
        this.setCanvas(canvas);
        this.setWidth(width);
        this.setHeight(height);
        this.mapData=new TileMapData[width][height];
        for(int index=0;index<width;index++){
            for(int index1=0;index1<height;index1++){
                this.mapData[index][index1]=new TileMapData(0);
            }
        }
    }
/* end constructors */
    
/* begin public methods */
    /** Imports tileset data. 
     * @param absoluteDir The absolute path to where images are stored.
     * @param tilePaths An array of relative paths to images.
     * @return Sucess of the import.
     */
    public boolean importTileSet(String absoluteDir,String[] tilePaths){
        boolean success=true;
        this.setAbsoluteDir(absoluteDir);
        int length=tilePaths.length;
        this.tilePaths=new String[length];
        this.tileSet=new BufferedImage[length];
        for(int index=0;index<length;index++){
            this.tilePaths[index]=tilePaths[index];
        }
        /* has everything worked so far? if so, load images into memory */
        if(success){ success=this.loadImages(); }
        return(success);
    }
    /** Imports tile data. 
     * @param tileData An array of tile data.
     * @return Sucess of the import.
     */
    public boolean importTileData(int[][] tileData){
        boolean success=true;
        try{
            for(int x=0;x<this.width;x++){
                for(int y=0;y<this.height;y++){
                    this.mapData[x][y].setTile(tileData[x][y]);
                }
            }
        } catch(ArrayIndexOutOfBoundsException aioobx){
            aioobx.printStackTrace();
            success=false;
        }
        return(success);
    }
    /** Imports type data. 
     * @param typeData An array of type data.
     * @return Sucess of the import.
     */
    public boolean importTypeData(int[][] typeData){
        boolean success=true;
        try{
            for(int x=0;x<this.width;x++){
                for(int y=0;y<this.height;y++){
                    this.mapData[x][y].setType(typeData[x][y]);
                }
            }
        } catch(ArrayIndexOutOfBoundsException aioobx){
            aioobx.printStackTrace();
            success=false;
        }
        return(success);
    }
    /** Imports object data. 
     * @param objectData An array of object data.
     * @return Sucess of the import.
     */
    public boolean importObjectData(Object[][] objectData){
        boolean success=true;
        try{
            for(int x=0;x<this.width;x++){
                for(int y=0;y<this.height;y++){
                    this.mapData[x][y].setObject(objectData[x][y]);
                }
            }
        } catch(ArrayIndexOutOfBoundsException aioobx){
            aioobx.printStackTrace();
            success=false;
        }
        return(success);
    }
    /** Draws the entire map. 
     */
    public void draw(){
        Graphics2D g2=(Graphics2D)this.getCanvas().getGraphics();
        /* assume all tiles are the same size */
        int tileWidth=this.tileSet[0].getWidth(this.getCanvas());
        int tileHeight=this.tileSet[0].getHeight(this.getCanvas());
        /* draw the map */
        for(int x=0;x<this.width;x++){
            for(int y=0;y<this.height;y++){
                TileMapData tileMapData=this.getMapData(x,y);
                int tileIndex=tileMapData.getTile();
                if(tileIndex>=0){
                    g2.drawImage(this.getTile(tileIndex),x*tileWidth,y*tileHeight,tileWidth,tileHeight,null);
                }
            }
        }
    }
    /** Draw the tile at (x,y).
     * @param x The x co-ordinate.
     * @param y The y co-ordinate.
     */
    public void draw(int x,int y){
        Graphics2D g2=(Graphics2D)this.getCanvas().getGraphics();
        /* assume all tiles are the same size */
        int tileWidth=this.tileSet[0].getWidth(this.getCanvas());
        int tileHeight=this.tileSet[0].getHeight(this.getCanvas());
        /* draw the tile */
        TileMapData tileMapData=this.getMapData(x,y);
        int tileIndex=tileMapData.getTile();
        if(tileIndex>=0){
            g2.drawImage(this.getTile(tileIndex),x*tileWidth,y*tileHeight,this.getCanvas());
        }
    }
    /** Draw the tiles specified by rectangle (x1,y1)-(x2,y2).
     * @param x1 The starting x co-ordinate.
     * @param y1 The starting y co-ordinate.
     * @param x2 The ending x co-ordinate.
     * @param y2 The ending y co-ordinate.
     */
    public void draw(int x1,int y1,int x2,int y2){
        Graphics2D g2=(Graphics2D)this.getCanvas().getGraphics();
        /* assume all tiles are the same size */
        int tileWidth=this.tileSet[0].getWidth(this.getCanvas());
        int tileHeight=this.tileSet[0].getHeight(this.getCanvas());
        /* draw the map */
        for(int x=x1;x<x2;x++){
            for(int y=y1;y<y2;y++){
                TileMapData tileMapData=this.getMapData(x,y);
                int tileIndex=tileMapData.getTile();
                if(tileIndex>=0){
                    g2.drawImage(this.getTile(tileIndex),x*tileWidth,y*tileHeight,this.getCanvas());
                }
            }
        }
    }
    /** Sets the object at (x,y). 
     * @param x The x co-ordinate.
     * @param y The y co-ordinate.
     * @param object The object to set at (x,y).
     */
    public void setObjectAt(int x,int y,Object object){
        if((x>=0)&&(x<this.mapData.length)){
            if((y>=0)&&(y<this.mapData[x].length)){
                this.mapData[x][y].setObject(object);
            }
        }
    }
    /** Returns the object at (x,y).
     * @param x The x co-ordinate.
     * @param y The y co-ordinate.
     * @return The object at (x,y).
     */
    public Object getObjectAt(int x,int y){
        Object object=null;
        if((x>=0)&&(x<this.mapData.length)){
            if((y>=0)&&(y<this.mapData[x].length)){
                object=this.mapData[x][y].getObject();
            }
        }
        return(object);
    }
    /** Returns a string representation of the object.
     * @return A string representation of the object.
     */
    public String toString(){
        StringBuffer tostring=new StringBuffer(this.getClass().getName());
        int length=this.tileSet.length;
        for(int index=0;index<length;index++){
            tostring.append("\n  tileSet["+index+"]:");
            tostring.append(this.tileSet[index].toString());
        }
        length=this.mapData.length;
        for(int index=0;index<length;index++){
            int length1=this.mapData[index].length;
            for(int index1=0;index1<length1;index1++){
                tostring.append("\n  mapData["+index+"]["+index1+"]:");
                tostring.append(this.mapData[index][index1].toString());
            }
        }
        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();
    }
/* end public methods */
    
/* begin private methods */
    private boolean loadImages(){
        boolean success=true;
        if(this.tilePaths!=null){
            int length=this.tilePaths.length;
            this.tileSet=new BufferedImage[length];
            for(int index=0;index<length;index++){
                String absolutePath=PathResolver.getAbsolutePath(this.absoluteDir,this.tilePaths[index]);
                this.tileSet[index]=ImageLoader.loadBufferedImage(absolutePath);
                if(this.tileSet[index]==null){
                    success=false;
                }
            }
        }
        return(success);
    }
/* end private methods */
}