/*
 * DataModel.java
 *
 * Created on February 17, 2006, 2:30 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package usda.weru.wmrm;

import com.klg.jclass.table.*;
import com.klg.jclass.table.data.*;
import com.klg.jclass.util.JCListenerList;
import de.schlichtherle.io.File;
import java.awt.EventQueue;
import java.io.Serializable;
import java.util.*;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.tree.*;
import javax.swing.event.*;
import usda.weru.wmrm.TableMeta.TableNamedDataModel;


/**
 *
 * @author Joseph Levin
 */
public final class DataModel extends AbstractDataSource implements TableNamedDataModel, TreeModel{
    private RunGroup c_rootGroup;
    private RunGroup c_currentProject;
    private RunGroup c_otherRuns;
    private RunGroup c_projects;
    private RunGroup c_selectedGroup;
    private List <RunWrapper> c_selectedRuns;
    private RunsWatcher c_watcher;
    private List <TreeModelListener> c_treeModelListeners;
    private List <String> c_dataKeys;
    
   
    
    private Map <File, RunGroup> c_groupLookup;
      
    
    private void init(){
        c_groupLookup = new HashMap <File, RunGroup> ();
        c_treeModelListeners = new Vector <TreeModelListener> ();
    }
    
    public DataModel() {
        init();
        //Create root node for the tree;
        c_rootGroup = new RunGroup("All Run Summaries");        
        c_currentProject = new RunGroup( "Current Project (none)");
        c_currentProject.setSortPriority(0);
        c_currentProject.setParent(c_rootGroup);
        c_currentProject.setExcludeFromRecursion(true);        
        c_rootGroup.addChild(c_currentProject);
        
        c_projects = new RunGroup("Directories");
        c_projects.setSortPriority(2);
        c_projects.setParent(c_rootGroup);    
        c_rootGroup.addChild(c_projects);
        
        c_otherRuns = new RunGroup("Single Runs");
        c_otherRuns.setSortPriority(3);
        c_otherRuns.setParent(c_rootGroup);    
        c_rootGroup.addChild(c_otherRuns);
        
        selectRunGroup(c_rootGroup);
        c_watcher = new RunsWatcher(this);
        c_watcher.start();
        
    }
    
    public void cleanup(){
        c_watcher.interrupt();
    }
    
    public RunGroup getRootGroup(){
        return c_rootGroup;
    }
    
    
    /**
     * Select a node, table data will reflect the selected tree node.
     */
    public void selectRunGroup(RunGroup group){
        selectRunGroups(group);
    }
    
    public void selectRunGroups(RunGroup... groups){
        if (groups.length == 0){
            c_selectedGroup = null;
            return;
        }
        else if(groups.length == 1){
            c_selectedGroup = groups[0];
        }
        else{
            c_selectedGroup = new RunGroup("Selected");
            for (RunGroup group : groups){
                c_selectedGroup.addChild(group);
            }
        }
        updateSelectedRuns();        
    }
    
    private void updateSelectedRuns(){
        c_selectedRuns = c_selectedGroup.getRuns(true);
        //Tell the table the data has updated
        fireDataReset();
    }

    @Override
    public void fireDataReset() {
        if(SwingUtilities.isEventDispatchThread()){
            super.fireDataReset();
        }
        else{
            SwingUtilities.invokeLater(new Runnable(){

                @Override
                public void run() {
                    DataModel.super.fireDataReset();
                }

            });
        }
    }



    public void setCurrentProject(RunGroup runGroup){ 
        if (runGroup == null){
            //System.err.println("Run Group is NULL!");
            return;
        }
        c_currentProject.setName("[ " + runGroup.toString() +" ]");
        c_currentProject.setChildren(runGroup.getChildren());
        c_currentProject.setRuns(runGroup.getRuns(false));
        fireTreeNodesChanged(c_currentProject);
    }

    public void addRunFileToModel(RunGroup group, File file){
        if (file == null ) return;
                
        RunWrapper run = new RunWrapper(file.getPath());
        if (group == null){
            //Try to find the directory group
            group = lookupRunGroup(new File(file.getParentFile()));
            if (group == null){
                //No luck, add it as a single run.
                group = c_otherRuns;
            }
        }
        RunGroup wrapperGroup = new RunGroup(file.getName());
        wrapperGroup.addRun(run);
        group.addChild(wrapperGroup);   
        wrapperGroup.setParent(group);
        fireTreeNodesInserted(wrapperGroup);
              
        updateSelectedRuns();
    }
    
