package foam.dao;

import com.google.appengine.api.datastore.*;
import foam.core.*;
import java.util.List;
import foam.mlang.order.Comparator;
import foam.mlang.predicate.Predicate;

// TODO: Deep serialization

public class DatastoreDAO extends AbstractDAO {
  private ClassInfo of_ = null;
  private PropertyInfo primaryKey_ = null;

  private DatastoreService datastore_ = DatastoreServiceFactory.getDatastoreService();
  private DatastoreService getData() { return datastore_; }

  public ClassInfo getOf() {
    return of_;
  }

  public DatastoreDAO setOf(ClassInfo of) {
    of_ = of;
    primaryKey_ = (PropertyInfo)of.getAxiomByName("id");
    return this;
  }

  public PropertyInfo getPrimaryKey() {
    return primaryKey_;
  }

  private List<PropertyInfo> getProperties(ClassInfo c) {
    return (List<PropertyInfo>)c.getAxiomsByClass(PropertyInfo.class);
  }

  private Key keyFromPK(Object key) {
    return KeyFactory.createKey(getOf().getId(), key.toString());
  }

  private Key keyFromFObject(FObject obj) {
    return keyFromPK(getPrimaryKey().get(obj));
  }

  public FObject put(FObject obj) {
    getData().put(serialize(obj));
    return obj;
  }

  public FObject remove(FObject obj) {
    getData().delete(keyFromFObject(obj));
    return obj;
  }

  public FObject find(Object id) {
    Entity result;
    try {
      result = getData().get(keyFromPK(id));
    } catch(EntityNotFoundException e) {
      return null;
    }

    return deserialize(result);
  }

  private Entity serialize(FObject o) {
    Entity entity = new Entity(keyFromFObject(o));

    ClassInfo info = o.getClassInfo();

    List<PropertyInfo> properties = getProperties(info);

    // TODO: Is cls_ a reserved work for properties?
    entity.setProperty("cls_", info.getId());

    for ( PropertyInfo prop : properties ) {
      entity.setProperty(prop.getName(), prop.get(o));
    }

    return entity;
  }

  private FObject deserialize(Entity e) {
    String classId = (String)e.getProperty("cls_");

    FObject obj;

    try {
      obj = (FObject)getX().create(Class.forName(classId));
    } catch(ClassNotFoundException exception) {
      throw new RuntimeException(exception);
    }

    List<PropertyInfo> props = getProperties(obj.getClassInfo());

    for ( PropertyInfo prop : props ) {
      prop.set(obj, e.getProperty(prop.getName()));
    }

    return obj;
  }

  public Sink select(Sink sink, Integer skip, Integer limit, Comparator order, Predicate predicate) {
    if ( sink == null ) {
      sink = new ListSink();
    }

    Sink decorated = decorateSink_(sink, skip, limit, order, predicate);

    FlowControl fc = (FlowControl)getX().create(FlowControl.class);

    // TODO: Richer query handling

    PreparedQuery query = getData().prepare(new Query(getOf().getId()));

    for ( Entity e : query.asIterable() ) {
      FObject obj = deserialize(e);

      if ( fc.getStopped() || fc.getErrorEvt() != null ) {
        break;
      }

      decorated.put(obj, fc);
    }

    if ( fc.getErrorEvt() != null ) {
      decorated.error();
      return sink;
    }

    decorated.eof();

    return sink;
  }

  public void removeAll(Integer skip, Integer limit, Comparator order, Predicate predicate) {
  }

  public void pipe(Sink s) {
    // TODO
  }
}

@@ -1136,16 +1136,23 @@
 erialize
+FObject
 (obj));%0A
@@ -1502,89 +1502,76 @@
 ate 
-Entity serialize(FObject o) %7B%0A    Entity entity = new Entity(keyFromFObject(o));%0A
+PropertyContainer serialize(PropertyContainer target, FObject obj) %7B
 %0A   
@@ -1589,16 +1589,18 @@
 info = o
+bj
 .getClas
@@ -1702,17 +1702,17 @@
 rved wor
-k
+d
  for pro
@@ -1720,30 +1720,30 @@
 erties?%0A    
-entity
+target
 .setProperty
@@ -1822,62 +1822,422 @@
     
-entity.setProperty(prop.getName(), prop.ge
+Object value = prop.get(obj);%0A%0A      if ( value instanceof FObject ) %7B%0A        EmbeddedEntity subEntity = new EmbeddedEntity();%0A        serialize(subEntity, (FObject)value);%0A        value = subEntity;%0A      %7D%0A%0A      target.setProperty(prop.getName(), value);%0A    %7D%0A%0A    return target;%0A  %7D%0A%0A  private Entity serializeFObject(FObject o) %7B%0A    Entity entity = new Entity(keyFromFObjec
 t(o));%0A
+%0A
     
-%7D
+serialize(entity, o);
 %0A%0A  
@@ -2288,22 +2288,33 @@
 rialize(
-Entity
+PropertyContainer
  e) %7B%0A  
@@ -2669,51 +2669,262 @@
     
+if ( ! e.hasProperty(
 prop.
-set(obj, e.getProperty(prop.getName())
+getName()) ) %7B%0A        continue;%0A      %7D%0A%0A      Object value = e.getProperty(prop.getName());%0A%0A      if ( value instanceof EmbeddedEntity ) %7B%0A        value = deserialize((EmbeddedEntity)value);%0A      %7D%0A%0A      prop.set(obj, value
 );%0A 

Fix serialization of nested objects.