package vermilion;

import java.util.ArrayList;
import java.util.TreeMap;

/**
 * An event provider that includes a tracker interface for programming events
 * on machines. This part of the provider is involved with handling and
 * manipulating pattern data. This is also cached for performance reasons.
 */
public class SystemComposerPatternData
{
    /**
     * This is where we cache all the patterns.
     */
    public TreeMap<String, TreeMap<String, PatternNode>> patternCache;

    // This is used to cache instances of machines, since we sometimes need
    // to know things like the parameters etc. on the machine.
    private TreeMap<String, MachineBase> refInstances = new TreeMap<String, MachineBase>();
    
    // Reloads the tracks (and cues) of the master pattern. This is kept current
    // and used during playback.
    public void refreshPatterns()
    {
        patternCache = new TreeMap<String, TreeMap<String, PatternNode>>();
        refInstances = new TreeMap<String, MachineBase>();

        // Read the master pattern, creating it if necessary.
        PatternNode masterNode = (PatternNode) Main.document.getValue("Patterns/" + SystemComposerPlayback.MASTER_PATTERN_NAME);

        if (masterNode == null)
        {
            masterNode = new PatternNode(SystemComposerPlayback.MASTER_PATTERN_NAME, 10000);

            // Since we have to write to the composition to add a timeline,
            // we reset the needsSaving flag for the case where we have
            // just created a new document.
            boolean docHadChanges = Main.document.needsSaving;
            Main.document.setValue("Patterns/" + SystemComposerPlayback.MASTER_PATTERN_NAME, masterNode);
            Main.document.needsSaving = docHadChanges;
        }

        cachePattern(masterNode, SystemComposerPlayback.MASTER_PATTERN_NAME, SystemComposerPlayback.MASTER_PATTERN_NAME);

        // Read machine patterns.
        ArrayList<UsefulFileObject> machines = Main.document.getChildren("Machines");

        for (UsefulFileObject machObj : machines)
        {
            MachineNode machine = (MachineNode) machObj;

            ArrayList<UsefulFileObject> patterns = Main.document.getChildren("Patterns/" + machine.machineName);

            if (patterns != null)
            {
                for (UsefulFileObject pattObj : patterns)
                {
                    PatternNode pattern = (PatternNode) pattObj;
                    cachePattern(pattern, machine.machineName, pattern.patternID);
                }
            }
        }
    }

    /**
     * Creates a new pattern in the composition for the selected machine and
     * displays it in the editor.
     */
    public void createPattern(MachineNode currMachine)
    {
        if (currMachine == null) return;

        TreeMap<String, PatternNode> patterns = patternCache.get(currMachine.machineName);
        ArrayList<String> pattNames = new ArrayList<String>();

        if (patterns != null)
        {
            for (PatternNode patt : patterns.values())
            {
                pattNames.add(patt.patternID);
            }
        }

        String newName = Main.chooseName("Pattern ", pattNames);

        PatternNode newNode = new PatternNode(newName);
        cachePattern(newNode, currMachine.machineName, newName);

        // Load the machine and get the parameter list.
        MachineBase mach = getMachInstance(currMachine.machineName);

        for (ParameterBase param : mach.parameters)
        {
            if (param.isTrackParam == false) createTrack(currMachine, newNode, param);
        }

        savePattern(currMachine.machineName, newName);
    }

    /**
     * Removes the currently selected pattern from the machine it belongs to.
     */
    void deletePattern(MachineNode currMachine, PatternNode currPattern)
    {
        if (currMachine != null && currPattern != null)
        {
            Main.document.delete("Patterns/" + currMachine.machineName + "/" + currPattern.patternID);
            patternCache.get(currMachine.machineName).remove(currPattern.patternID);
        }
    }

    // Creates a new track for the current pattern, given the name of the parameter
    // (or machine) in question and the type or that parameter.
    void createTrack(MachineNode currMachine, PatternNode currPattern, ParameterBase param)
    {
        if (currMachine == null || currPattern == null) return;

        ArrayList<String> existingNames = new ArrayList<String>();
        
        for (UsefulFileObject obj : currPattern.tracks.arrayValue)
        {
            PatternTrackNode track = (PatternTrackNode) obj;
            if (track.groupNumber == 0) existingNames.add(track.columnName);
        }
        
        String newName = Main.chooseName(param.parameterName + " ", existingNames);

        currPattern.tracks.add(new PatternTrackNode(newName, param.parameterName, param.parameterType, 0));

        savePattern(currMachine.machineName, currPattern.patternID);
    }

