/*
 * Copyright (c) 2006-2008 MiniMe. Code released under The MIT/X Window System
 * License. Full license text can be found in license.txt
 */
package minime.core;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import javax.microedition.rms.RecordStore;


/**
 * 
 *  This class implement a storage facility for any resource used by the application.
 *  ResourceManager can store string, image, sound or other binary data.
 *  
 *  Resource are actually store into different Resource store that are all managed by the ResourceManager.
 *  All ResourceStores are store into a Hashtable and identified by the name of the file where resources are 
 *  actually store into the jar.
 *  
 *      for ex: Some string resources are store into a file name english.bin . The following code shows how 
 *      to access them:     
 *  
 *      String rightButtonLabel = (String)RessourceManager.getString("english", TextRsc.Tx_rbLabel) ;
 *      
 *  ResourceManager can also support default ResourceStore.   The following code show how to define and use
 *  a default resource store:
 *
 *      RessourceManager.setDefaultResourceStore(RessourceManager.STRING_STORE, "english") ;\n
 *      String rightButtonLabel = RessourceManager.getString(TextRsc.Tx_rbLabel) ;\n
 *  
 *  Each resource type can be access as follow:\n
 *      String rightButtonLabel = RessourceManager.getString(TextRsc.Tx_rbLabel) ;\n
 *      Image bgImage = RessourceManager.getImage(TextRsc.Im_bgImage) ;\n
 *      Player playSound = RessourceManager.getSound(TextRsc.Sn_success) ;\n
 *  
 *  Resource Store.\n
 *  Each resource are actually store into RecordStore object.  Each record store contain a array of object to store the resource object,
 *  and an array of integer that store the offset of the resource in the file.
 *  When looking for the resource, the ResourceStore first check if the resource is already in the Object array, if no, it use the 
 *  offset array to loading from the file, create the correspoding object (String, Image or Player) and store it in the Object array.
 *    
 *  Resource files.\n
 *  All resource are store into resource files.   Resource files are created during build time, and packed all resources of same type
 *  into one file.  All resource files have a common header:\n
 *  <nbr of resource><offset of resource 1><offset of resource 2><offset of resource 3><offset of resource 4>....\n   
 *  Each header elts is 4 byte longs (int), so the header size is ((nbr of resource)+1)*4 
 *  
 *  Resource ID.\n
 *  Each resource is identified by an ID (and no longer by a string), which is the position of the resource in the file,
 *  and then the position of the resource in the array.   All Ids are static final member of the RscId class, which is generated 
 *  during the build.   For ex the RscId class will look like that:   
 *  
 *  public final class RscId\n
 *  {\n
 *      public final short Tx_rbLabel = 0 ;\n
 *      public final short Tx_lbLabel = 1 ;\n
 *      public final short Tx_mainViewTitle = 2 ;\n
 *      public final short Tx_welcomeMessage = 3 ;\n
 *      ...\n
 *      public final short Im_mainViewBackground = 0 ;\n
 *      public final short Im_logo = 1 ;\n
 *      public final short Im_sbBackgroundImage = 2 ;\n
 *      ...\n
 *      public final short Sn_success = 0 ;\n
 *      public final short Sn_error = 1 ;\n
 *  }\n
 *  
 *  it could be generate by the build from such resource files:\n
 *  text.english.resource\n
 *  rbLabel="ok"\n
 *  lbLabel="Quit"\n
 *  mainViewTitle="this is the main view"\n
 *  welcomeMessage="Hello world"\n
 *  
 *  image.scr320_200.resource\n
 *  mainViewBackground="view_background.png"\n
 *  logo="logo.png"\n
 *  sbBackgroundImage="softbar_background.png"\n
 *  
 *  sound.AppSound.resource\n
 *  success="success_sound.mid"\n
 *  error=="error_sound.mid"\n
 *  
 *  Each resource file name is coded as the following: <type>.<name>.resource   The build shall automatically process any .resource files.
 */
