/*
 * RemoteTree.java
 *
 * Created on February 5, 2005, 5:43 PM
 */

package hep.aida.ref.remote;

import java.io.IOException;
import java.util.*;
import java.util.logging.*;

import hep.aida.*;
import hep.aida.dev.*;
import hep.aida.ref.event.*;
import hep.aida.ref.*;
import hep.aida.ref.remote.*;
import hep.aida.ref.remote.interfaces.*;

import org.freehep.util.FreeHEPLookup;
import org.openide.util.Lookup;


/**
 *
 * @author  serbo
 */
public class RemoteTree extends AIDAObservable implements IDevTree, IsObservable {
    protected IStore  aidaStore;
    private String    storeName;
    private boolean   readOnly;
    private boolean   readOnlyUserDefined;
    private boolean   appendAxisType;
    private boolean   createNew;
    protected String  storeType = null;
    protected Map     optionsMap;
    protected boolean overwrite;
    protected Vector  updateBuffer;
    protected Logger  remoteLogger;
    
    private Object lock;
    protected TreeMap map = new java.util.TreeMap();
    
    
    /** Creates a new instance of RemoteTree */
    public RemoteTree() {
        this("Tree");
    }    
    public RemoteTree(String name) {
        this(name, null);
    }    
     public RemoteTree(String name, IDevMutableStore aidaStore) {
        this(name, aidaStore, false);
    }    
    public RemoteTree(String name, IDevMutableStore aidaStore, boolean overwrite) {
        this(name, aidaStore, overwrite, false);        
    }
    public RemoteTree(String name, IDevMutableStore aidaStore, boolean overwrite, boolean appendAxisType) {
        super();
        this.storeName = name;
        this.aidaStore = aidaStore;
        this.lock = new Object();
        this.overwrite = overwrite;
        this.remoteLogger = Logger.getLogger("hep.aida.ref.remote");
        this.appendAxisType = appendAxisType;
        this.updateBuffer = new Vector();
        setIsValidAfterNotify(true);
        addFolder("/");
    }
    
    
    // Service methods
    
    public void submitEventToListeners(AidaUpdateEvent ev) {
        remoteLogger.finest("RemoteTree.submitEventToListeners ::  id="+ev.id()+", path="+ev.path());
        if (ev instanceof RemoteUpdateEvent) fireStateChanged((EventObject) ev);
        else {
            int id = ev.id();
            String path = ev.path();
            String type = ev.nodeType();
            RemoteUpdateEvent rev = new RemoteUpdateEvent(id, path, type);
            fireStateChanged(rev);
        }
    }
    
    public void init( String storeName, boolean readOnly, boolean createNew, String storeType, String options, boolean readOnlyUserDefined ) throws IOException {
        this.storeName = storeName;
        this.readOnly = readOnly;
        this.readOnlyUserDefined = readOnlyUserDefined;
        this.createNew = createNew;
        this.storeType = storeType;
        
        if (readOnly && createNew) throw new IllegalArgumentException("readOnly and createNew not allowed");
        optionsMap = AidaUtils.parseOptions( options );
        
        if (storeName!=null && storeName.length()>0 && ! createNew) {
            if (getLock() != null) {
                synchronized (getLock()) {
                    createStore().read(this,optionsMap, readOnly, createNew);                    
                }
            } else {
                createStore().read(this,optionsMap, readOnly, createNew);
            }
        }
    }
    protected IStore createStore() throws IOException
    {
       if (aidaStore == null)
       {
          if (storeType == null || storeType.length()==0) storeType = "xml";

         // Look for a handler for this storeType
         Lookup.Template template = new Lookup.Template(IStoreFactory.class);
         Lookup.Result result = FreeHEPLookup.instance().lookup(template);
         for (Iterator i = result.allInstances().iterator(); i.hasNext(); )
         {
            IStoreFactory factory = (IStoreFactory) i.next();
            if (factory.supportsType(storeType))
            {   
               //System.out.println("Got item for type: "+storeType+",  "+item.getClass()+",  "+item);
               aidaStore =  factory.createStore();
               
               //If the readOnly flag has not been set by the user AND the store is
               //read-only, then we default to read-only=true.
               //FIX for JAIDA-46
               if ( ! readOnlyUserDefined && aidaStore.isReadOnly() )
                   this.readOnly = true;               
               if ( aidaStore.isReadOnly() && ( ! isReadOnly() ) )
                   throw new IllegalArgumentException("When opening a read-only file, the associated tree must be read-only. Please correct the options with which you created the tree.");
               return aidaStore;
            }
         }
         throw new IOException("Unknown store type: "+storeType);
       }
       else return aidaStore;
    }

