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;
import scriptbuilder.structures.ScriptEvent.ScriptEventType;
import scriptbuilder.structures.events.I_AudioEvent;
import scriptbuilder.structures.events.AudioEvent;

/**
 * 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;
    
    public int audioEventCount = 0;

    public 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);
    }
    
//    /**
//     * Constructor with type and location parameters.
//     * @param color
//     * @param number
//     * @param name
//     * @param description
//     * @param script
//     * @param offset
//     * @param type
//     * @param location 
//     */
//    public ScriptIncident(Color color, int number, String name,
//            String description, SimulationScript script,
//            int offset, String type, String location)
//    {
//        this.color = color;
//        this.number = number;
//        this.name = name;
//        this.description = description;
//        this.script = script;
//        slices = new TreeMap<Integer, TimeSlice>();
//        this.setOffset(offset);
//        this.location = location;
//        this.type = type;
//        insertCadData(offset, new CadData(type,location));
//    }

    /**
     * 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();
    }
    /**
     * shifts all timeslices over by timeAdd amount starting at offset
     * @param offset amount of time preceding where we want to move all of the following events.
     * @param timeAdd amount of time added after offset 
     */
    public void moveAllFollowingEvents(int offset, int timeAdd)
    {
        //int old = this.offset;
        TreeMap<Integer, TimeSlice> newSlices = new TreeMap<Integer, TimeSlice>();

        

        for (Integer k : slices.keySet())
        {
            if(k<offset)
            {
                newSlices.put(k, slices.get(k));
            }else
            {
                if(k+timeAdd >0)
                {
                    newSlices.put(k+timeAdd, slices.get(k));
                }else
                {
                    newSlices.put(0, slices.get(k));
                }
                
            }
            
            
        }
        for (TimeSlice ts : newSlices.values())
        {
            if(ts.getTime()>offset )
            {
                if( ts.getTime()+timeAdd>=0)
                {
                    ts.shift(timeAdd);
                }
                else
                {
                    ts.shift(-ts.getTime());
                }
                
            }
            
        }
        latestStart = (latestStart + timeAdd>0) ? latestStart + timeAdd: 0;
        slices = newSlices;
        updateLength();
        script.update();
    }
    /**
     * Adds an event to the correct timeslice when imported from an XML file.
     * @param ev Event to be added to the timeslice.
     * @param start Time when event needs to be added.
     */
    public void addNewEventFromXML(I_ScriptEvent ev, int start)
    {
        TimeSlice t = slices.get(start);
        if(t==null)
        {
            t = new TimeSlice(start,this);
            slices.put(start,t); 
        }
        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();
        
    }
    /**
     * 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);
        //logic to check if the added event needs to also have an associated audio event.
        
        //If not, make one; then, add the event to it
        if (t == null)
        {
            t = new TimeSlice(start, this);
            slices.put(start, t);
        }
        
        //checks if event to add is an I_AudioEvent and if the I_AudioEvent already has an AudioEvent to connect with it
        if(ev instanceof I_AudioEvent){
            
            
            //checks to see if there is already a filename in the I_AudioEvent, if so, set the AudioEvent ID. This is because the ID is not stored within the Script itself.
            if(!((I_AudioEvent) ev).getFileName().equals(""))
            {
                ((I_AudioEvent) ev).setID(((I_AudioEvent) ev).getFileName().replaceAll(".mp3",""));
            }
            // checks to see if the I_AudioEvent already has an ID or not, if not, set one based on the audioEventCount. 
            if(((I_AudioEvent) ev).getID().equals(""))
            {
                ((I_AudioEvent) ev).setID(number+Integer.toString(audioEventCount));
                audioEventCount++;
            }
            if(t.getCorrespondingAudioEvent((I_AudioEvent)ev)==null)
            {
                AudioEvent audio = (AudioEvent) ScriptEvent.factoryByType(ScriptEventType.AUDIO_EVENT);

                //sets AudioEvent id to id of I_AudioEvent
                audio.id = ((I_AudioEvent) ev).getID();
                //sets I_AudioEvent filename to the AudioEvent id
                ((I_AudioEvent) ev).setFileName(audio.id+ ".mp3");
                //sets the file path of the AudioEvent based on its internal id
                audio.setAudioFilePathRelative();
                //adds AudioEvent to the timeslice
                t.addEvent(audio);
            }
            
            
        }
        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 += XMLWriter.internalDTD();
        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;
    }

    public 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;
    }
    
    public CadData getCadData(long currentEventTime)
    {
        int time = (int) currentEventTime;
        TimeSlice slice;
        
        slice = slices.get(time);
        return slice.cadData;
    }

    /**
     * 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)
        {
            if (slices.size() > 0)
            {
                offset = slices.firstKey();
            }
            else
            {
                offset = 0;
            }
        }
        this.updateLength();
        script.update();
    }

    /**
     * Shifts the start time of a given event in this incident. The first event
     * in the incident cannot be shifted.
     *
     * @param evt the event to be relocated
     * @param oldTime the current start time of the event
     * @param newTime the destination start time of the event
     * @return true if the event is shifted over properly
     * @pre the event in question exists
     * @pre oldTime != newTime
     */
    public boolean changeEventStart(I_ScriptEvent evt, int oldTime, int newTime)
    {
        if (!(oldTime == 0 && newTime > oldTime))
        {
            if(evt instanceof I_AudioEvent)
            {
                //todo: need to figure out how to remove the audioEvent tha corresponds with the 
                this.slices.get(oldTime).removeCorrespondingAudioEvent((I_AudioEvent)evt);
                //this.addNewEvent(evt,newTime)
            }
            this.slices.get(oldTime).events.remove(evt);
            this.addNewEvent(evt, newTime);
            return true;
        }
        return false;
    }
}
