package scriptbuilder.structures;

import java.awt.Color;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.TreeMap;
import scriptbuilder.structures.events.I_ScriptEvent;

/**
 * A script incident. It has an ID number, a name, and a description. It may
 * contain several script events. It also has a color in the GUI window, and is
 * collapsible. Incidents may start as soon as the script begins to run, or they
 * can be offset from the start of the script.
 *
 * @author Greg Eddington <geddingt@calpoly.edu>
 * @author Bryan McGuffin
 * @version 2017/06/29
 */
public class ScriptIncident implements I_XML_Writable
{

    /**
     * The moments in time which have associated events.
     */
    public TreeMap<Integer, TimeSlice> slices;

    /**
     * GUI display color of this slice.
     */
    public Color color;

    /**
     * ID number for this incident.
     */
    public int number;

    /**
     * Name of the incident.
     */
    public String name;

    /**
     * Description of the incident.
     */
    public String description;

    /**
     * Length, in seconds, of the incident.
     */
    public int length = 0;

    /**
     * If true, incident appears minimized.
     */
    public boolean collapsed = false;

    /**
     * Number of seconds between start of simulation and start of this incident.
     */
    public int offset = 0;

    /**
     * Start position of the latest timeslice.
     */
    private int latestStart = 0;

    /**
     * Number of events in this incident.
     */
    public int eventCount = 0;

    SimulationScript script;

    /**
     * Basic constructor.
     *
     * @param number The incident ID number
     * @param name The name of the incident
     * @param description The description of the incident
     * @param script The script object holding this incident
     */
    public ScriptIncident(int number, String name, String description,
            SimulationScript script)
    {
        color = Color.BLACK;
        this.number = number;
        this.name = name;
        this.description = description;
        this.script = script;
        slices = new TreeMap<Integer, TimeSlice>();
    }

    /**
     * Constructor with color parameter.
     *
     * @param color The color to use in the GUI for this event
     * @param number The incident ID number
     * @param name The name of the incident
     * @param description The description of the incident
     * @param script The script object holding this incident
     */
    public ScriptIncident(Color color, int number, String name,
            String description, SimulationScript script)
    {
        this.color = color;
        this.number = number;
        this.name = name;
        this.description = description;
        this.script = script;
        slices = new TreeMap<Integer, TimeSlice>();
    }

    /**
     * Constructor with color and offset parameters.
     *
     * @param color The color to use in the GUI for this event
     * @param number The incident ID number
     * @param name The name of the incident
     * @param description The description of the incident
     * @param script The script object holding this incident
     * @param offset Number of seconds after 00:00:00 that this incident begins
     */
    public ScriptIncident(Color color, int number, String name,
            String description, SimulationScript script,
            int offset)
    {
        this.color = color;
        this.number = number;
        this.name = name;
        this.description = description;
        this.script = script;
        slices = new TreeMap<Integer, TimeSlice>();
        this.setOffset(offset);
    }

    /**
     * Set whether or not the incident is fully visible or in a compacted state.
     *
     * @param collapsed True if the event is compacted
     */
    public void setCollapsed(boolean collapsed)
    {
        this.collapsed = collapsed;
        script.update();
    }

    /**
     * Set the delay time between the start of the script and the start of this
     * incident.
     *
     * @param offset Number of seconds after 00:00:00 that this incident begins
     */
    public void setOffset(int offset)
    {
        int old = this.offset;
        this.offset = offset;
        TreeMap<Integer, TimeSlice> newSlices = new TreeMap<Integer, TimeSlice>();

        int latest = 0;

        for (Integer k : slices.keySet())
        {
            newSlices.put(k + (offset - old), slices.get(k));
            latest = k + (offset - old);
        }

        latestStart = latest;

        for (TimeSlice ts : newSlices.values())
        {
            ts.shift(offset - old);
        }

        slices = newSlices;
        updateLength();
        script.update();
    }