    public String correctPath(String path, boolean isDir) {
        String tmpPath = path;
        if (tmpPath.equals("/")) return tmpPath;
        tmpPath.replaceAll("///", "/");
        tmpPath.replaceAll("//", "/");
        if (!tmpPath.startsWith("/")) tmpPath = "/"+tmpPath;
        if (isDir && !tmpPath.endsWith("/")) tmpPath = tmpPath+"/";
        if (!isDir && tmpPath.endsWith("/")) tmpPath = tmpPath.substring(0, tmpPath.length()-1);
        return tmpPath;
    }
    /*
    public String parseName(String path) {
        String tmp = path;
        if (tmp == null || tmp.equals("/") || tmp.trim().equals("")) return "";
        if (tmp.startsWith("/")) tmp = tmp.substring(1, tmp.length());
        if (tmp.endsWith("/")) tmp = tmp.substring(0, tmp.length()-1);
        int index = tmp.lastIndexOf("/");
        if (index <= 0) return tmp;
        else if (index == tmp.length()) return null;
        String objectName = tmp.substring(index+1);
        return objectName;
    }
    
    public String parseFolder(String path) {
        String tmp = path;
        if (tmp == null || tmp.equals("/") || tmp.trim().equals("")) return tmp;
        if (tmp.startsWith("/")) tmp = tmp.substring(1, tmp.length());
        int index = tmp.lastIndexOf("/");
        if (index <= 0) return null;
        String folderName = tmp.substring(0, index);
        return folderName;
    }
      */  
    protected String[] executeListObjectNames(String path, boolean recursive) {
        String[] names = null;
        remoteLogger.finest("RemoteTree.executeListObjectNames for recursive="+recursive+",  Path: "+ path);
        Map tailMap = map.tailMap(path);
        ArrayList list = new ArrayList(tailMap.size());
        Iterator tailIt = tailMap.keySet().iterator();
        while (tailIt.hasNext()) {
            String key = (String) tailIt.next();
            if (path.equals(key)) continue;
            else if (recursive || path.equals("/")) {
                list.add(key);
            } else if (key.startsWith(path)) {
                //if (!path.equals(key)) {
                    String folder = AidaUtils.parseDirName(key);
                    if (path.equals(folder)) list.add(key);
                //}
            }
        }
        list.trimToSize();
        names = new String[list.size()];
        list.toArray(names);
        
        return names;
    }
    
    protected String[] executeListObjectTypes(String path, boolean recursive) {
        String[] types = null;
        remoteLogger.finest("RemoteTree.executeListObjectTypes for recursive="+recursive+",  Path: "+ path);
        Map tailMap = map.tailMap(path);
        ArrayList list = new ArrayList(tailMap.size());
        Iterator tailIt = tailMap.keySet().iterator();
        while (tailIt.hasNext()) {
            String key = (String) tailIt.next();
            String type = null;
            if (path.equals(key)) continue;
            else if (key.startsWith(path)) {
                
                boolean acceptKey = false;
                if (recursive || path.equals("/")) {
                    acceptKey = true;
                } else if (!path.equals(key)) {
                    String folder = AidaUtils.parseDirName(key);
                    if (path.equals(folder)) acceptKey = true;
                }
                
                if (acceptKey) {
                    Object obj = map.get(key);
                    if (obj instanceof ManagedObject) type = ((ManagedObject) obj).getAIDAType();
                    else type = obj.getClass().getName();
                    if (appendAxisType) {
                        if (obj instanceof RemoteManagedObject) {
                            String xType = "double";
                            RemoteManagedObject rmo = (RemoteManagedObject) obj;
                            rmo.setFillable(true);
                            Annotation a = null;
                            if (rmo instanceof IBaseHistogram) {
                                try {
                                    a = (Annotation) ((IBaseHistogram) rmo).annotation();
                                    a.setFillable(true);
                                    String tmp = a.value("xAxisType");
                                    if (tmp != null && !tmp.equals("")) {
                                        xType = tmp;
                                        type = type + ":" + xType;
                                    }
                                } catch (IllegalArgumentException e) {}
                            } else if (rmo instanceof IDataPointSet) {
                                try {
                                    a = (Annotation) ((IDataPointSet) rmo).annotation();
                                    a.setFillable(true);
                                    String tmp = a.value("xAxisType");
                                    if (tmp != null && !tmp.equals("")) {
                                        xType = tmp;
                                        type = type + ":" + xType;
                                    }
                                } catch (IllegalArgumentException e) {}
                            }
                            if (a != null) a.setFillable(false);
                            rmo.setFillable(false);
                        }
                    }
                    list.add(type);
                }
            }
        }
        list.trimToSize();
        types = new String[list.size()];
        list.toArray(types);
        
        return types;
    }