public class ResourceManager
{
    // no resource
    public static final int NONE_RESOURCE = -1;

    public static final int STRING_STORE = 0;
    public static final int IMAGE_STORE = 1;
    public static final int SOUND_STORE = 2;

    public static final String STRING_TYPE = "text";
    public static final String IMAGE_TYPE = "image";
    public static final String SOUND_TYPE = "sound";
    
    public static final String STRING_RMS_NAME = "text";
    public static final String IMAGE_RMS_NAME = "image";
    public static final String SOUND_RMS_NAME = "sound";
    

    private static Hashtable resource;
    private static String[] defaultStoreName;

    private static String getTypeFromPath(String path)
    {
    	int slashLastIndex =path.lastIndexOf('/');
        int dotIndex =path.lastIndexOf('.');
        if(slashLastIndex==-1 || dotIndex==-1)
        	return "";
        String type = path.substring(slashLastIndex+1, dotIndex);
        dotIndex = type.indexOf('.');
        if(dotIndex != -1)
        	type = type.substring(0,dotIndex);
        return type;
    }
    /**
     * Create all resources associated with the ResourceManager from filenames
     * 
     * @param resourceFileNames
     */
    public static void createResourceManger(String[] resourceFileNames) throws IOException
    {
        if (resourceFileNames.length > 0)
        {
            if (resource == null)
                resource = new Hashtable();
            for (int i = 0; i < resourceFileNames.length; i++)
            {
            	if(! resourceFileNames[i].endsWith(".bin"))
            		continue;
            	String type = getTypeFromPath(resourceFileNames[i]);
                if (type.equals(STRING_TYPE))
                    openRecordStore(STRING_STORE, resourceFileNames[i]);
                else if (type.equals(IMAGE_TYPE))
                    openRecordStore(IMAGE_STORE, resourceFileNames[i]);
                else if (type.equals(SOUND_TYPE))
                    openRecordStore(SOUND_STORE, resourceFileNames[i]);
            }
        }
    }

    /**
     * Sets the defaultResourceStore based on type and file name
     * 
     * @param type
     *            the resource type
     * @param name
     *            the file name
     */
    public static void setDefaultResourceStore(int type, String name) throws IOException
    {
        if (defaultStoreName == null)
            defaultStoreName = new String[3];
        if (resource == null)
            resource = new Hashtable();

        defaultStoreName[type] = name;
        if (!resource.containsKey(name))
        {
            openRecordStore(type, name);
        }
    }

    /**
     * Return the corresponding ResourceStore.
     * 
     * Each resource store is store in the resource Hashtable and associated
     * 
     * @param storeName,
     *            name of the resource store
     * @return
     */
    // public static ResourceStore getStore(String storeName)
    // {
    // if (resource != null)
    // return (ResourceStore) resource.get(storeName);
    // return null;
    // }
    /**
     * Return the string from the corresponding ResourceStore based on resource
     * store name and id.if rscId is less than or equal to NONE_RESOURCE, will
     * return null.
     * 
     * Each resource store is store in the resource Hashtable and associated
     * 
     * @param storeName
     *            name of the resource store
     * @param rscId
     *            string id
     * @return the string
     * @throws IllegalArgumentException
     *             if rscId is greater than or equal to the number of strings in
     *             resource file
     */
    public static String getString(String name, int rscId) throws IllegalArgumentException
    {
        if (resource != null && rscId >= 0)
        {
            ResourceStore rscStore = (ResourceStore) resource.get(name);
            return (String) rscStore.getResource(rscId);
        }
        return null;
    }

