package models.relationships;

import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;

import play.libs.F.Function;
import play.libs.F.None;
import play.libs.F.Option;
import play.libs.F.Promise;
import play.libs.F.Some;
import play.libs.F.Tuple;

import constants.RelationshipType;
import managers.relationships.AllowsRelationshipManager;
import managers.relationships.RelationshipManager;
import models.nodes.Feature;
import models.nodes.OntologyNode;


public class AllowsRelationship extends TypedRelationship {

    private AllowsRelationship() {
        this.type = RelationshipType.ALLOWS;
    }

    public AllowsRelationship(Feature startNode, OntologyNode endNode) {
        this();
        this.startNode = startNode;
        this.endNode = endNode;
    }

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

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

    public Promise<Tuple<Option<Relationship>, Boolean>> getOrCreate() {
        return this.exists().flatMap(new GetOrCreateFunction(this));
    }

    @Override
    public Promise<Boolean> delete() {
        Promise<JsonNode> json = AllowsRelationshipManager.get(this);
        Promise<Relationship> relationship = json.map(new GetFunction(this));
        return relationship.flatMap(
            new Function<Relationship, Promise<Boolean>>() {
                public Promise<Boolean> apply(Relationship relationship) {
                    return RelationshipManager.delete(relationship);
                }
            });
    }

    public static Promise<Boolean> deleteAllFrom(Feature startNode) {
        Promise<List<Relationship>> outgoingRelationships = startNode
            .getOutgoingRelationships(RelationshipType.ALLOWS);
        Promise<List<Boolean>> deletedList = outgoingRelationships.flatMap(
            new Function<List<Relationship>, Promise<List<Boolean>>>() {
                public Promise<List<Boolean>> apply(
                    List<Relationship> relationships) {
                    List<Promise<? extends Boolean>> deletedList =
                        new ArrayList<Promise<? extends Boolean>>();
                    for (Relationship relationship: relationships) {
                        Promise<Boolean> deleted = relationship.delete();
                        deletedList.add(deleted);
                    }
                    return Promise.sequence(deletedList);
                }
            });
        return deletedList.map(
            new Function<List<Boolean>, Boolean>() {
                public Boolean apply(List<Boolean> deletedList) {
                    Boolean allDeleted = true;
                    for (Boolean deleted: deletedList) {
                        if (!deleted) {
                            allDeleted = false;
                            break;
                        }
                    }
                    return allDeleted;
                }
            });
    }


    private class GetOrCreateFunction
        implements Function<Boolean,
                   Promise<Tuple<Option<Relationship>, Boolean>>> {
        private AllowsRelationship relationship;
        public GetOrCreateFunction(AllowsRelationship relationship) {
            this.relationship = relationship;
        }
        public Promise<Tuple<Option<Relationship>, Boolean>> apply(
            Boolean exists) {
            if (exists) {
                return Promise.pure(
                    new Tuple<Option<Relationship>, Boolean>(
                        new Some<Relationship>(this.relationship), false));
            }
            Promise<Boolean> created = AllowsRelationshipManager.create(
                this.relationship);
            return created.map(new CreatedFunction(this.relationship));
        }
    }

    private class CreatedFunction
        implements Function<Boolean, Tuple<Option<Relationship>, Boolean>> {
        private Relationship relationship;
        public CreatedFunction(Relationship relationship) {
            this.relationship = relationship;
        }
        public Tuple<Option<Relationship>, Boolean> apply(Boolean created) {
            if (created) {
                return new Tuple<Option<Relationship>, Boolean>(
                    new Some<Relationship>(this.relationship), true);
            }
            return new Tuple<Option<Relationship>, Boolean>(
                new None<Relationship>(), false);
        }
    }

    private class GetFunction implements Function<JsonNode, Relationship> {
        private Relationship relationship;
        public GetFunction(Relationship relationship) {
            this.relationship = relationship;
        }
        public Relationship apply(JsonNode json) {
            String url = json.findValue("self").asText();
            int ID = Integer.parseInt(
                url.substring(url.lastIndexOf("/") + 1));
            this.relationship.ID = ID;
            return this.relationship;
        }
    }

}

@@ -177,117 +177,14 @@
 s.F.
-None;%0Aimport play.libs.F.Option;%0Aimport play.libs.F.Promise;%0Aimport play.libs.F.Some;%0Aimport play.libs.F.Tupl
+Promis
 e;%0A%0A
@@ -1335,157 +1335,8 @@
  %7D%0A%0A
-    public Promise%3CTuple%3COption%3CRelationship%3E, Boolean%3E%3E getOrCreate() %7B%0A        return this.exists().flatMap(new GetOrCreateFunction(this));%0A    %7D%0A%0A
     
@@ -3195,32 +3195,32 @@
               %7D%0A
+
             %7D);%0A
@@ -3230,1489 +3230,8 @@
  %7D%0A%0A
-%0A    private class GetOrCreateFunction%0A        implements Function%3CBoolean,%0A                   Promise%3CTuple%3COption%3CRelationship%3E, Boolean%3E%3E%3E %7B%0A        private AllowsRelationship relationship;%0A        public GetOrCreateFunction(AllowsRelationship relationship) %7B%0A            this.relationship = relationship;%0A        %7D%0A        public Promise%3CTuple%3COption%3CRelationship%3E, Boolean%3E%3E apply(%0A            Boolean exists) %7B%0A            if (exists) %7B%0A                return Promise.pure(%0A                    new Tuple%3COption%3CRelationship%3E, Boolean%3E(%0A                        new Some%3CRelationship%3E(this.relationship), false));%0A            %7D%0A            Promise%3CBoolean%3E created = AllowsRelationshipManager.create(%0A                this.relationship);%0A            return created.map(new CreatedFunction(this.relationship));%0A        %7D%0A    %7D%0A%0A    private class CreatedFunction%0A        implements Function%3CBoolean, Tuple%3COption%3CRelationship%3E, Boolean%3E%3E %7B%0A        private Relationship relationship;%0A        public CreatedFunction(Relationship relationship) %7B%0A            this.relationship = relationship;%0A        %7D%0A        public Tuple%3COption%3CRelationship%3E, Boolean%3E apply(Boolean created) %7B%0A            if (created) %7B%0A                return new Tuple%3COption%3CRelationship%3E, Boolean%3E(%0A                    new Some%3CRelationship%3E(this.relationship), true);%0A            %7D%0A            return new Tuple%3COption%3CRelationship%3E, Boolean%3E(%0A                new None%3CRelationship%3E(), false);%0A        %7D%0A    %7D%0A%0A
     

Clean up: Remove obsolete `getOrCreate` method from AllowsRelationship class.