    public IManagedObject executeFind(String path) {
        String p = correctPath(path, false);
        if (!map.containsKey(p)) throw new IllegalArgumentException("Object does not exist for path: "+p);
        IManagedObject obj = (IManagedObject) map.get(p);
        //remoteLogger.finest("RemoteTree.executeFind path="+path+", correctedPath="+p+", object="+obj);
        return obj;
    }
    
    protected void executeClose() {
        if (aidaStore != null) {
            try {
                aidaStore.close();
            } catch (IOException ioe) { ioe.printStackTrace(); }
        }
        map.clear(); 
    }
    
    // Next four methods are used by Store to change Tree structure
    // Store must provide correct path String
    // Those methods must not be called by anybody except the Store
    public void addObject(String path, IManagedObject object) throws IllegalArgumentException {
        String p = correctPath(path, true);
        String fullPath = p + AidaUtils.modifyName(object.name());
        remoteLogger.finest("RemoteTree.addObject path="+path+", correctedPath="+p+",  fullPath="+fullPath+", object="+object);
        if (map.containsKey(fullPath) && !overwrite) throw new IllegalArgumentException("Object already exists for path: "+fullPath);
        map.put(fullPath, object);
    }
    
    public void addFolder(String path) throws java.lang.IllegalArgumentException {
        String p = correctPath(path, true);
        //if (map.containsKey(path) && !overwrite) throw new IllegalArgumentException("Folder already exists for path: "+path);
        Object obj = map.get(p);
        if (obj != null) return;
        String objectName = AidaUtils.parseName(p);
        String folderName = AidaUtils.parseDirName(p);
        RemoteFolder f = new RemoteFolder(objectName);
        f.setTreeFolder(folderName);
        map.put(p, f);
    }
    
    void removeObject(String path) throws java.lang.IllegalArgumentException {
        String p = correctPath(path, false);
        if (!map.containsKey(p)) throw new IllegalArgumentException("Object does not exist for path: "+p);
        Object obj = map.remove(p);
         remoteLogger.finest("RemoteTree.removeObject path="+path+", correctedPath="+p+", object="+obj);
         //updateBuffer.remove(path);
   }
    
    void removeFolder(String path) throws java.lang.IllegalArgumentException {
        String p = correctPath(path, true);
        if (!map.containsKey(p)) throw new IllegalArgumentException("Folder does not exist for path: "+p);
        remoteLogger.finest("RemoteTree.removeFolder path="+path+", correctedPath="+p);
        Map tailMap = map.tailMap(p);        
        Set keySet = tailMap.keySet();
        int size = keySet.size();
        Object[] a = new Object[size];
        keySet.toArray(a);
        for (int i=0; i<size; i++) {
            String key = (String) a[i];
            if (key.startsWith(p)) {
                Object obj = map.remove(key);
                //updateBuffer.remove(key);
            }
        }
    }
        
    String executeFindPath(IManagedObject iManagedObject) {
        String path = null;
        String key = null;
        Iterator it = map.keySet().iterator();
        while (it.hasNext()) {
            key = (String) it.next();
            if (map.get(key).equals(iManagedObject)) {
                path = key;
                break;
            }
        }
        return path;
    }