    /**
     * Return the image from the corresponding ResourceStore based on resource
     * store name and id.if rscId is less than or equal to NONE_RESOURCE, will
     * return null.
     * 
     * Each resource store is store in the resource Hashtable and associated
     * 
     * @param storeName
     *            name of the resource store
     * @param rscId
     *            image id
     * @return the image
     * @throws IllegalArgumentException
     *             if rscId is greater than or equal to the number of images in
     *             resource file
     */
    public static javax.microedition.lcdui.Image getImage(String name, int rscId) throws IllegalArgumentException
    {
        if (resource != null && rscId >= 0)
        {
            ResourceStore rscStore = (ResourceStore) resource.get(name);
            return (javax.microedition.lcdui.Image) rscStore.getResource(rscId);
        }
        return null;
    }

    /**
     * Return the player from the corresponding ResourceStore based on resource
     * store name and id.if rscId is less than or equal to NONE_RESOURCE, will
     * return null.
     * 
     * Each resource store is store in the resource Hashtable and associated
     * 
     * @param storeName
     *            name of the resource store
     * @param rscId
     *            player id
     * @return the player
     * @throws IllegalArgumentException
     *             if rscId is greater than or equal to the number of sounds in
     *             resource file
     */
    public static javax.microedition.media.Player getSound(String name, int rscId) throws IllegalArgumentException
    {
        if (resource != null && rscId >= 0)
        {
            ResourceStore rscStore = (ResourceStore) resource.get(name);
            return (javax.microedition.media.Player) rscStore.getResource(rscId);
        }
        return null;
    }

    private static ResourceStore getStore(int storeIndex)
    {
        if (resource != null && storeIndex >= 0)
            return (ResourceStore) resource.get(defaultStoreName[storeIndex]);
        return null;
    }

    /**
     * get the string from resource file based on resource id, it should be used
     * after setDefaultResourceStore.if rscId is less than or equal to
     * NONE_RESOURCE, will return null.
     * 
     * @param rscId
     *            resource id
     * @return the string
     * @throws IllegalArgumentException
     *             if rscId is greater than or equal to the number of strings in
     *             resource file
     */
    public static String getString(int rscId) throws IllegalArgumentException
    {
        ResourceStore rscStore = getStore(STRING_STORE);
        if (rscStore != null && rscId >= 0)
            return (String) rscStore.getResource(rscId);
        return null;
    }

    /**
     * get the player from resource file based on resource id, it should be used
     * after setDefaultResourceStore.if rscId is less than or equal to
     * NONE_RESOURCE, will return null.
     * 
     * @param rscId
     *            resource id
     * @return the image
     * @throws IllegalArgumentException
     *             if rscId is greater than or equal to the number of images in
     *             resource file
     */
    public static javax.microedition.lcdui.Image getImage(int rscId) throws IllegalArgumentException
    {
        ResourceStore rscStore = getStore(IMAGE_STORE);
        if (rscStore != null && rscId >= 0)
            return (javax.microedition.lcdui.Image) rscStore.getResource(rscId);
        return null;
    }

    /**
     * get the player from resource file based on resource id, it should be used
     * after setDefaultResourceStore.if rscId is less than or equal to
     * NONE_RESOURCE, will return null.
     * 
     * @param rscId
     *            resource id
     * @return the player
     * @throws IllegalArgumentException
     *             if rscId is greater than or equal to the number of sounds in
     *             resource file
     */
    public static javax.microedition.media.Player getSound(int rscId) throws IllegalArgumentException
    {
        ResourceStore rscStore = getStore(SOUND_STORE);
        if (rscStore != null && rscId >= 0)
            return (javax.microedition.media.Player) rscStore.getResource(rscId);
        return null;
    }

