package cn.ibizlab.core.lite.extensions.model;

import cn.ibizlab.core.lite.extensions.domain.EntityModel;
import cn.ibizlab.core.lite.extensions.domain.EntityObj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

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

@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
public class DataModel {
    @JsonIgnore
    @JSONField(serialize = false)
    private String modelString;

    public String getModelString()
    {
        if(StringUtils.isEmpty(modelString))
        {
            modelString= JSONObject.toJSONString(this);
        }
        return modelString;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public DataModel clone()
    {
        DataModel clone =  JSON.toJavaObject(JSONObject.parseObject(this.getModelString()),DataModel.class);
        clone.setModelString(this.getModelString());
        return clone;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private DataModel parentDataModel;


    @JsonIgnore
    @JSONField(serialize = false)
    public DataModel getRootDataModel()
    {
        if(this.getParentDataModel()==null)
            return this;
        else
            return this.getParentDataModel().getRootDataModel();
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public int getLayerNo()
    {
        int layerNo=1;
        DataModel parent=this.getParentDataModel();
        while (parent!=null)
        {
            layerNo++;
            parent=parent.getParentDataModel();
        }
        return layerNo;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public Property getRootFactPorperty()
    {
        return this.getRootFactPorperty().getFactPorperty();
    }


    @JSONField(ordinal = 0)
    private String dataModelId;
    public String getDataModelId()
    {
        if(StringUtils.isEmpty(dataModelId))
        {
            if(this.getParentDataModel()!=null)
                return this.getRootDataModel().getDataModelId()+":"+this.getDataModelName();
            else
                return this.getDataModelName();
        }
        return dataModelId;
    }

    @JSONField(ordinal = 1)
    private String dataModelName;
    public String getDataModelName()
    {
        if(StringUtils.isEmpty(dataModelName))
        {
            Property property = getFactPorperty();
            if(property!=null)
                return property.getPropertyName();
        }
        return dataModelName;
    }

    @JSONField(ordinal = 2)
    private LinkedHashSet<Property> objectProperties = new LinkedHashSet<>();
    public void setObjectProperties(LinkedHashSet<Property> objectProperties) {
        if(objectProperties!=null) {
            objectProperties.forEach(objectPorperty -> objectPorperty.setOwnerDataModel(this));
            this.objectProperties = objectProperties;
        }
    }

    @JSONField(ordinal = 3)
    private LinkedHashSet<DataModel> nestedDataModels = new LinkedHashSet<>();
    public void setNestedDataModels(LinkedHashSet<DataModel> nestedDataModels) {
        if(nestedDataModels!=null) {
            nestedDataModels.forEach(nestedDataModel -> nestedDataModel.setParentDataModel(this));
            this.nestedDataModels = nestedDataModels;
        }
    }

    @JSONField(ordinal = 4)
    private LinkedHashSet<LayerMapping> layerMappings = new LinkedHashSet<>();
    public void setLayerMappings(LinkedHashSet<LayerMapping> layerMappings)
    {
        if(layerMappings!=null) {
            layerMappings.forEach(layerMapping -> layerMapping.setSelfDataModel(this));
            this.layerMappings = layerMappings;
        }
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public Property getFactPorperty()
    {
        if(!ObjectUtils.isEmpty(this.getObjectProperties()))
        {
            return (Property)this.getObjectProperties().toArray()[0];
        }
        return null;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public EntityModel getFactEntityModel()
    {
        return this.getFactPorperty().getEntityModel();
    }

    public Property getObjectProperty(String name)
    {
        if(this.getObjectProperties()!=null&&(!StringUtils.isEmpty(name)))
        {
            for (Property property:this.getObjectProperties())
                if(name.equalsIgnoreCase(property.getPropertyName()))
                    return property;
        }
        return null;
    }

    public Property findObjectProperty(String name,String direction)
    {
        if(StringUtils.isEmpty(direction))
            direction="ALL";
        Property rt=this.getObjectProperty(name);
        if(rt!=null)
            return rt;
        if(direction.equalsIgnoreCase("ALL")||direction.equalsIgnoreCase("DOWN")) {
            if (this.getNestedDataModels() != null) {
                for (DataModel dm : this.getNestedDataModels()) {
                    rt = dm.findObjectProperty(name, "DOWN");
                    if (rt != null)
                        return rt;
                }
            }
        }
        if(direction.equalsIgnoreCase("ALL")||direction.equalsIgnoreCase("UP")) {
            DataModel parent = this.getParentDataModel();
            while (parent != null) {
                rt = parent.getObjectProperty(name);
                if (rt != null)
                    return rt;
                else
                    parent = parent.getParentDataModel();
            }
        }
        return null;
    }


    public DataModel getNestedDataModel(String name)
    {
        if(this.getNestedDataModels()!=null&&(!StringUtils.isEmpty(name)))
        {
            for (DataModel dataModel:this.getNestedDataModels())
                if(name.equalsIgnoreCase(dataModel.getDataModelName()))
                    return dataModel;
            for (DataModel dataModel:this.getNestedDataModels())
                if(dataModel.getObjectProperty(name)!=null)
                    return dataModel;
        }
        return null;
    }

    public DataModel findDataModel(String name, String direction)
    {
        if(StringUtils.isEmpty(direction))
            direction="ALL";
        if(name.equalsIgnoreCase(this.getDataModelName()))
            return this;
        DataModel rt= null;
        if(direction.equalsIgnoreCase("ALL")||direction.equalsIgnoreCase("DOWN")) {
            rt=this.getNestedDataModel(name);
            if(rt!=null)
                return rt;
            if(this.getNestedDataModels()!=null)
            {
                for(DataModel dm:this.getNestedDataModels())
                {
                    rt=dm.findDataModel(name,"DOWN");
                    if(rt!=null)
                        return rt;
                }
            }
        }

        if(this.getObjectProperty(name)!=null)
            return this;

        if(direction.equalsIgnoreCase("ALL")||direction.equalsIgnoreCase("UP")) {
            DataModel parent = this.getParentDataModel();
            while (parent != null) {
                if (name.equalsIgnoreCase(parent.getDataModelName()))
                    rt = parent;
                if (rt == null && parent.getObjectProperty(name) != null)
                    rt = parent;
                if (rt != null)
                    return rt;
                else
                    parent = parent.getParentDataModel();
            }
        }

        return null;
    }

    @Override
    public String toString() {
        return "DataModel{" +
                "dataModelName='" + getDataModelName() + '\'' +
                ", objectProperties=" + getObjectProperties() +
                ", nestedDataModels=" + getNestedDataModels() +
                ", layerMappings=" + getLayerMappings() +
                "}\r\n";
    }


    public String lookup(List<EntityObj> parentEntityList)
    {
        String sql="";
        String cond="";
        List<String> selfCols=new ArrayList<>();
        List<String> parentCols=new ArrayList<>();
        if(layerMappings.size()>0)
        {
            layerMappings.forEach(layerMapping -> {
                selfCols.add(layerMapping.getSelfPropertyColumn());
                parentCols.add(layerMapping.getParentPropertyColumn());
            });

        }
        else
        {
            EntityModel entityModel=this.getFactEntityModel();
            if(entityModel.getKeyField().isPhysicalField())
            {
                selfCols.add(entityModel.getKeyField().getColumnName());
                parentCols.add(entityModel.getKeyField().getColumnName());
            }
            else
                entityModel.getUnionKeyFields().forEach(fieldModel -> {
                    selfCols.add(fieldModel.getColumnName());
                    parentCols.add(fieldModel.getColumnName());
                });
        }

        for (String selfCol : selfCols)
        {
            if (!StringUtils.isEmpty(sql))
                sql += ",";
            sql += selfCol;
        }
        for(EntityObj entityObj:parentEntityList)
        {
            String condItem="";
            for(String parentCol:parentCols)
            {
                Object val=entityObj.get(parentCol);
                if(val instanceof String)
                {
                    val="'"+val.toString()+"'";
                }
                if (!StringUtils.isEmpty(condItem))
                    condItem += ",";
                condItem += val;
            }

            if (!StringUtils.isEmpty(cond))
                cond += ",";
            cond=cond+"("+condItem+")";
        }
        return "("+sql+") in ("+cond+")";
    }


    @JsonIgnore
    @JSONField(serialize = false)
    public List<DataModel> getSubDataModel()
    {
        List<DataModel> sublist=new ArrayList<>();
        if(this.getNestedDataModels()!=null)
        {
            for (DataModel dataModel:this.getNestedDataModels())
                sublist.add(dataModel);
            for (DataModel dataModel:this.getNestedDataModels())
                sublist.addAll(dataModel.getSubDataModel());
        }
        return sublist;
    }


    @JsonIgnore
    @JSONField(serialize = false)
    public List<Property> getParentProperty()
    {
        List<Property> properties=new ArrayList<>();

        this.getObjectProperties().forEach(property -> properties.add(property));

        DataModel parent = this.getParentDataModel();
        while (parent != null) {
            parent.getObjectProperties().forEach(property -> properties.add(property));
            parent = parent.getParentDataModel();
        }
        return properties;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public LinkedHashSet<Property> getAllProperty()
    {
        LinkedHashSet<Property> properties=new LinkedHashSet<>();
        properties.addAll(this.getObjectProperties());

        if (this.getNestedDataModels() != null) {
            for (DataModel dm : this.getNestedDataModels()) {
                properties.addAll(dm.getAllProperty());
            }
        }
        return properties;
    }

}
