package models.dataSynchronizationModel;

import models.deltaAlgebra.MultiplicityValue;
import models.deltaAlgebra.ObjectNode;
import models.deltaAlgebra.ReferenceEdge;
import parser.ArrayValue;
import parser.JsonValue;
import parser.StringValue;
import parser.Value;
import parser.exceptions.ExpectedJsonValue;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The reference context and properties of specific data synchronization.
 *
 * @author Nitta
 *
 */
public class DataSynchronizationContext {
    private List<Relation> intermediateRelations;
    private Relation syncronizationRelation;
    private String dataName;
    private PushPullValue transferStyle;
    private Map<String, ObjectNode> objectNodeMap = new HashMap<>();

    public DataSynchronizationContext(Value jsonValue) {
        setDataTransferContext(jsonValue);
    }

    public DataSynchronizationContext(List<Relation> intermediateRelations, Relation syncronizationRelation, String dataName, PushPullValue transferStyle) {
        this.intermediateRelations = intermediateRelations;
        this.syncronizationRelation = syncronizationRelation;
        this.dataName = dataName;
        this.transferStyle = transferStyle;
    }

    public List<Relation> getIntermediateRelations() {
        return intermediateRelations;
    }

    public Relation getSyncronizationRelation() {
        return syncronizationRelation;
    }

    public boolean isFlat() {
        return syncronizationRelation.isFlat();
    }

    public ObjectHierarchy getSrcObjectHierarchy() {
        return syncronizationRelation.getSrcObjectHierarchy();
    }

    public ObjectHierarchy getDstObjectHierarchy() {
        return syncronizationRelation.getDstObjectHierarchy();
    }

    public ObjectNode getSrcTopObject() {
        return syncronizationRelation.getSrcTopObject();
    }

    public ObjectNode getDstTopObject() {
        return syncronizationRelation.getDstTopObject();
    }

    public ObjectNode getSrcBottomObject() {
        return syncronizationRelation.getSrcBottomObject();
    }

    public ObjectNode getDstBottomObject() {
        return syncronizationRelation.getDstBottomObject();
    }

    public MultiplicityValue getTransferMultiplicity() {
        return syncronizationRelation.getMultiplicity();
    }

    public boolean isTransferToOne() {
        return (syncronizationRelation.getMultiplicity() == MultiplicityValue.OneToOne || syncronizationRelation.getMultiplicity() == MultiplicityValue.ManyToOne);
    }

    public boolean isTransferToMany() {
        return (syncronizationRelation.getMultiplicity() == MultiplicityValue.OneToMany || syncronizationRelation.getMultiplicity() == MultiplicityValue.ManyToMany);
    }

    public boolean isTransferOneTo() {
        return (syncronizationRelation.getMultiplicity() == MultiplicityValue.OneToOne || syncronizationRelation.getMultiplicity() == MultiplicityValue.OneToMany);
    }

    public boolean isTransferManyTo() {
        return (syncronizationRelation.getMultiplicity() == MultiplicityValue.ManyToOne || syncronizationRelation.getMultiplicity() == MultiplicityValue.ManyToMany);
    }

    public String getDataName() {
        return dataName;
    }

    public PushPullValue getTransferStyle() {
        return transferStyle;
    }

    private void setDataTransferContext(Value value) {
        if (value instanceof JsonValue) {
            for (StringValue keyValue : ((JsonValue) value).getJson().keySet()) {
                String key = keyValue.getStringValue();
                if (key.equals("relations")) {//この部分は動作未確認(おそらくまだ動かない)
                    intermediateRelations = new ArrayList<>();
                    ArrayValue arrayValue = ((ArrayValue)((JsonValue) value).getValue(keyValue));
                    List<Value> relationValues = arrayValue.getValues();
                    for(Value relationValue:relationValues){
                        intermediateRelations.add(getRelation(relationValue));
                    }
                } else if (key.equals("transfer")) {
                    syncronizationRelation = getRelation(((JsonValue) value).getValue(keyValue));
                }
            }
        }
    }