    /**
     * Initialized a record store from the ressource file contained in the jar.
     * Depending the type of the record store (STRING, IMAGE, SOUND), the
     * coresponding record store is created and store in the Hashtable under the
     * resource file name.
     * 
     * When creating the RecordStore the associated resource file is pre-loaded,
     * so the record store can be created with the correct size and initialized
     * with the index of the resource (offset of the resource in the file).
     * 
     * @param type,
     *            type of the record store to create and initialized (STRING,
     *            IMAGE, SOUND)
     * @param name,
     *            name of the file where to take resource from, and then name of
     *            the record store in the Hashtable
     */
    public static void openRecordStore(int type, String name) throws IOException
    {
        if (resource == null)
            resource = new Hashtable();

        switch (type)
        {
            case STRING_STORE:
                resource.put(name, new StringResourceStore(name, STRING_RMS_NAME));
                break;
            case IMAGE_STORE:
                resource.put(name, new ImageResourceStore(name, IMAGE_RMS_NAME));
                break;
            case SOUND_STORE:
                resource.put(name, new SoundResourceStore(name, SOUND_RMS_NAME));
                break;
        }
    }

    /**
     * 
     * @author aaa
     * 
     * Base abstract class for resource store.
     */
    private abstract static class ResourceStore
    {
        String path;
        String rmsName;
        int[] resourceOffset;
        Object[] content;

        public ResourceStore(String path, String rmsName) throws IOException
        {
            this.path = path;
            this.rmsName = rmsName;
            preloadResourceFile(path, rmsName);
        }

        /**
         * Returns an object based on resource id.
         * 
         * @param rscId
         *            the resource id
         * @return a specific resource object.
         * @throws IllegalArgumentException
         *             if rscId greater than or equal to resource number.
         */
        protected Object getResource(int rscId) throws IllegalArgumentException
        {
            if (rscId >= content.length)
                throw new IllegalArgumentException();

            if (content[rscId] == null)
                //content[rscId] = loadResourceFromFile(rscId);
            	content[rscId] = loadResourceFromRms(rscId);
            return content[rscId];
        }

        /**
         * pre-load resource file, actually only load resource ids into content
         * vector.
         * 
         * @param filename
         *            the resource file name.
         * @throws IOException
         *             if open file failed.
         */
        private void preloadResourceFile(String path, String rmsName) //throws IOException
        {      
        	getDataFromFileAndSaveToRms(path, rmsName);
        }

        /**
         * Read all data from the resource file and save it to RecordStore, it should called just once at the beginning of midlet start. 
         * 
         * @param path
         *            path of the .bin file that include the image, string or sound.
         * @param rmsName
         * 			name of RecordStore the image, string or sound located, e.g. image stored in the RecordStore has name 'Image'.
         * @return void
         */
        protected void getDataFromFileAndSaveToRms(String path,String rmsName)
        {   
           try{
        	   RecordStore rms=javax.microedition.rms.RecordStore.openRecordStore(rmsName, true, RecordStore.AUTHMODE_PRIVATE, false);
        	   try
               {                
                   InputStream is = getClass().getResourceAsStream(path);
                   DataInputStream dis = new DataInputStream(is);
                   int streamLen = dis.available();
                   int rscNum = dis.readInt();
                   int [] rscSize = new int[rscNum];
                   int off=dis.readInt();
             
//                   byte[] temp=new byte[(rscNum +1)*4+off];
                   byte[][] data=new byte[rscNum][];
                   
                   content = new Object[rscNum];
                   
//                   int test=rms.getNumRecords();
//                   if(rscNum==rms.getNumRecords())
//                   {
//                	   dis.close(); 
//                       dis=null;
//                       return;
//                   }
                   
                   for (int i = 0; i < rscNum; i++)
                   {  
                	  if(i<rscNum-1)
                	  {
                   	      int nextOff =dis.readInt(); 
                   	      rscSize[i] = nextOff-off ;
                   	      data[i]=new byte[rscSize[i]];
                   	      off = nextOff ;
                	  }
                	  else
                	  {      		  
                		  rscSize[i]= streamLen - (rscNum +1)*4 - off;
                		  data[i]=new byte[rscSize[i]];
                	  }
                   }
                   
//                   dis.reset();
//                   dis.read(temp);
                   for(int i =0 ; i <rscNum; i++)
                   {
                	   dis.read(data[i]);
                	   if(rms.getNumRecords()<i+1)
                	   {   
                		   rms.addRecord(data[i],0,data[i].length); 
                	   }
                	   else
                		   rms.setRecord(i+1,data[i],0,data[i].length); //may be a bug here. should be rms.setRecord(i+1,data[i],0,data[i].length);? 
                   } 
                   dis.close(); 
                   dis=null;
               }
               catch (IOException e)
               {
            	   
               }
               rms.closeRecordStore();
              }
           catch(Exception e)
           {  
           }
             	
        }
        /**
         * Read the resource data from file based on resource id.Return the data
         * as a binary array.
         * We don't use this function anymore, replace it with getDataFromFileAndSaveToRms. 
         * 
         * @param index
         *            the resource id
         * @return the resource binary array data.
         */
        protected byte[] getDataFromFile(int index)
        {     
            byte[] data = null;
            try
            {
                InputStream is = getClass().getResourceAsStream(path);
                DataInputStream dis = new DataInputStream(is);

                int streamLen = dis.available();
                int offset = resourceOffset[index]; // the resource offset
                int datalen = 0; // the resource data size

                // calculate the resource data size
                if (index < resourceOffset.length - 1)
                {
                    int nextOffset = resourceOffset[index + 1];
                    datalen = nextOffset - offset;
                }
                else
                    datalen = streamLen - offset - (resourceOffset.length + 1) * 4;
                data = new byte[datalen];

                dis.reset();
                int bytesToSkip = (resourceOffset.length + 1) * 4 + offset;
                byte[] temp = new byte[bytesToSkip];
                dis.read(temp);
                dis.read(data);
                
                dis.close();
                dis = null;
                
                return data;
        	
            }
            catch (IOException e)
            {
                return null;
            }

        }