    /**
     * Deletes a track from the currently showing pattern, given the track name.
     * This is normally the name of the track where the cursor is sitting in
     * the tracker area. TODO: Edit followed by remove track causes save after
     * delete which fails (but reorders tracks).
     */
    void deleteTrack(MachineNode currMachine, PatternNode currPattern, String trackName)
    {
        if (currMachine == null || currPattern == null) return;

        for (UsefulFileObject obj : currPattern.tracks.arrayValue)
        {
            PatternTrackNode track = (PatternTrackNode) obj;
            if (track.groupNumber == 0 && track.columnName.equals(trackName))
            {
                currPattern.tracks.arrayValue.remove(obj);
                break;
            }
        }

        savePattern(currMachine.machineName, currPattern.patternID);
    }

    /**
     * Creates a new track group for the current pattern and adds the tracks
     * that make up the group.
     */
    public void createTrackGroup(MachineNode currMachine, PatternNode currPattern)
    {
        if (currMachine == null || currPattern == null) return;

        int newGroupNum = 1;
        for (UsefulFileObject obj : currPattern.tracks.arrayValue)
        {
            PatternTrackNode track = (PatternTrackNode) obj;
            if (track.groupNumber >= newGroupNum) newGroupNum = track.groupNumber+1;
        }

        MachineBase machine = getMachInstance(currMachine.machineName);

        for (ParameterBase param : machine.parameters)
        {
            if (param.isTrackParam) currPattern.tracks.add(
                    new PatternTrackNode(param.parameterName + "." + newGroupNum,
                    param.parameterName, param.parameterType, newGroupNum));
        }

        savePattern(currMachine.machineName, currPattern.patternID);
    }

    public void removeTrackGroup(MachineNode currMachine, PatternNode currPattern, int groupNum)
    {
        // TODO: Removing track groups from patterns. Remember to save.
    }


    /**
     * Returns and caches machine instances so we can look them up to get the
     * list of parameters and other information.
     */
    public MachineBase getMachInstance(String machineName)
    {
        MachineBase cached = refInstances.get(machineName);
        if (cached != null) return cached;

        MachineBase mach = (MachineBase) Main.plugins.getPlugin(machineName);
        mach.setupMachine();
        refInstances.put(machineName, mach);
        return mach;
    }

    ArrayList<UsefulFileObject> getPatterns(String machineName)
    {
        TreeMap<String, PatternNode> patterns = patternCache.get(machineName);

        if (patterns == null) return null;

        ArrayList<UsefulFileObject> pattList = new ArrayList<UsefulFileObject>();

        for (UsefulFileObject pattern : patterns.values())
        {
            pattList.add(pattern);
        }

        return pattList;
    }

    PatternNode getPattern(String machineName, String patternID)
    {
        TreeMap<String, PatternNode> patterns = patternCache.get(machineName);

        if (patterns == null) return null;

        return patterns.get(patternID);
    }

    ArrayList<UsefulFileObject> getPatternTracks(String machineName, String patternID)
    {
        PatternNode pattern = getPattern(machineName, patternID);

        if (pattern == null) return null;

        return pattern.tracks.arrayValue;
    }

    void savePattern(String machineName, String patternID)
    {
        PatternNode pattern = getPattern(machineName, patternID);
        if (patternID.equals(SystemComposerPlayback.MASTER_PATTERN_NAME))
            pattern = getPattern(patternID, patternID);
        if (pattern == null) return;

        String location = "Patterns/" + machineName + "/" + patternID;
        if (patternID.equals(SystemComposerPlayback.MASTER_PATTERN_NAME))
            location = "Patterns/" + patternID;

        System.out.println("Saving commands to " + location);

        Main.document.setValue(location, pattern);
    }

    private void cachePattern(PatternNode pattNode, String machineName, String patternID)
    {
        TreeMap<String, PatternNode> machPatterns = patternCache.get(machineName);

        if (machPatterns == null)
        {
            machPatterns = new TreeMap<String, PatternNode>();
            patternCache.put(machineName, machPatterns);
        }

        machPatterns.put(patternID, pattNode);
    }
}