    int executeUpdate(String updatePath) {
        if (updateBuffer.isEmpty()) return 0;
        Object[] paths = null;
        if (lock != null) {
            synchronized (lock) {
                paths = updateBuffer.toArray();
                updateBuffer.clear();
            }
        } else {
            paths = updateBuffer.toArray();
            updateBuffer.clear();            
        }
        int nObjects = paths.length;
        
        for (int i=0; i<paths.length; i++) {
            String path = (String) paths[i];
            if (updatePath == null || updatePath.equals("/") || path.startsWith(updatePath)) {
                try {
                    Object obj = (IManagedObject) map.get(path);
                    remoteLogger.finest("RemoteTree.executeUpdate: Path="+path+",  object="+obj);
                    System.out.println("RemoteTree.executeUpdate: Path="+path+",  object="+obj);
                    if (obj == null) continue;
                    if (obj instanceof RemoteManagedObject) {
                        RemoteManagedObject rmo = (RemoteManagedObject) obj;
                        if (rmo.isDataValid()) rmo.setDataValid(false);
                    } else if (obj instanceof ManagedObject) {
                        ManagedObject mo = (ManagedObject) obj;
                        // don't do anything here for now
                    }
                } catch (Exception e) {
                    remoteLogger.log(Level.INFO, "RemoteTree.executeUpdate  Exception for Path: "+path, e);
                    remoteLogger.log(Level.FINEST, "", e.getStackTrace());
                }
            }
        }
        return nObjects;
    }
    
    public int doUpdate(String updatePath) {
         if (lock != null) {
            //synchronized (lock) {
                return executeUpdate(updatePath);
            //}
        } else {
            return executeUpdate(updatePath);
        }
    }
    
    // IDevTree methods
    
    public Object getLock() { return lock; }
    
    public void setLock(Object lock) { this.lock = lock; }
    
    public void close() throws java.io.IOException {
        if (lock != null) {
            synchronized (lock) {
                executeClose();
            }
        } else {
            executeClose();
        }
        removeAllListeners();
    }
    
    public hep.aida.IManagedObject find(String path) throws java.lang.IllegalArgumentException {
        String p = correctPath(path, false);
        IManagedObject mo = null;
        if (lock != null) {
            synchronized (lock) {
                mo = executeFind(p);
                if (!updateBuffer.contains(p)) updateBuffer.add(p);
            }
        } else {
            mo = executeFind(p);
            if (!updateBuffer.contains(p)) updateBuffer.add(p);
        }
        return mo;
    }
    
    public void hasBeenFilled(String path) throws IllegalArgumentException {
        return;
    }
    
    public String[] listObjectNames(String path) throws java.lang.IllegalArgumentException {
        return listObjectNames(path, false);
    }
    
    public String[] listObjectNames(String path, boolean param) throws java.lang.IllegalArgumentException {
        String p = correctPath(path, true);
        if (lock != null) {
            synchronized (lock) {
                return executeListObjectNames(p, param);
            }
        } else {
            return executeListObjectNames(p, param);
        }
    }
    
    public String[] listObjectTypes(String path) throws java.lang.IllegalArgumentException {
        return listObjectTypes(path, false);
    }
    
    public String[] listObjectTypes(String path, boolean param) throws java.lang.IllegalArgumentException {
        String p = correctPath(path, true);
        if (lock != null) {
            synchronized (lock) {
                return executeListObjectTypes(p, param);
            }
        } else {
            return executeListObjectTypes(p, param);
        }
    }
    
    public String findPath(hep.aida.IManagedObject iManagedObject) throws java.lang.IllegalArgumentException {
       if (lock != null) {
            synchronized (lock) {
                return executeFindPath(iManagedObject);
            }
        } else {
            return executeFindPath(iManagedObject);
        }
    }
    
    public void setOverwrite() {
        overwrite = true;
    }
    
    public void setOverwrite(boolean param) {
        overwrite = param;
    }
    
    public String storeName() {
        return storeName;
    }
    
    public boolean isReadOnly() {
        return readOnly;
    }
    
    
    // Unsupported IDevTree methods below
    
    public void add(String path, IManagedObject object) throws IllegalArgumentException {
        throw new UnsupportedOperationException();
    }

    public void mkdir(String str) throws java.lang.IllegalArgumentException {
         throw new UnsupportedOperationException();
   }
    
    public void mkdirs(String str) throws java.lang.IllegalArgumentException {
         throw new UnsupportedOperationException();
   }
    
