package scriptbuilder.structures;

import java.awt.Color;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Random;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import scriptbuilder.structures.ScriptEvent.ScriptEventType;
import scriptbuilder.structures.ScriptIncident.IncidentFocusedEvent;
import scriptbuilder.structures.ScriptIncident.SliceChangedEvent;
import scriptbuilder.structures.units.Unit;

/**
 * Representation of the script to be run by the TMC Simulator. Holds a list of
 * incidents, which have start and end times and contain events.
 *
 * @author Greg Eddington <geddingt@calpoly.edu>
 *
 * @author Bryan McGuffin <bmcguffi@calpoly.edu>
 * @version 2017/06/22
 */
public class SimulationScript extends Observable implements I_XML_Writable
{

    /**
     * All default options for GUI colorings of incidents.
     */
    public static final Color[] incidentColors =
    {
        Color.BLACK,
        Color.BLUE,
        Color.RED,
        new Color(0x38, 0x5E, 0x0F),
        new Color(128, 0, 128),
        Color.MAGENTA,
        new Color(0x23, 0x6B, 0x8E),
        Color.ORANGE,
        new Color(0x60, 0x33, 0x11),
        Color.GRAY
    };

    /**
     * The file to which this script will be saved.
     */
    public File saveFile = null;

    /**
     * The name of this script.
     */
    public String title = "";

    /**
     * The incidents displayed by the GUI.
     */
    public List<ScriptIncident> incidents;

    /**
     * The units which participate in Unit events.
     */
    public List<Unit> units;

    //Somewhere in the code, something assumes that the list of incidents
    //contains exactly 10 items. Until I can find and un-break that, this will do.
    private final int INCIDENT_FILL_COUNT = 10;

    /**
     * Number of incidents currently displayed.
     */
    public int numberOfIncidents;

    /**
     * Script handler for parsing incoming XML files.
     */
    private MyScriptHandler sh;

    //TODO: Pretty much everything in this constructor is dummy data.
    //Replace all of it.
    /**
     * Constructor. Backfill incident list with null objects, to be replaced
     * later.
     */
    public SimulationScript()
    {
        sh = new MyScriptHandler(this);
        incidents = new ArrayList<ScriptIncident>();
        units = new ArrayList<Unit>();
        numberOfIncidents = 0;

        //Backfill with null incidents
        for (int i = numberOfIncidents; i < INCIDENT_FILL_COUNT; i++)
        {
            incidents.add(null);
        }
    }

    /**
     * Update the script's observers.
     *
     */
    public void update()
    {
        // The script has changed, notify observers
        setChanged();
        notifyObservers(this);
    }

    /**
     * Tell this script's observers that there is a new slice event.
     *
     * @param e the slice focus event
     */
    public void broadcastEvent(SliceChangedEvent e)
    {
        // The slice focus has changed; pass the message
        setChanged();
        notifyObservers(e);
    }

    /**
     * Tell this script's observers that there is a new slice event.
     *
     * @param e the incident focus event
     */
    public void broadcastEvent(IncidentFocusedEvent e)
    {
        // The slice focus has changed; pass the message
        setChanged();
        notifyObservers(e);
    }

    /**
     * Load in an existing script from an XML file.
     *
     * @param f the file containing the script
     */
    public void loadScriptFromFile(File f)
    {
        try
        {

            SAXParserFactory.newInstance().newSAXParser().parse(f, sh);

            Vector<ScriptIncident> inc = sh.getIncidents();
            units = sh.getUnits();
            for (ScriptIncident sci : inc)
            {
                addIncident(sci);
            }
        }
        catch (Exception ex)
        {
            System.out.println("ERROR LOADING SCRIPT");
            ex.printStackTrace();
        }
        this.update();
    }

    /**
     * Add a new incident to the script.
     *
     * @param sci the incident to be added.
     * @return true if there was enough room to add this incident.
     */
    public boolean addIncident(ScriptIncident sci)
    {
        if (numberOfIncidents < INCIDENT_FILL_COUNT)
        {
            incidents.set(numberOfIncidents++, sci);
            return true;
        }
        return false;
    }

    /**
     * Write this script, in proper XML format, to the file in question.
     *
     * @param f the destination savefile to be written.
     */
    public void saveScriptToFile(File f)
    {
        try
        {
            f.createNewFile();

            BufferedWriter bw = new BufferedWriter(new FileWriter(f));
            bw.write(this.toXML());
            bw.flush();
            bw.close();

        }
        catch (Exception ex)
        {
            System.out.println("ERROR SAVING SCRIPT");
            ex.printStackTrace();
        }
    }

    @Override
    public String toXML()
    {
        ArrayList<TimeSlice> slices = arrangeAllSlices();
        String output = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n";
        output += "<!DOCTYPE TMC_SCRIPT SYSTEM \"script.dtd\">\n";
        output += XMLWriter.openTag(ELEMENT.TMC_SCRIPT.tag + " title=\"" + this.title + "\"");

        if (units.size() > 0)
        {
            output += XMLWriter.openTag(ELEMENT.SCRIPT_DATA.tag);
            for (Unit unit : units)
            {
                output += unit.toXML();
            }
            output += XMLWriter.closeTag(ELEMENT.SCRIPT_DATA.tag);
        }
        for (TimeSlice slice : slices)
        {
            output += slice.toXML();
        }
        output += XMLWriter.closeTag(ELEMENT.TMC_SCRIPT.tag);
        return output;
    }

    /**
     * Arranges all timeslices in this script in chronological order, then by
     * incident number.
     *
     * @return a list of all timeslices in the simulation script
     */
    public ArrayList<TimeSlice> arrangeAllSlices()
    {
        ArrayList<TimeSlice> list = new ArrayList<TimeSlice>();
        int length = absoluteLength();

        for (int i = 0; i < length; i++)
        {
            for (ScriptIncident inc : incidents)
            {

                if (inc != null && inc.slices.get(i) != null)
                {
                    list.add(inc.slices.get(i));
                }
            }
        }
        return list;
    }

    /**
     * Gets the total length of the simulation in seconds.
     *
     * @return
     */
    public int absoluteLength()
    {
        int length = 0;
        for (ScriptIncident inc : incidents)
        {
            if (inc != null)
            {
                inc.updateLength();
                int currentLength = inc.length + inc.offset;
                if (currentLength > length)
                {
                    length = currentLength;
                }
            }
        }
        return length;
    }

    /**
     * Counts the number of incidents currently running.
     *
     * @return the number of non-null incidents currently in the script. A
     * number between 0 and INCIDENT_FILL_COUNT, inclusive.
     */
    public int incidentCount()
    {
        int count = 0;
        for (ScriptIncident inc : incidents)
        {
            if (inc != null)
            {
                count++;
            }
        }
        return count;
    }

}