    public RunGroup lookupRunGroup(File directory){
        return c_groupLookup.get(directory);       
    }
    
           
    public RunGroup addDirectoryToModel (File directory){
        if(!directory.exists()){
            JOptionPane.showMessageDialog(null, "Location not found:\n" + directory.getPath(), "Path Warning", JOptionPane.WARNING_MESSAGE);
            return null;
        }
        if (!directory.isDirectory()) return null;
        if (lookupRunGroup (directory) != null) return lookupRunGroup (directory);
        
        RunFileFilter filter = new RunFileFilter(true);
        if(filter.accept(directory, false)){
            addRunFileToModel(null, directory);
            return null;
        }
        
        RunGroup dirGroup = new RunGroup(directory.getName());
        dirGroup.setSourceFile(directory);
        
        RunGroup parent = c_groupLookup.get(directory.getParentFile());
        if (parent == null){
            parent = c_projects;
        }
        
        dirGroup.setParent(parent);                
        parent.addChild(dirGroup);
        
        c_groupLookup.put(directory, dirGroup);
        
        fireTreeNodesInserted(dirGroup);
        
        
        
        File[] children = directory.listFiles(filter, File.getDefaultArchiveDetector());
        for (File child : children){
            if (filter.accept(child, false)){
                addRunFileToModel(dirGroup, child);
            }
            else if (child.isDirectory()){                
                addDirectoryToModel(child);
            }
        }        
        
        
        //Sort
        Collections.sort((List) c_projects.getChildren());
        fireTreeStructureChanged(c_projects);
        return dirGroup;
    }
     
    public void removeRunGroupFromModel(RunGroup group){
        RunGroup parent = group.getParent();
        group.setParent(null);        
        parent.removeChild(group);
        if (c_selectedGroup == group){
            selectRunGroup(parent);
        }
        fireTreeStructureChanged(parent);
        updateSelectedRuns();
    }
    
    private synchronized void buildDataKeyList(){        
        List <String> keys = new LinkedList <String> ();        
        for(RunWrapper run : c_selectedRuns){
            for (String key : run.getDataKeys()){
                if (!keys.contains(key)){
                    keys.add(key);                    
                }
            }
        }
        c_dataKeys = keys;            
    }
    
    private String getDataKeyFromIndex(int columnIndex){
        switch(columnIndex){
            case 0:
                return RunWrapper.DataTag.RunName.getFirstTag();
            case 1:
                return RunWrapper.DataTag.RunLocation.getFirstTag();
            case 2:
                return RunWrapper.DataTag.ManagementName.getFirstTag();
            case 3:
                return RunWrapper.DataTag.SoilName.getFirstTag();
            case 4:
                return RunWrapper.DataTag.FieldSize.getFirstTag();
            default:
                try{
                    if (c_dataKeys == null || c_dataKeys.size() == 0){ 
                        buildDataKeyList();
                    }
                    return c_dataKeys.get(columnIndex);
                }
                catch(NullPointerException npe){
                    return null;
                }
                catch(ArrayIndexOutOfBoundsException aioobe){
                    return "";
                }
                catch(IndexOutOfBoundsException ioobe){
                    return "";
                }
        }
        
    }

    
    
 
    //JCTable Interface ************************************************
    public Object getTableDataItem(int rowIndex, int columnIndex) {
        String dataKey = getDataKeyFromIndex(columnIndex);        
        return getTableDataItem(rowIndex, dataKey);
    }

    @Override
    public Object getTableDataItem(int rowIndex, String column) {
        try{            
            Object value = c_selectedRuns.get(rowIndex).getValue(column);
            return value;  
        }
        catch(ArrayIndexOutOfBoundsException aioob){
            return null;
        }
    }
    
    
    
    
    public String getTableDataToolTipText(int rowIndex, int columnIndex){
        String dataKey = getDataKeyFromIndex(columnIndex);
        try{
            return c_selectedRuns.get(rowIndex).getToolTipText(dataKey);            
        }
        catch(ArrayIndexOutOfBoundsException aioob){
            return null;
        }
    }
    
    public RunWrapper getRunWrapper(int rowIndex){        
        return c_selectedRuns.get(rowIndex);        
    }

    public int getNumRows() {
      return c_selectedRuns.size();
    }

    public int getNumColumns() {
        if (c_dataKeys == null){
            buildDataKeyList();
        }
        if (c_dataKeys != null){
            return c_dataKeys.size();
        }
        else{
            return 0;  
        }
        
    }

    public Object getTableRowLabel(int rowIndex) {
      return rowIndex;
    }

    public Object getTableColumnLabel(int columnIndex) {
      if (c_dataKeys != null){
            return c_dataKeys.get(columnIndex);
        }
        else{
            return Integer.toString(columnIndex);  
        }
    }
    

    //********************************************
    
    //JTree Interface ************************************************
    
    
    public Object getChild(Object parent, int index){
        try {
            RunGroup parentGroup = (RunGroup) parent;
            return parentGroup.getChild(index);            
        } catch (ArrayIndexOutOfBoundsException aioob) {
            return null;
        }
    }
    
