/*
 * This file is part of the Vamsas Client version 0.2. 
 * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite, 
 *  Andrew Waterhouse and Dominik Lindner.
 * 
 * Earlier versions have also been incorporated into Jalview version 2.4 
 * since 2008, and TOPALi version 2 since 2007.
 * 
 * The Vamsas Client is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * The Vamsas Client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.
 */
package uk.ac.vamsas.test.simpleclient.simpleapp;

import java.io.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Vector;
import java.util.jar.*;
import org.exolab.castor.xml.*;
import org.exolab.castor.mapping.Mapping;

import uk.ac.vamsas.client.Vobject;
import uk.ac.vamsas.client.VorbaId;
import uk.ac.vamsas.objects.core.*;
import uk.ac.vamsas.objects.utils.DocumentStuff;
import uk.ac.vamsas.test.simpleclient.ClientDoc;

/*
 * 
 * static {
 * org.exolab.castor.util.LocalConfiguration.getInstance().getProperties().setProperty(
 * "org.exolab.castor.serializer", "org.apache.xml.serialize.XMLSerilazizer"); }
 * 
 */

public class VamsasDatastore {
  org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
      .getLog(VamsasDatastore.class);

  Entry provEntry = null;

  org.exolab.castor.types.Date date = new org.exolab.castor.types.Date(
      new java.util.Date());

  ClientDoc cdoc;

  Hashtable vobj2jv;

  IdentityHashMap jv2vobj;

  public VamsasDatastore(ClientDoc cdoc, Hashtable vobj2jv,
      IdentityHashMap jv2vobj, Entry provEntry) {
    this.cdoc = cdoc;
    this.vobj2jv = vobj2jv;
    this.jv2vobj = jv2vobj;
    this.provEntry = provEntry;
  }

  /*
   * public void storeJalview(String file, AlignFrame af) { try { // 1. Load the
   * mapping information from the file Mapping map = new
   * Mapping(getClass().getClassLoader()); java.net.URL url =
   * getClass().getResource("/jalview_mapping.xml"); map.loadMapping(url); // 2.
   * Unmarshal the data // Unmarshaller unmar = new Unmarshaller();
   * //unmar.setIgnoreExtraElements(true); //unmar.setMapping(map); // uni =
   * (UniprotFile) unmar.unmarshal(new FileReader(file)); // 3. marshal the data
   * with the total price back and print the XML in the console Marshaller
   * marshaller = new Marshaller( new FileWriter(file) );
   * 
   * marshaller.setMapping(map); marshaller.marshal(af); } catch (Exception e) {
   * e.printStackTrace(); } }
   */
  /**
   * @return the Vobject bound to Jalview datamodel object
   */
  protected Vobject getjv2vObj(Object jvobj) {
    if (jv2vobj.containsKey(jvobj))
      return cdoc.getObject((VorbaId) jv2vobj.get(jvobj));
    return null;
  }

  /**
   * 
   * @param vobj
   * @return Jalview datamodel object bound to the vamsas document object
   */
  protected Object getvObj2jv(uk.ac.vamsas.client.Vobject vobj) {
    VorbaId id = vobj.getVorbaId();
    if (id == null) {
      id = cdoc.registerObject(vobj);
      log.debug("Registering new object and returning null for getvObj2jv");
      return null;
    }
    if (vobj2jv.containsKey(vobj.getVorbaId()))
      return vobj2jv.get(vobj.getVorbaId());
    return null;
  }

  protected void bindjvvobj(Object jvobj, uk.ac.vamsas.client.Vobject vobj) {
    VorbaId id = vobj.getVorbaId();
    if (id == null) {
      id = cdoc.registerObject(vobj);
      if (id == null || vobj.getVorbaId() == null)
        log.error("Failed to get id for "
            + (vobj.isRegisterable() ? "registerable" : "unregisterable")
            + " object " + vobj);
    }
    if (vobj2jv.containsKey(vobj.getVorbaId()) || jv2vobj.containsKey(jvobj)) {
      log.error("Duplicate object binding! " + vobj + " id "
          + vobj.getVorbaId().getId() + " to " + jvobj);
    } else {
      vobj2jv.put(vobj.getVorbaId(), jvobj);// JBPNote - better implementing a
      // hybrid invertible hash.
      jv2vobj.put(jvobj, vobj.getVorbaId());
    }
  }

  /**
   * put the alignment viewed by AlignViewport into cdoc.
   * 
   * @param av
   */
  public void storeVAMSAS(Object fromAppsDatamodel) {
    boolean nw = false;
    VAMSAS root = null; // will be resolved based on Dataset Parent.
    DataSet dataset = (DataSet) getjv2vObj(fromAppsDatamodel);
    if (dataset == null) {
      root = cdoc.getVamsasRoots()[0]; // default vamsas root for modifying.
      dataset = new DataSet();
      root.addDataSet(dataset);
      bindjvvobj(fromAppsDatamodel, dataset);
      dataset.setProvenance(dummyProvenance());
      dataset.getProvenance().addEntry(provEntry);
      nw = true;
    } else {
      root = (VAMSAS) dataset.getV_parent();
    }
    // etc...
  }

  private Property newProperty(String name, String type, String content) {
    Property vProperty = new Property();
    vProperty.setName(name);
    if (type != null)
      vProperty.setType(type);
    vProperty.setContent(content);
    return vProperty;
  }

