import net.sf.jirr.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;

public class EngineManager 
{
  static protected int READ_BUFFER_SIZE = 1000;
  static protected EngineManager instance = null;
  protected IrrlichtDevice device = null;
  protected boolean running = true;
  protected String filename;
  
  protected long lastFrame = 0;
  
  // a collection of the BaseObjects 
  protected ArrayList<BaseObject> baseObjects = new ArrayList<BaseObject>();
  // a collection where new BaseObjects are placed, to avoid adding items 
  // to baseObjects while in the baseObjects collection while it is in a loop
  protected ArrayList<BaseObject> newBaseObjects = new ArrayList<BaseObject>();
  // a collection where removed BaseObjects are placed, to avoid removing items 
  // to baseObjects while in the baseObjects collection while it is in a loop
  protected ArrayList<BaseObject> removedBaseObjects = new ArrayList<BaseObject>();
  
  static public EngineManager getInstance()
  {
    if (instance == null)
      instance = new EngineManager();
    return instance;
  }
  
  public IrrlichtDevice getDevice()
  {
    return device;
  }
  
  public ISceneManager getSceneManager()
  {
    return device.getSceneManager();
  }
  
  public IVideoDriver getVideoDriver()
  {
    return device.getVideoDriver();
  }
  
  public IGUIEnvironment getGuiEnvironment()
  {
    return device.getGUIEnvironment();
  }
  
  protected EngineManager()
  {
    
  }
  
  public void startupEngineManager(String caption)
  {
    // create the Irrlicht device
    device = Jirr.createDevice(E_DRIVER_TYPE.EDT_DIRECT3D9, new dimension2di(600400)16 );
    // set the window title
    device.setWindowCaption(caption);
    // add a camera to the scene
    device.getSceneManager().addCameraSceneNode(null, new vector3df(0,0,-50)new vector3df(0,0,0));
    // load a font and set it as the default
    IGUISkin skin = getGuiEnvironment().getSkin();
        IGUIFont font = getGuiEnvironment().getFont("fonthaettenschweiler.bmp");
        if (font != null
          skin.setFont(font);
        
        loadResources();
        
        lastFrame = System.currentTimeMillis();
  }
  
  protected void loadResources()
  {
    try
    {
      // The EngineManager assumes that there is a file called media.zip, either in the
      // JAR file downloaded via Webstart, or just in the current working directory.
      // Webstart doesn't allow you to know the name of the local JAR filename in the cache,
      // but we can get access to the media.zip file easily enough with a call to 
      // getResourceAsStream. Once we get access to that file stream we can copy
      // the media.zip file out into a known temporary location and give Irrlicht 
      // access to it there.
      
      // get the file stream (should be null for a local app, and not null for a webstart app)
      InputStream in = this.getClass().getResourceAsStream("media.zip");
          if (in != null)
          {
            // find a unique temporary file name 
            int i = 0;
            String tempDir = System.getProperty("java.io.tmpdir");
            File file = null;
            do 
            {
              ++i;
              file = new File(tempDir + "media" + i + ".zip");
            while (file.exists());
            
            // write the output file
            filename = file.getPath();
            OutputStream out = new FileOutputStream(filename);
            int c;
            byte[] buffer = new byte[READ_BUFFER_SIZE];
              while ((c = in.read(buffer)) != -1)
              {
                  out.write(buffer, 0, c);
              }
              in.close();
              out.close();
            
            System.out.println("addZipFileArchive: " + file);
            device.getFileSystem().addZipFileArchive(file.getPath());
          }
          else
          {
          // otherwise assume there is a file called media.zip
            System.out.println("addZipFileArchive: media.zip");
            device.getFileSystem().addZipFileArchive("media.zip");
          }
    }
    catch (Exception ex)
    {
      
    }
  }
  
  public void shutdown()
  {
    shutdownAll();
    
    // clean up the Irrlicht engine
    device.drop();
    device = null;
    
    // clean up the temporary file
    if (filename != null)
    {
      File file = new File(filename);
      file.delete();
    }
  }
  
  public void stopGameLoop()
  {
    running = false;
  }
  
  public void enterGameLoop()
  {
    while (device.run() && running)
    {
      enterFrame();
      
      // begin the scene
      device.getVideoDriver().beginScene(true, true, new SColor(255,100,101,140));
      // draw the scene
      device.getSceneManager().drawAll();
      // draw the GUI
      device.getGUIEnvironment().drawAll();
      // end the scene
      device.getVideoDriver().endScene();
    }
  }
  
  public void enterFrame()
  {
    // Calculate the time since the last frame
    long thisTick = System.currentTimeMillis();
    double seconds = (thisTick - lastFrame1000.0;
      lastFrame = thisTick;
      
      // sync up the baseObjects collection before we enter the loop
      // this is done to prevent the collection being modified
      // while we are looping over it
      removeDeletedBaseObjects();
      insertNewBaseObjects();
      
      // now allow objects to update themselves
    for (BaseObject gameObject : baseObjects)
      if (gameObject.inUse
        gameObject.enterFrame(seconds);
  }
  
  public void addBaseObject(BaseObject baseObject)
  {
    newBaseObjects.add(baseObject);
  }
  
  public void removeBaseObject(BaseObject baseObject)
  {
    removedBaseObjects.add(baseObject);
  }
  
  protected void shutdownAll()
  {
    // don't dispose objects twice
    for (BaseObject baseObject : baseObjects)
    {
      boolean found = false;
      for (BaseObject removedObject : removedBaseObjects)
      {
        if (removedObject == baseObject)
        {
          found = true;
          break;
        }
      }
      
      if (!found)
        baseObject.shutdown();
    }
  }
  
  protected void insertNewBaseObjects()
  {
    for (BaseObject baseObject : newBaseObjects)
      baseObjects.add(baseObject);
    
    newBaseObjects.clear();
  }
  
  protected void removeDeletedBaseObjects()
  {
    // insert the object according to it's z position
    for (BaseObject removedObject : removedBaseObjects)
      baseObjects.remove(removedObject);
    
    removedBaseObjects.clear();
  }
}