    public int  getChildCount(Object parent){
        RunGroup parentGroup = (RunGroup) parent;
        return parentGroup.getChildCount();
    }
    
    public int getIndexOfChild(Object parent, Object child){
        try{
            RunGroup parentGroup = (RunGroup) parent;
            RunGroup childGroup = (RunGroup) child;
            return parentGroup.getIndexOfChild(childGroup);            
        } catch (ArrayIndexOutOfBoundsException aioob) {
            return 0;
        }
    }
    
    public Object getRoot(){
        return c_rootGroup;
    }
    
    public boolean isLeaf(Object node){        
        RunGroup group = (RunGroup) node;
        return group.getChildCount() == 0;
    }
    
    public void addTreeModelListener(TreeModelListener l){
        c_treeModelListeners.add(l);
    }
    
    public void removeTreeModelListener(TreeModelListener l){
        c_treeModelListeners.remove(l);
    }
    
    public void valueForPathChanged(TreePath path, Object newValue){
        
    }
    
    //Tree events
    public void fireTreeStructureChanged(RunGroup group){        
        TreeModelEvent event = new TreeModelEvent(this, getPathToNode(group));
        for (TreeModelListener listener : c_treeModelListeners){
            listener.treeStructureChanged(event);
        }
    }
    
    public void fireTreeNodesChanged(RunGroup group){        
        TreeModelEvent event = new TreeModelEvent(this, getPathToNode(group));
        for (TreeModelListener listener : c_treeModelListeners){
            listener.treeNodesChanged(event);
        }
    }
        
    public void fireTreeNodesInserted(RunGroup group){        
        TreePath parentPath = getPathToNode(group.getParent());
        int[] childIndices = {getIndexOfChild(group.getParent(), group)};
        Object[] children = {group};
        TreeModelEvent event = new TreeModelEvent(this, parentPath, childIndices, children);
        for (TreeModelListener listener : c_treeModelListeners){
            listener.treeNodesInserted(event);
        }
    }
            
    public void fireTreeNodesRemoved(RunGroup group){        
        TreeModelEvent event = new TreeModelEvent(this, getPathToNode(group.getParent()));
        for (TreeModelListener listener : c_treeModelListeners){
            listener.treeNodesRemoved(event);
        }
    }

    
    //********************************************
    
    public TreePath getPathToNode(RunGroup group){
        Vector <RunGroup> path = new Vector <RunGroup> ();        
        while (group != null){
            path.add(group);
            group = group.getParent();
        }
        Collections.reverse(path);
        return new TreePath(path.toArray());
    }

    public void addTableDataListener(JCTableDataListener jCTableDataListener) {        
        listeners = JCListenerList.add(listeners, jCTableDataListener);
    }
    
    static class RunGroup implements Comparable, Serializable {
        private List <RunGroup> c_children;
        private List <RunWrapper> c_runs;
        private boolean c_excludeFromRecursion = false;
        private boolean c_removeFlag;
        private int c_sortPriority = 100;        //Low = at the top of the tree;
        private File c_sourceFile;
        
        public RunGroup (String name){            
            c_runs = Collections.synchronizedList(new LinkedList <RunWrapper> ());
            c_name = name;
        }
        
        public Vector <RunWrapper> getRuns(){
            return getRuns(false);
        }
        
        public Vector <RunWrapper> getRuns(boolean recursive){
            Vector <RunWrapper> tempRunList = new Vector <RunWrapper> ();
            for (RunWrapper run : c_runs){
                if (!tempRunList.contains(run)) tempRunList.add(run);
            }     
            if (recursive){
                List <RunGroup> children = getChildren();
                synchronized(children){
                    for (RunGroup group : children){
                        if (group.getExcludeFromRecursion()) continue;
                        for (RunWrapper run : group.getRuns(true)){
                            if (!tempRunList.contains(run)) tempRunList.add(run);
                        } 
                    }
                }
            }
            return tempRunList;
        }
        
        public void addChild(RunGroup child){                        
            //We don't want to add the group if we already have it in our model                
            if (!getChildren().contains(child)){
                //synchronize access to the children
                getChildren().add(child);
            }            
        }
        
        public List <RunGroup> getChildren(){
            synchronized(this){
                if (c_children == null){
                    c_children = Collections.synchronizedList(new LinkedList<RunGroup>());
                }
                return c_children;
            }            
        }
        
        public void setChildren(List <RunGroup> children){
            synchronized(this){
                c_children = children;
            }
        }
        
        public int getIndexOfChild(RunGroup child){
            return getChildren().indexOf(child);
        }
        
        public int getChildCount(){
            return getChildren().size();
        }
        