  /**
   * get start<end range of segment, adjusting for inclusivity flag and
   * polarity.
   * 
   * @param visSeg
   * @param ensureDirection
   *          when true - always ensure start is less than end.
   * @return int[] { start, end, direction} where direction==1 for range running
   *         from end to start.
   */
  private int[] getSegRange(Seg visSeg, boolean ensureDirection) {
    boolean incl = visSeg.getInclusive();
    // adjust for inclusive flag.
    int pol = (visSeg.getStart() <= visSeg.getEnd()) ? 1 : -1; // polarity of
    // region.
    int start = visSeg.getStart() + (incl ? 0 : pol);
    int end = visSeg.getEnd() + (incl ? 0 : -pol);
    if (ensureDirection && pol == -1) {
      // jalview doesn't deal with inverted ranges, yet.
      int t = end;
      end = start;
      start = t;
    }
    return new int[] { start, end, pol < 0 ? 1 : 0 };
  }

  public void updateToJalview() {
    VAMSAS _roots[] = cdoc.getVamsasRoots();

    for (int _root = 0; _root < _roots.length; _root++) {
      VAMSAS root = _roots[_root];
      boolean newds = false;
      for (int _ds = 0, _nds = root.getDataSetCount(); _ds < _nds; _ds++) {
        // ///////////////////////////////////
        // ///LOAD DATASET
        DataSet dataset = root.getDataSet(_ds);
        int i, iSize = dataset.getSequenceCount();
        Vector dsseqs;
        Object appsdataset = (Object) getvObj2jv(dataset);
        int jremain = 0;
        if (appsdataset == null) {
          log.debug("Initialising new dataset fields");
          newds = true;
          dsseqs = new Vector();
        } else {
          log.debug("Update dataset from vamsas dataset.");
        }
        // etc for annotations, alignments, trees, etc.
      }
    }
  }

  /**
   * get real bounds of a RangeType's specification. start and end are an
   * inclusive range within which all segments and positions lie. TODO: refactor
   * to vamsas utils
   * 
   * @param dseta
   * @return int[] { start, end}
   */
  private int[] getBounds(RangeType dseta) {
    if (dseta != null) {
      int[] se = null;
      if (dseta.getSegCount() > 0 && dseta.getPosCount() > 0)
        throw new Error(
            "Invalid vamsas RangeType - cannot resolve both lists of Pos and Seg from choice!");
      if (dseta.getSegCount() > 0) {
        se = getSegRange(dseta.getSeg(0), true);
        for (int s = 1, sSize = dseta.getSegCount(); s < sSize; s++) {
          int nse[] = getSegRange(dseta.getSeg(s), true);
          if (se[0] > nse[0])
            se[0] = nse[0];
          if (se[1] < nse[1])
            se[1] = nse[1];
        }
      }
      if (dseta.getPosCount() > 0) {
        // could do a polarity for pos range too. and pass back indication of
        // discontinuities.
        int pos = dseta.getPos(0).getI();
        se = new int[] { pos, pos };
        for (int p = 0, pSize = dseta.getPosCount(); p < pSize; p++) {
          pos = dseta.getPos(p).getI();
          if (se[0] > pos)
            se[0] = pos;
          if (se[1] < pos)
            se[1] = pos;
        }
      }
      return se;
    }
    return null;
  }

  /**
   * map from a rangeType's internal frame to the referenced object's coordinate
   * frame.
   * 
   * @param dseta
   * @return int [] { ref(pos)...} for all pos in rangeType's frame.
   */
  private int[] getMapping(RangeType dseta) {
    Vector posList = new Vector();
    if (dseta != null) {
      int[] se = null;
      if (dseta.getSegCount() > 0 && dseta.getPosCount() > 0)
        throw new Error(
            "Invalid vamsas RangeType - cannot resolve both lists of Pos and Seg from choice!");
      if (dseta.getSegCount() > 0) {
        for (int s = 0, sSize = dseta.getSegCount(); s < sSize; s++) {
          se = getSegRange(dseta.getSeg(s), false);
          int se_end = se[1 - se[2]] + (se[2] == 0 ? 1 : -1);
          for (int p = se[se[2]]; p != se_end; p += se[2] == 0 ? 1 : -1) {
            posList.add(new Integer(p));
          }
        }
      } else if (dseta.getPosCount() > 0) {
        int pos = dseta.getPos(0).getI();

        for (int p = 0, pSize = dseta.getPosCount(); p < pSize; p++) {
          pos = dseta.getPos(p).getI();
          posList.add(new Integer(pos));
        }
      }
    }
    if (posList != null && posList.size() > 0) {
      int[] range = new int[posList.size()];
      for (int i = 0; i < range.length; i++)
        range[i] = ((Integer) posList.elementAt(i)).intValue();
      posList.clear();
      return range;
    }
    return null;
  }

  /**
   * 
   * @return default initial provenance list for a VamsasDatastore created
   *         vamsas object.
   */
  Provenance dummyProvenance() {
    return dummyProvenance(null);
  }

  Entry dummyPEntry(String action) {
    Entry entry = new Entry();
    entry.setApp(this.provEntry.getApp());
    if (action != null)
      entry.setAction(action);
    else
      entry.setAction("created.");
    entry.setDate(new java.util.Date());
    entry.setUser(this.provEntry.getUser());
    return entry;
  }

  Provenance dummyProvenance(String action) {
    Provenance prov = new Provenance();
    prov.addEntry(dummyPEntry(action));
    return prov;
  }

  void addProvenance(Provenance p, String action) {
    p.addEntry(dummyPEntry(action));
  }

}