    private Relation getRelation(Value value){
        if (value instanceof JsonValue) {
            MultiplicityValue multiplicityValue = null;
            List<ReferenceEdge> srcEdges = new ArrayList<>();
            List<ReferenceEdge> dstEdges = new ArrayList<>();
            ObjectNode src = null;
            ObjectNode dst = null;
            for (StringValue keyValue : ((JsonValue) value).getJson().keySet()) {
                String key = keyValue.getStringValue();
                if (key.equals("src")) {
                    src = setEdges((StringValue) ((JsonValue) value).getValue(keyValue), srcEdges);
                } else if (key.equals("dst")) {
                    dst = setEdges((StringValue) ((JsonValue) value).getValue(keyValue), dstEdges);
                } else if (key.equals("style")) {
                    setTransferStyle((StringValue) ((JsonValue) value).getValue(keyValue));
                } else if (key.equals("multiplicity")) {
                    StringValue multi = (StringValue) ((JsonValue) value).getValue(keyValue);
                    String multiplicity = multi.getStringValue();
                    if (multiplicity.equals("*:1")) {
                        multiplicityValue = MultiplicityValue.ManyToOne;
                    } else if (multiplicity.equals("1:1")) {
                        multiplicityValue = MultiplicityValue.OneToOne;
                    } else if (multiplicity.equals("*:*")) {
                        multiplicityValue = MultiplicityValue.ManyToMany;
                    } else if (multiplicity.equals("1:*")) {
                        multiplicityValue = MultiplicityValue.OneToMany;
                    }
                } else if (key.equals("data")) {
                    setTransferData(((JsonValue) value).getValue(keyValue));
                }
            }
            if (multiplicityValue == null) {
                throw new RuntimeException("multiplicityが存在していません");
            }
            if(src != null&&dst != null){//両方単独ObjectNode
                return new Relation(src,dst,multiplicityValue);
            }else if(src == null&&dst != null){//片方単独
                return new Relation(srcEdges,dst,multiplicityValue);
            }else if(src != null&&dst == null){//上と同じ
                return new Relation(src,dstEdges,multiplicityValue);
            }else{//両方複数
                return new Relation(srcEdges,dstEdges,multiplicityValue);
            }
        }
        throw new ExpectedJsonValue("Jsonではありません。");
    }

    private void setTransferStyle(StringValue stringValue){
        String style = stringValue.getStringValue();
        if (style.equals("PUSH")) {
            transferStyle = PushPullValue.PUSH;
        } else if (style.equals("PULL")) {
            transferStyle = PushPullValue.PULL;
        }
    }

    private void setTransferData(Value dataTransferValue){
        if (dataTransferValue instanceof JsonValue) {
            for (StringValue dataTransferValueKeyValue : ((JsonValue) dataTransferValue).getJson().keySet()) {
                String dataTransferValueKey = dataTransferValueKeyValue.getStringValue();
                if (dataTransferValueKey.equals("name")) {
                    String name = ((StringValue) ((JsonValue) dataTransferValue).getValue(dataTransferValueKeyValue)).getStringValue();
                    dataName = name;
                }
            }
        } else {
            throw new ExpectedJsonValue("jsonのtransferのdataが足りていません");
        }
    }

    private ObjectNode setEdges(StringValue stringValue, List<ReferenceEdge> edges) {//objectNodeの場合はobjectNodeを返したい。
        String S = stringValue.getStringValue();
        int cur = 0;
        ArrayList<String> list = new ArrayList<>();
        while (cur < S.length()) {
            StringBuilder variable = new StringBuilder();
            while (cur < S.length() && S.charAt(cur) != '.') {
                variable.append(S.charAt(cur));
                cur++;
            }
            list.add(variable.toString());
            cur++;
        }
        if(list.size() == 1){//一つだけの場合はobjectNodeを返す
            String src = list.get(0);
            objectNodeMap.put(src, new ObjectNode(src));
            return objectNodeMap.get(src);
        }
        for (int i = 0; i < list.size() - 1; i++) {
            String src = list.get(i);
            String dst = list.get(i + 1);
            if (dst.charAt(0) == '{' && dst.charAt(dst.length() - 1) == '}') {//oneToMany
                dst = dst.substring(1, dst.length() - 1);
                if(!objectNodeMap.containsKey(dst)){
                    objectNodeMap.put(dst, new ObjectNode(dst));
                }
                if (src.charAt(0) == '{' && src.charAt(src.length() - 1) == '}') {
                    src = src.substring(1, src.length() - 1);
                    if (!objectNodeMap.containsKey(src)) {
                        objectNodeMap.put(src, new ObjectNode(src));
                    }
                } else {
                    if (!objectNodeMap.containsKey(src)) {
                        objectNodeMap.put(src, new ObjectNode(src));
                    }
                }
                edges.add(new ReferenceEdge(objectNodeMap.get(src), objectNodeMap.get(dst), convertTheFirstCharacterToLowerCase(dst), "String"));
            } else {//oneToOne
                if(!objectNodeMap.containsKey(dst)){
                    objectNodeMap.put(dst, new ObjectNode(dst));
                }
                if (src.charAt(0) == '{' && src.charAt(src.length() - 1) == '}') {
                    src = src.substring(1, src.length() - 1);
                    if (!objectNodeMap.containsKey(src)) {
                        objectNodeMap.put(src, new ObjectNode(src));
                    }
                } else {
                    if (!objectNodeMap.containsKey(src)) {
                        objectNodeMap.put(src, new ObjectNode(src));
                    }
                }
                edges.add(new ReferenceEdge(objectNodeMap.get(src), objectNodeMap.get(dst), convertTheFirstCharacterToLowerCase(dst)));
            }
        }
        return null;
    }

    private String convertTheFirstCharacterToLowerCase(String s){
        if (Character.isUpperCase(s.charAt(0))) {
            s = s.substring(0,1).toLowerCase() + s.substring(1);
            return s;
        } else {
            return s;
        }
    }
}
