package sim.app.heatbugs;
import sim.engine.*;
import sim.field.grid.*;
import sim.util.*;
import ec.util.*;
import java.io.*;

public /*strictfp*/ class HeatBugs extends SimState
    {
    public double minIdealTemp = 17000;
    public double maxIdealTemp = 31000;
    public double minOutputHeat = 6000;
    public double maxOutputHeat = 10000;

    public double evaporationRate = 0.993;
    public double diffusionRate = 1.0;
    public static final double MAX_HEAT = 32000;
    public double randomMovementProbability = 0.1;

    public int gridHeight;
    public int gridWidth;
    public int bugCount;
    HeatBug[] bugs;
    
    public double getMinimumIdealTemperature() { return minIdealTemp; }
    public void setMinimumIdealTemperature( double temp ) { if( temp <= maxIdealTemp ) minIdealTemp = temp; }
    public double getMaximumIdealTemperature() { return maxIdealTemp; }
    public void setMaximumIdealTemperature( double temp ) { if( temp >= minIdealTemp ) maxIdealTemp = temp; }
    public double getMinimumOutputHeat() { return minOutputHeat; }
    public void setMinimumOutputHeat( double temp ) { if( temp <= maxOutputHeat ) minOutputHeat = temp; }
    public double getMaximumOutputHeat() { return maxOutputHeat; }
    public void setMaximumOutputHeat( double temp ) { if( temp >= minOutputHeat ) maxOutputHeat = temp; }
    public double getEvaporationConstant() { return evaporationRate; }
    public void setEvaporationConstant( double temp ) { if( temp >= 0 && temp <= 1 ) evaporationRate = temp; }
    public Object domEvaporationConstant() { return new Interval(0.0,1.0); }
    public double getDiffusionConstant() { return diffusionRate; }
    public void setDiffusionConstant( double temp ) { if( temp >= 0 && temp <= 1 ) diffusionRate = temp; }
    public Object domDiffusionConstant() { return new Interval(0.0, 1.0); }
    public double getRandomMovementProbability() { return randomMovementProbability; }
    public void setRandomMovementProbability( double t )
        {
        if (t >= 0 && t <= 1)
            {
            randomMovementProbability = t;
            for( int i = 0 ; i < bugCount ; i++ )
                if (bugs[i]!=null)
                    bugs[i].setRandomMovementProbability( randomMovementProbability );
            }
        }
    public Object domRandomMovementProbability() { return new Interval(0.0, 1.0); }
        
    public double getMaximumHeat() { return MAX_HEAT; }

    // we presume that no one relies on these DURING a simulation
    public int getGridHeight() { return gridHeight; }
    public void setGridHeight(int val) { if (val > 0) gridHeight = val; }
    public int getGridWidth() { return gridWidth; }
    public void setGridWidth(int val) { if (val > 0) gridWidth = val; }
    public int getBugCount() { return bugCount; }
    public void setBugCount(int val) { if (val >= 0) bugCount = val; }
    
    public DoubleGrid2D valgrid;
    public DoubleGrid2D valgrid2;
    public SparseGrid2D buggrid;
    

    /** Creates a HeatBugs simulation with the given random number seed. */
    public HeatBugs(long seed)
        {
        this(seed, 100, 100, 100);
        }
        
    public HeatBugs(long seed, int width, int height, int count)
        {
        super(new MersenneTwisterFast(seed), new Schedule(2));
        gridWidth = width; gridHeight = height; bugCount = count;
        createGrids();
        }

    protected void createGrids()
        {
        bugs = new HeatBug[bugCount];
        valgrid = new DoubleGrid2D(gridWidth, gridHeight,0);
        valgrid2 = new DoubleGrid2D(gridWidth, gridHeight, 0);
        buggrid = new SparseGrid2D(gridWidth, gridHeight);      
        }
    
    /** Resets and starts a simulation */
    public void start()
        {
        super.start();  // clear out the schedule
        
        // make new grids
        createGrids();
    
        // Schedule the heat bugs -- we could instead use a RandomSequence, which would be faster
        // But we spend no more than 3% of our total runtime in the scheduler max, so it's not worthwhile
        for(int x=0;x<bugCount;x++)
            {
            bugs[x] = new HeatBug(random.nextDouble() * (maxIdealTemp - minIdealTemp) + minIdealTemp,
                                  random.nextDouble() * (maxOutputHeat - minOutputHeat) + minOutputHeat,
                                  randomMovementProbability);
            buggrid.setObjectLocation(bugs[x],random.nextInt(gridWidth),random.nextInt(gridHeight));
            schedule.scheduleRepeating(bugs[x]);
            }
                        
        // Here we're going to pick whether or not to use Diffuser (the default) or if
        // we're really going for the gusto and have multiple processors on our computer, we
        // can use our multithreaded super-neato ThreadedDiffuser!  On a Power Mac G5 with
        // two processors, we get almost a 90% speedup in the underlying model because *so*
        // much time is spent in the Diffuser.
                            
        // Schedule the diffuser to happen after the heatbugs
        if (availableProcessors() >  1)  // yay, multi-processor!
            schedule.scheduleRepeating(Schedule.EPOCH,1,new ThreadedDiffuser(),1);
        else
            schedule.scheduleRepeating(Schedule.EPOCH,1,new Diffuser(),1);
        }
    
    /** This little function calls Runtime.getRuntime().availableProcessors() if it's available,
        else returns 1.  That function is nonexistent in Java 1.3.1, but it exists in 1.4.x.
        So we're doing a little dance through the Reflection library to call the method tentatively!
        The value returned by Runtime is the number of available processors on the computer.  
        If you're only using 1.4.x, then all this is unnecessary -- you can just call
        Runtime.getRuntime().availableProcessors() instead. */
    public int availableProcessors()
        {
        Runtime runtime = Runtime.getRuntime();
        try { return ((Integer)runtime.getClass().getMethod("availableProcessors", null).
                      invoke(runtime,null)).intValue(); }
        catch (Exception e) { return 1; }  // a safe but sometimes wrong assumption!
        }
        
    
    
    /** Runs HeatBugs headless (no GUI).  Checkpoints out to files every 500 simulation timesteps.
        If no command-line arguments are provided, a new HeatBugs is started.
        Optionally the user can say "-checkpoint [filename]" and HeatBugs will load a checkpoint
        and continue from that point. */
        
    // This example is overly elaborate, with checkpointing and stuff.
    // But to run a simulation, the basic thing you need to do is:
    //
    //
    // 1. Make a SimState               heatbugs = new HeatBugs(23);  // seed the RNG with 23, what the heck
    // 2. Tell it we're starting        heatbugs.start();
    // 3. Loop:                         for(...however long I want to run...)
    // 4.       Step the schedule               if (!heatbugs.schedule.step(heatbugs)) break;
    // 5. Finish up                     heatbugs.finish();
    //
    // ...and that's it!
    
    public static void main(String[] args)
        {
        HeatBugs heatbugs = null;
        
        // should we load from checkpoint?  I wrote this little chunk of code to
        // check for this to give you the general idea.
        
        for(int x=0;x<args.length-1;x++)  // "-checkpoint" can't be the last string
            if (args[x].equals("-checkpoint"))
                {
                SimState state = SimState.readFromCheckpoint(new File(args[x+1]));
                if (state == null)   // there was an error -- it got printed out to the screen, so just quit
                    System.exit(1);
                else if (!(state instanceof HeatBugs))  // uh oh, wrong simulation stored in the file!
                    {
                    System.out.println("Checkpoint contains some other simulation: " + state);
                    System.exit(1);
                    }
                else // we're ready to lock and load!  
                    heatbugs = (HeatBugs)state;
                }
        
        // ...or should we start fresh?
        if (heatbugs==null)  // no checkpoint file requested
            {
            heatbugs = new HeatBugs(System.currentTimeMillis());  // make a new heatbugs.  Seed the RNG with the time
            heatbugs.start();  // prep the bugs!
            System.out.println("Starting HeatBugs.  Running for 5000 steps.");
            }
        
        long time;
        while((time = heatbugs.schedule.time()) < 5000)
            {
            // step the schedule.  This is where everything happens.
            if (!heatbugs.schedule.step(heatbugs)) break;   // it won't happen that we end prematurely,
                                                            // but it's worth checking for!

            if (time%100==0 && time!=0) 
                System.out.println("Time Step " + time);
        
            // checkpoint
            if (time%500==0 && time!=0)
                {
                String s = "hb." + time + ".checkpoint";
                System.out.println("Checkpointing to file: " + s);
                heatbugs.writeToCheckpoint(new File(s));
                }
            }
            
        heatbugs.finish();  // we don't use this, but it's good style
                
        // If we're using the ThreadedDiffuser, it uses ParallelSequence, and so we have to 
        // explicitly say System.exit(0) here instead of just dropping out -- see the docs
        // on ParallelSequence for more information.
        System.exit(0);
        }
        

    }