package models;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.nio.charset.Charset;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import play.libs.Json;
import play.libs.F.Function;
import play.libs.F.Promise;

import constants.NodeType;
import managers.LHSManager;


public class LHS extends LabeledNodeWithProperties {
    public Rule rule;
    public JsonNode json;

    private LHS() {
        this.label = NodeType.AVM;
        this.jsonProperties = Json.newObject();
    }

    public LHS(Rule rule) {
        this();
        this.rule = rule;
        UUID uuid = UUID.nameUUIDFromBytes(
            rule.name.getBytes(Charset.forName("UTF-8")));
        this.jsonProperties.put("uuid", uuid.toString());
    }

    public Promise<Boolean> create() {
        Promise<Boolean> created = this.exists()
            .flatMap(new CreateFunction(this));
        return created.flatMap(new ConnectToRuleFunction(this));
    }

    public Promise<LHS> get() {
        Promise<JsonNode> json = LHSManager.get(this);
        return json.flatMap(new GetFunction(this));
    }

    public Promise<Boolean> add(Feature feature) {
        return new HasRelationship(this, feature).create();
    }

    private class CreateFunction implements
                                     Function<Boolean, Promise<Boolean>> {
        private LHS lhs;
        public CreateFunction(LHS lhs) {
            this.lhs = lhs;
        }
        public Promise<Boolean> apply(Boolean exists) {
            if (exists) {
                return Promise.pure(false);
            }
            return LHSManager.create(this.lhs);
        }
    }

    private class ConnectToRuleFunction
        implements Function<Boolean, Promise<Boolean>> {
        private LHS lhs;
        public ConnectToRuleFunction(LHS lhs) {
            this.lhs = lhs;
        }
        public Promise<Boolean> apply(Boolean created) {
            return new LHSRelationship(this.lhs).create();
        }
    }

    private class GetFunction implements Function<JsonNode, Promise<LHS>> {
        private LHS lhs;
        public GetFunction(LHS lhs) {
            this.lhs = lhs;
        }
        public Promise<LHS> apply(JsonNode json) {
            JsonNode data = json.get("data");
            if (data.size() > 0) {
                // Process node-relationship-node triples appropriately;
                // for now just get names of end nodes (i.e.,
                // features) and add them to pairs with arbitrary
                // default value
                List<JsonNode> endNodes = data.findValues("end");
                List<Promise<? extends Feature>> featureList =
                    new ArrayList<Promise<? extends Feature>>();
                for (JsonNode endNode: endNodes) {
                    Promise<Feature> feature = Feature
                        .getByURL(endNode.asText());
                    featureList.add(feature);
                }
                Promise<List<Feature>> features = Promise
                    .sequence(featureList);
                Promise<List<String>> featureNames = features.map(
                    new Function<List<Feature>, List<String>>() {
                        public List<String> apply(List<Feature> features) {
                            List<String> featureNames =
                                new ArrayList<String>();
                            for (Feature feature: features) {
                                featureNames.add(feature.name);
                            }
                            return featureNames;
                        }
                    });
                Promise<ObjectNode> pairs = featureNames.map(
                    new Function<List<String>, ObjectNode>() {
                        public ObjectNode apply(List<String> featureNames) {
                            ObjectNode pairs = Json.newObject();
                            for (String featureName: featureNames) {
                                pairs.put(featureName, "");
                            }
                            return pairs;
                        }
                    });
                return pairs.map(new SetPairsFunction(this.lhs));
            } else {
                ObjectNode pairs = Json.newObject();
                this.lhs.json = pairs;
                return Promise.pure(this.lhs);
            }
        }
    }

    private static class SetPairsFunction implements
                                            Function<ObjectNode, LHS> {
        private LHS lhs;
        public SetPairsFunction(LHS lhs) {
            this.lhs = lhs;
        }
        public LHS apply(ObjectNode pairs) {
            this.lhs.json = pairs;
            return this.lhs;
        }
    }

}

@@ -3141,33 +3141,24 @@
 ise%3C
-List%3CString%3E%3E featureName
+ObjectNode%3E pair
 s = 
@@ -3219,28 +3219,26 @@
 ature%3E, 
-List%3CString%3E
+ObjectNode
 %3E() %7B%0A  
@@ -3266,28 +3266,26 @@
  public 
-List%3CString%3E
+ObjectNode
  apply(L
@@ -3341,89 +3341,41 @@
     
-List%3CString%3E featureNames =%0A                                new ArrayList%3CString%3E
+ObjectNode pairs = Json.newObject
 ();%0A
@@ -3472,415 +3472,23 @@
     
-featureNames.add(feature.name);%0A                            %7D%0A                            return featureNames;%0A                        %7D%0A                    %7D);%0A                Promise%3CObjectNode%3E pairs = featureNames.map(%0A                    new Function%3CList%3CString%3E, ObjectNode%3E() %7B%0A                        public ObjectNode apply(List%3CString%3E featureNames) %7B%0A                            ObjectNode pairs
+ObjectNode info
  = J
@@ -3536,48 +3536,48 @@
     
-for (String featureName: featureNames) %7B
+    info.put(%22type%22, feature.getType());
 %0A   
@@ -3626,16 +3626,19 @@
 ture
-N
+.n
 ame, 
-%22%22
+info
 );%0A 

Make sure feature type information for features that are part of a rule's LHS is available to client-side code responsible for rendering the rule.