    public void rm(String str) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void rmdir(String str) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void symlink(String str, String str1) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void unmount(String str) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
     public void mount(String str, hep.aida.ITree iTree, String str2) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void mv(String str, String str1) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public String pwd() {
        throw new UnsupportedOperationException();
    }
    
    public void ls() throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void ls(String str) throws java.lang.IllegalArgumentException {
         throw new UnsupportedOperationException();
   }
    
    public void ls(String str, boolean param) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void ls(String str, boolean param, java.io.OutputStream outputStream) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void cd(String str) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void commit() throws java.io.IOException {
        throw new UnsupportedOperationException();
    }
    
    public void cp(String str, String str1) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public void cp(String str, String str1, boolean param) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
    public hep.aida.ITree findTree(String str) throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
     public String[] listObjectNames() throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
     public String[] listObjectTypes() throws java.lang.IllegalArgumentException {
        throw new UnsupportedOperationException();
    }
    
 
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        java.util.logging.Logger remoteLogger = java.util.logging.Logger.getLogger("hep.aida.ref.remote");
        remoteLogger.setLevel(Level.FINEST);
        Handler[] handlers = remoteLogger.getHandlers();
        for (int i=0; i<handlers.length; i++) { 
            handlers[i].setLevel(Level.FINEST);
        }
        
        RemoteTree tree = new RemoteTree();       
        tree.addFolder("/hist/");
        tree.addFolder("/hist/dir0/");
        tree.addObject("/hist/dir0/", new RemoteHistogram1D("h01"));
        tree.addObject("/hist/dir0/", new RemoteHistogram1D("h02"));
        tree.addObject("/hist/dir0/", new RemoteHistogram1D("h03"));

        tree.addFolder("/hist/dir1/");
        tree.addObject("/hist/dir1/", new RemoteHistogram1D("h11"));
        tree.addObject("/hist/dir1/", new RemoteHistogram1D("h12"));
        tree.addObject("/hist/dir1/", new RemoteHistogram1D("h13"));
        tree.addFolder("/hist/dir1/subdir/");
        tree.addObject("/hist/dir1/subdir/", new RemoteHistogram1D("h13"));
        
        tree.addFolder("/hist/dir2/");
        tree.addObject("/hist/dir2/", new RemoteHistogram1D("h21"));
        tree.addObject("/hist/dir2/", new RemoteHistogram1D("h22"));
        tree.addObject("/hist/dir2/", new RemoteHistogram1D("h23"));
        
        String path = "/hist/dir1/h12";
        Object obj = tree.find(path);
        System.out.println("PATH="+path+",  FOUND="+obj);
        
        path = "/hist/dir2/h22";
        obj = tree.find(path);
        System.out.println("PATH="+path+",  FOUND="+obj);
        
        path = "/hist/dir2/h23";
        obj = tree.find(path);
        System.out.println("PATH="+path+",  FOUND="+obj);
        
        
         //hep.aida.ref.tree.TreeUpdater updater = new hep.aida.ref.tree.TreeUpdater(tree);
        //updater.startUpdating();
        
        path = "/JasServerInfo/Free\\/Used Memory";
                    String name = AidaUtils.parseName(path);
                    String objDir = AidaUtils.parseDirName(path);
                    
                    IManagedObject h = new RemoteHistogram1D(name);
                    
                    // Make sure all directories in the path exist.
                    if(tree instanceof RemoteTree) ((RemoteTree) tree).addFolder(objDir);
                    else tree.mkdirs(objDir);
                    
                    if(tree instanceof RemoteTree) ((RemoteTree) tree).addObject(objDir, h);
                    else tree.add(objDir, h);
                    
                    if (h instanceof RemoteManagedObject) {
                        ((RemoteManagedObject) h).setTreeFolder(objDir);
                        if (h instanceof RemoteUnavailableObject) ((RemoteManagedObject) h).setDataValid(true);
                        else ((RemoteManagedObject) h).setDataValid(false);
                    }
        
    String[] names = tree.listObjectNames("/", false);
    String[] types = tree.listObjectTypes("/", false);
        for (int i=0; i<names.length; i++) {
            System.out.println("name="+names[i]+"   type="+types[i]);
        }
        
    }
        
    
}