        public RunGroup getChild(int index){
            synchronized(getChildren()){
                return getChildren().get(index);
            }
        }
        
        public void removeChild(final RunGroup child){
            getChildren().remove(child);

        }
        
        public boolean getExcludeFromRecursion(){
            return c_excludeFromRecursion;
        }
        
        public void setExcludeFromRecursion(boolean exclude){
            c_excludeFromRecursion = exclude;
        }
        
//        public Vector <RunGroup> getChildren(){
//            return c_children;
//        }     
                
        public void addRun(RunWrapper run){
            c_runs.add(run);
        }
        
        private String c_name = "Unknown";

        public int hashCode() {
            return c_name.hashCode();
        }
        private RunGroup c_parent = null;
        
        
        
        public void setSourceFile(File file){
            c_sourceFile = file;
        }
        
        public File getSourceFile(){
            return c_sourceFile;
        }
        
        public boolean getRemoval(){
            return c_removeFlag;
        }
        
        public void markRemoval(boolean remove){
            c_removeFlag = remove;
        }
        
        
        public boolean containsRunFile(File runFile){
            runFile = new File(runFile.getAbsoluteFile());
            return containsRunFile(this, runFile);
        }
        
        private boolean containsRunFile(RunGroup group, File runFile){ 
            synchronized(this){
                for (RunWrapper run : group.getRuns()){
                    if (run.getDirectory().getAbsoluteFile().equals(runFile)){
                        return true;                    
                    }
                }                
                List <RunGroup> children = group.getChildren();                                
                synchronized(children){
                    Iterator<RunGroup> i = children.iterator();
                    while (i.hasNext()){
                        RunGroup child = i.next();
                        if (containsRunFile(child, runFile)){
                            return true;
                        }
                    }
                }
                return false;
            }
        }
        
        public RunGroup getParent (){
            return c_parent;
        }        
        
        public void setParent(RunGroup parent){
            c_parent = parent;
        }
        
        public String toString(){
            return c_name;
        }
        
        public void setName(String name){
            c_name = name;
        }
        
        public void setRuns (Vector <RunWrapper> runs){
            c_runs = runs;
        }
                
        public int getSortPriority(){
            return c_sortPriority;
        }
        
        public void setSortPriority(int priority){
            c_sortPriority = priority;
        }
        
        @Override
        public int compareTo(Object o){
            RunGroup group = (RunGroup) o;
            int p1 = getSortPriority();
            int p2 = group.getSortPriority();
            if (p1 == p2){
                //Same priority, now by string
                return toString().compareTo(group.toString());
            }
            else{
                //Not the same priority;
                if (p1 > p2) return 1;
                else return -1;
            }           
        }
        
        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof RunGroup)) return false;
            return (this.compareTo(obj) == 0) ? true : false;
        }
    }    
    
    public void checkRunGroup(){
        checkRunGroup(getRootGroup());        
    }
    
    private void checkRunGroup(RunGroup group){
        if(group == null){
            return;
        }
        
        File dir = group.getSourceFile();

        //If the run is not up to date then update and fire a datachange event
        for (RunWrapper run : group.getRuns()){
            //Check if the run is still there.
            if(run.exists()){
                //Check that the run is current
                if (!run.upToDate()){
                    run.updateData();
                    //Fire a data change event here.
                    updateSelectedRuns();
                }
            }
            else{
                //Run has been removed from the file system.                    
                group.markRemoval(true);                    
                return;
            }
        }

        //Add new runs
        if (dir != null){                
            for (java.io.File file : dir.listFiles(new RunFileFilter())){
                File file2 = new File(file);
                if (group.containsRunFile(file2) == false){
                    addRunFileToModel(group, file2);                        
                }
            }
        }            

        //Continue recursivly, uses ListIterator to allow removal
        List <RunGroup> children = group.getChildren();
        boolean updated = false;
        synchronized (children){
            Iterator <RunGroup> i = group.getChildren().listIterator();
            while (i.hasNext()){
                final RunGroup child = i.next();
                checkRunGroup(child);
                if (child.getRemoval()){
                    i.remove();
                    EventQueue.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            fireTreeStructureChanged(child.getParent());
                        }
                    });
                    updated = true;                        
                }                
            }   
        }
        if (updated){
            updateSelectedRuns();
        }                     
    }

    
    class RunsWatcher extends Thread implements Serializable {
        private DataModel c_model;
        private int c_sleep = 5000;
        
        public RunsWatcher(DataModel model){
            c_model = model;
        }
        
                
        public void run(){            
            while(true){
                try{                    
                    checkRunGroup(c_model.getRootGroup());                                       
                    //Sleep awhile, then wake up and check the runs again.
                    sleep(c_sleep);
                }
                catch(InterruptedException ie){
                    //check right now!
                    break;
                }
            }
        }
    }
        
}

