package org.javarosa.core.model.instance;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

import org.javarosa.core.model.IDataReference;
import org.javarosa.core.services.storage.Persistable;
import org.javarosa.core.util.CacheTable;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.ExtWrapNullable;
import org.javarosa.core.util.externalizable.PrototypeFactory;

/**
 * A data instance represents a tree structure of abstract tree
 * elements which can be accessed and read with tree references. It is
 * a supertype of different types of concrete models which may or may not
 * be read only.
 * 
 * @author ctsims
 *
 */
public abstract class DataInstance<T extends AbstractTreeElement<T>> implements Persistable {

    
    /** The integer Id of the model */
    private int recordid = -1;
    
    /** The name for this data model */
    protected String name;
    /** The ID of the form that this is a model for */
    protected int formId;
    
    protected String instanceid;
    
    private CacheTable<TreeReference, T> referenceCache;
    
    public DataInstance() {
        referenceCache = new CacheTable<TreeReference, T>();
    }

    
    public DataInstance(String instanceid) {
        this.instanceid = instanceid;
        referenceCache = new CacheTable<TreeReference, T>();
    }

    public static TreeReference unpackReference(IDataReference ref) {
        return (TreeReference) ref.getReference();
    }
    
    public abstract AbstractTreeElement<T> getBase();

    public abstract T getRoot();

    public String getInstanceId() {
        return instanceid;
    }
    
    /**
     * Whether the structure of this instance is only available at runtime. 
     * 
     * @return true if the instance structure is available and runtime and can't
     * be checked for consistency until the reference is made available. False
     * otherwise.
     * 
     */
    public boolean isRuntimeEvaluated() {
        return false;
    }

    public T resolveReference(TreeReference ref) {
        if (!ref.isAbsolute()){
            return null;
        }
        
        T t = referenceCache.retrieve(ref);
        if(t != null) {
            return t;
        }
    
        AbstractTreeElement<T> node = getBase();
        T result = null;
        for (int i = 0; i < ref.size(); i++) {
            String name = ref.getName(i);
            int mult = ref.getMultiplicity(i);
            
            if(mult == TreeReference.INDEX_ATTRIBUTE) {
                //Should we possibly just return here? 
                //I guess technically we could step back...
                node = result = node.getAttribute(null, name);
                continue;
            }
            if (mult == TreeReference.INDEX_UNBOUND) {
                if (node.getChildMultiplicity(name) == 1) {
                    mult = 0;
                } else {
                    // reference is not unambiguous
                    node = result = null;
                    break;
                }
            } 
    
            node = result = node.getChild(name, mult);
            if (node == null) {
                break;
            }
        }
        
        t = (node == getBase() ? null : result); // never return a reference to '/'
        referenceCache.register(ref, t);
        return t;
    }

    public Vector explodeReference(TreeReference ref) {
        if (!ref.isAbsolute())
            return null;
    
        Vector nodes = new Vector();
        AbstractTreeElement<T> cur = getBase();
        for (int i = 0; i < ref.size(); i++) {
            String name = ref.getName(i);
            int mult = ref.getMultiplicity(i);
            
            //If the next node down the line is an attribute
            if(mult == TreeReference.INDEX_ATTRIBUTE) {
                //This is not the attribute we're testing
                if(cur != getBase()) {
                    //Add the current node
                    nodes.addElement(cur);
                }
                cur = cur.getAttribute(null, name);
            }
            
            //Otherwise, it's another child element
            else {
                if (mult == TreeReference.INDEX_UNBOUND) {
                    if (cur.getChildMultiplicity(name) == 1) {
                        mult = 0;
                    } else {
                        // reference is not unambiguous
                        return null;
                    }
                }
    
                if (cur != getBase()) {
                    nodes.addElement(cur);
                }
    
                cur = cur.getChild(name, mult);
                if (cur == null) {
                    return null;
                }
            }
        }
        return nodes;
    }

    public T getTemplate(TreeReference ref) {
        T node = getTemplatePath(ref);
        return (node == null ? null : ((node.isRepeatable() || node.isAttribute()) ? node : null));
    }

    public T getTemplatePath(TreeReference ref) {
        if (!ref.isAbsolute()) {
            return null;
        }
    
        T walker = null;
        AbstractTreeElement<T> node = getBase();
        for (int i = 0; i < ref.size(); i++) {
            String name = ref.getName(i);
            
            if(ref.getMultiplicity(i) == TreeReference.INDEX_ATTRIBUTE) {
                node = walker = node.getAttribute(null, name);
            } else {
    
                T newNode = node.getChild(name, TreeReference.INDEX_TEMPLATE);
                if (newNode == null) {
                    newNode = node.getChild(name, 0);
                } 
                if (newNode == null) {
                    return null;
                }
                node = walker = newNode;
            }
        }
        return walker;
    }

    public T resolveReference(IDataReference binding) {
        return resolveReference(unpackReference(binding));
    }

    public void setFormId(int formId) {
        this.formId = formId;
    }

    public int getFormId() {
        return this.formId;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        String name = "NULL";
        if(this.name != null)
        {
            name = this.name;
        }
        return name;
    }
    
    public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException {
        recordid = ExtUtil.readInt(in);
        formId = ExtUtil.readInt(in);
        name = (String) ExtUtil.read(in, new ExtWrapNullable(String.class), pf);
        instanceid = (String) ExtUtil.nullIfEmpty(ExtUtil.readString(in));
    
    }

    public void writeExternal(DataOutputStream out) throws IOException {
        ExtUtil.writeNumeric(out, recordid);
        ExtUtil.writeNumeric(out, formId);
        ExtUtil.write(out, new ExtWrapNullable(name));
        ExtUtil.write(out, ExtUtil.emptyIfNull(instanceid));
    }
    

    public int getID() {
        return recordid;
    }

    public void setID(int recordid) {
        this.recordid = recordid;
    }


    
    public abstract void initialize(InstanceInitializationFactory initializer, String instanceId);

}
@@ -1210,76 +1210,8 @@
 d;%0D%0A
-    %0D%0A    private CacheTable%3CTreeReference, T%3E referenceCache;%0D%0A    
 %0D%0A  
@@ -1233,17 +1233,16 @@
 stance()
- 
 %7B%0D%0A     
@@ -1248,60 +1248,8 @@
     
-referenceCache = new CacheTable%3CTreeReference, T%3E();
 %0D%0A  
@@ -1249,26 +1249,24 @@
    %0D%0A    %7D%0D%0A
-%0D%0A
     %0D%0A    pu
@@ -1348,70 +1348,8 @@
 d;%0D%0A
-        referenceCache = new CacheTable%3CTreeReference, T%3E();%0D%0A
     
@@ -2156,128 +2156,10 @@
  %7D%0D%0A
-        %0D%0A        T t = referenceCache.retrieve(ref);%0D%0A        if(t != null) %7B%0D%0A            return t;%0D%0A        %7D%0D%0A    
 %0D%0A
+
     
@@ -3154,21 +3154,14 @@
     
-%0D%0A        t =
+return
  (no
@@ -3234,69 +3234,8 @@
 /'%0D%0A
-        referenceCache.register(ref, t);%0D%0A        return t;%0D%0A
     

fix for incomplete caseid bug