package scriptbuilder.structures;

import java.awt.Color;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Vector;
import javax.xml.parsers.SAXParserFactory;
import scriptbuilder.structures.ScriptIncident.IncidentFocusedEvent;
import scriptbuilder.structures.ScriptIncident.SliceChangedEvent;
import scriptbuilder.structures.events.*;
import scriptbuilder.structures.units.Unit;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.TreeMap;
import static scriptbuilder.structures.XMLBuilder.prettyPrintXML;

/**
 * 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>
 * @author Sebastien Danthinne <sdanthin@calpoly.edu>
 * @version 2017/06/22
 */
public class SimulationScript extends Observable implements I_XML_Writable
{

    /**
     * Strings to show in the incident color combo box.  Last item should be black,
     * which will be used if invalid color is provided to lookupColor().
     */
    public static final String[] colorNames = {"BLUE", "RED", "CYAN", "GREEN", 
        "ORANGE", "MAGENTA", "YELLOW", "BLACK"};
    /** 
     * Allowed color choices for incident display. 
     * These colors must match the items in colorNames.
     */
    public static final Color[] incidentColors = {Color.BLUE, Color.RED, Color.CYAN, 
        Color.GREEN, Color.ORANGE, Color.MAGENTA, Color.YELLOW, Color.BLACK};
    /**
     * 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.
    //todo: this incident fill count error
    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;
    
    public final String audioDirectory = "Audio";

    
    public boolean saved;
    
    //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;
        saved = true;

        //Backfill with null incidents
        for (int i = numberOfIncidents; i < INCIDENT_FILL_COUNT; i++)
        {
            incidents.add(null);
        }
    }
    
    /**
     * checks and sees if this object has the unit passed.
     * @param unitID
     * @return does this SimulationScript have unitnum?
     */
    public boolean hasUnit(String unitID){
        boolean indicator = false;
        if(units.size()!=0){
            for(Unit u : units){
               if(unitID.equals(u.UnitNum)){
                   indicator = true;
               }
            }
        }
            
        return indicator;
    }
    /**
     * creates a dummy unit that only has unit number
     * @param unitNum 
     */
    public void addDummyUnit(String unitNum){
        Unit dummy = new Unit();
        dummy.UnitNum = unitNum;
        units.add(dummy);
    }
    /**
     * Update the script's observers.
     * 
     */
    public void update()
    {
        // The script has changed, notify observers
        //use to rewrite the save indicator
        //System.out.println("Script changed");
        setChanged();
        notifyObservers(this);
        saved = false;
        
        
    }
    

    /**
     * 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();
        }
        System.out.println("H");
        for(Unit testUnit : units){
            System.out.println(testUnit.toXML());
        }
        this.update();
    }
    
    /**
     * Load in an existing list of units from an XML file.
     * @param inStream the input stream for the file containing the units
     */
    public void loadUnitsFromFile(java.io.InputStream inStream){
        try
        {
            SAXParserFactory.newInstance().newSAXParser().parse(inStream, sh);
            units.addAll(sh.getUnits());
        }
        catch (Exception ex)
        {
            System.out.println("ERROR LOADING UNITS");
            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));
            // convert to XML and remove newlines
            String xmlOut = (this.toXML()).replace("\n",""); 
            // pretty print and save to file
            bw.write(prettyPrintXML(xmlOut));
            bw.flush();
            bw.close();
        }
        catch (Exception ex)
        {
            System.out.println("ERROR SAVING SCRIPT");
            ex.printStackTrace();
        }
        try
        {
            createAudioDirectory(Paths.get(f.getCanonicalPath()).getParent());
        }
        catch(IOException ex)
        {
            System.err.println("there was a problem creating the audio directories.");
        }
        
        
        saved = true;
    }
    
    /**
     * Creates the proper audio directory using the I_AudioEvent types.
     * @param path of audio directory root
     */
    private void createAudioDirectory(Path path){
        String separator = System.getProperty("file.separator");
        File f = new File(path.toString()+separator+audioDirectory);
        if(!f.exists())
        {
            f.mkdir();
            //scan through to see what is already there and if there is no existing audio directory, create it.
        }
        for(ScriptIncident i : incidents)
        {
            if(i!=null)
            {
                String name = ((Integer) i.number).toString();
                File incidentFolder = new File(path.toString()+separator+audioDirectory+separator+name);
                if(!incidentFolder.exists())
                {
                    //create an incidentfolder since one does not already exist
                    incidentFolder.mkdir();
                }

                
                for(TimeSlice slice : i.getSlices())
                {
                    for(I_ScriptEvent event : slice.events)
                    {
                        if(event instanceof I_AudioEvent)
                        {
                            //if the event is a chp radio event
                            //then add the dummy file to the subdirectory created
                            //CURRENTLY this ONLY is implemented for CHPRadioEvent, so it will need to be changed later.
                            CHPRadioEvent radioEvent = (CHPRadioEvent) event;

                            String output = radioEvent.toScriptFile();
                            //optimally, this line should use the ID, but to account for files being read in, it needs to be the radiofile name
                            System.out.println("Attempting to create file: "+ path.toString()+
                                            separator+
                                            audioDirectory+
                                            separator+
                                            name+
                                            separator+
                                            radioEvent.radioFile.replaceAll(".mp3","")+
                                            ".txt");
                            File newAudioScript = new File(
                                    path.toString()+
                                            separator+
                                            audioDirectory+
                                            separator+
                                            name+
                                            separator+
                                            radioEvent.radioFile.replaceAll(".mp3","")+
                                            ".txt");
                            
                            try
                            {
                                newAudioScript.createNewFile();
                                BufferedWriter bw = new BufferedWriter(new FileWriter(newAudioScript));
                                bw.write(output);
                                bw.flush();
                                bw.close();
                            }catch(Exception e)
                            {
                                //to find the bug that triggers this Exception, we need to print out the lines that 
                                System.err.println("there was a problem creating your text files for: " + radioEvent.radioFile + "\n" + e.getMessage());
                            }

                        }
                    }
                }

                
            }

            
        }
    }

    @Override
    public String toXML()
    {
        ArrayList<TimeSlice> slices = arrangeAllSlices();
        String output = "";
        output += XMLBuilder.openTag(ELEMENT.TMC_SCRIPT.tag + " title=\"" + this.title + "\"");

        if (units.size() > 0)
        {
            output += XMLBuilder.openTag(ELEMENT.SCRIPT_DATA.tag);
            for (Unit unit : units)
            {
                output += unit.toXML();
            }
            output += XMLBuilder.closeTag(ELEMENT.SCRIPT_DATA.tag);
        }
        for (TimeSlice slice : slices)
        {
            output += slice.toXML();
        }
        output += XMLBuilder.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;
    }
    
    /** Given a color, find its index in the incidentColors.
     * @param color a java color 
     * @return the index of color in incidentColors, or last index if color isn't 
     * in incidentColors.  The last item in incidentColors should be black.
     */
    public static int lookupColor(Color color)
    {
        int idx = 0;
        // search color array for target
        while(idx < incidentColors.length && !incidentColors[idx].equals(color))
        { 
            idx++;
        }
        // if color not found, return index of last item.
        if (idx == incidentColors.length) 
        {
            return idx-1;
        }
        else return idx;
    }
}