        byte[] getDataFromRms(int index)
        {
        	byte[] data = null;
        	try{
        		// open record store
        		RecordStore rms=javax.microedition.rms.RecordStore.openRecordStore(this.rmsName, true);
        		// read a binary data in data
        		data=rms.getRecord(index+1);
         	   	return data;
         	}
         	catch(Exception e)
         	{		
         		return null;
         	}
        }
        
        abstract Object loadResourceFromFile(int index);
        abstract Object loadResourceFromRms(int index);
    }

    private static class StringResourceStore extends ResourceStore
    {

        public StringResourceStore(String path, String rmsName) throws IOException
        {
            super(path, rmsName);
        }

        Object loadResourceFromFile(int index)
        {
            byte[] data = getDataFromFile(index);
            if (data.length > 0)
                return new String(data);
            else
                return null;
        }
        
        Object loadResourceFromRms(int index)
        {
            byte[] data = getDataFromRms(index);
            if (data.length > 0)
                return new String(data);
            else
                return null;
        }
        
    }

    private static class ImageResourceStore extends ResourceStore
    {

        public ImageResourceStore(String path, String rmsName) throws IOException
        {
            super(path,rmsName);
        }

        Object loadResourceFromFile(int index)
        {   
            byte[] data = getDataFromFile(index);
            if (data.length > 0)
                return javax.microedition.lcdui.Image.createImage(data, 0, data.length);
            else
                return null;
        }
        
        Object loadResourceFromRms(int index)
        {
        	byte[] data = getDataFromRms(index);
            if (data.length > 0)
                return javax.microedition.lcdui.Image.createImage(data, 0, data.length);
            else
                return null;
        }
    }

    private static class SoundResourceStore extends ResourceStore
    {

        public SoundResourceStore(String path, String rmsName) throws IOException
        {
            super(path, rmsName);
        }

        Object loadResourceFromFile(int index)
        {   
            // \TODO should load resource for the file
            return null;
        }
        Object loadResourceFromRms(int index)
        {
        	return null;
        }
    }
}