    /**
     * Add a new script event to this incident.
     *
     * @param ev The new event
     * @param start Start time of this event, in seconds, from the beginning of
     * the simulation
     */
    public void addNewEvent(I_ScriptEvent ev, int start)
    {
        //Check to see if there's already a timeslice here
        TimeSlice t = slices.get(start);
        //If not, make one; then, add the event to it
        if (t == null)
        {
            t = new TimeSlice(start, this);
            t.addEvent(ev);
            slices.put(start, t);
        }
        else
        {
            t.addEvent(ev);
        }
        eventCount++;

        //If this is the latest start time in the incident, update that number
        if (start > latestStart)
        {
            latestStart = start;
        }
        //If this event is earlier than all previous events, or if there are 
        //no other events, the offset is equal to this event's start time
        if (start < offset || eventCount == 1)
        {
            offset = start;
            //System.out.println("Offset: " + offset);
        }
        updateLength();
    }

    /**
     * Get an array of all valid timeSlices.
     *
     * @return List of timeSlices which are not null
     */
    public ArrayList<TimeSlice> getSlices()
    {
        ArrayList<TimeSlice> arr = new ArrayList<TimeSlice>();
        for (int i = 0; i <= latestStart; i++)
        {
            TimeSlice ts = slices.get(i);
            if (ts != null)
            {
                arr.add(ts);
            }
        }
        return arr;
    }

    /**
     * Write this incident, in proper XML form, to the file in question.
     *
     * @param f the destination savefile
     */
    public void saveIncidentToFile(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()
    {
        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.script.title + "\"");

        for (TimeSlice slice : slices.values())
        {
            output += slice.toXML();
        }
        output += XMLWriter.closeTag(ELEMENT.TMC_SCRIPT.tag);
        return output;
    }

    void insertCadData(long currentEventTime, CadData cad)
    {
        int time = (int) currentEventTime;

        TimeSlice slice;

        if (slices.get(time) == null)
        {
            slices.put(time, new TimeSlice(time, this));
        }
        slice = slices.get(time);
        slice.cadData = cad;
    }

    /**
     * Update the offset and apparent length of this incident. The offset is the
     * start time of the earliest event in the incident. The length is the time
     * that the latest, longest-lasting event ends, minus the offset.
     */
    public void updateLength()
    {
        int lengthSoFar = 0;
        for (int i = 0; i <= latestStart; i++)
        {
            TimeSlice ts = slices.get(i);
            if (ts != null)
            {
                int reach = ts.getTime() + ts.getEffectiveDuration() - offset;
                if (reach > lengthSoFar)
                {
                    lengthSoFar = reach;
                }
            }
        }
        length = lengthSoFar;
    }

    /**
     * An event which is fired if the focused slice changes.
     */
    public static class SliceChangedEvent
    {

        /**
         * The slice which has received focus in this event.
         */
        public TimeSlice slice;

        SliceChangedEvent(TimeSlice slice)
        {
            this.slice = slice;
        }
    }

    /**
     * Update and cause the system to focus on the given timeslice.
     *
     * @param i Index of the slice to focus on
     */
    public void setSliceActive(int i)
    {
        if (this.slices.get(i) != null)
        {
            script.broadcastEvent(new SliceChangedEvent(this.slices.get(i)));
        }
    }

    /**
     * An event which is fired if the focused incident changes.
     */
    public static class IncidentFocusedEvent
    {

        /**
         * The incident which has received focus in this event.
         */
        public ScriptIncident incident;

        IncidentFocusedEvent(ScriptIncident i)
        {
            incident = i;
        }
    }

    /**
     * Update and cause the system to focus on this incident.
     */
    public void setIncidentActive()
    {
        script.broadcastEvent(new IncidentFocusedEvent(this));
    }

    /**
     * String representation of this incident.
     *
     * @return String of the form "[Incident number] - [Incident name]"
     */
    @Override
    public String toString()
    {
        return this.number + " - " + this.name;
    }

    /**
     * Remove the timeslice at the given position.
     *
     * @param seconds absolute start time of the timeslice to remove.
     */
    public void removeTimeSlice(int seconds)
    {
        //remove the timeslice
        this.slices.remove(seconds);

        //offset is equivalent to start time of earliest slice
        if (seconds == offset)
        {
            offset = slices.firstKey();
        }
        this.updateLength();
        script.update();
    }
}
