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

import cn.ibizlab.core.lite.extensions.model.DataModel;
import cn.ibizlab.core.lite.extensions.model.Property;
import cn.ibizlab.util.helper.DataObject;
import cn.ibizlab.util.helper.RuleUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.*;

public class ModelObj extends DataObj<String,Object> {

    public ModelObj(ModelObj modelObj)
    {
        if(modelObj.dataModel!=null)
            this.setDataModel(modelObj.dataModel);


        if(this.getDataModel()!=null)
        {
            this.getDataModel().getObjectProperties().forEach(property -> {
                Object obj=modelObj.get(property.getPropertyName());
                if(obj!=null&&obj instanceof EntityObj)
                {
                    this.setEntity(property.getPropertyName(),((EntityObj)obj).copy());
                }
            });

            this.getDataModel().getNestedDataModels().forEach(dm -> {
                Object list=modelObj.get(dm.getDataModelName());
                if(list!=null&&list instanceof NestedArray)
                {
                    NestedArray nestedArray=(NestedArray) list;

                    nestedArray.forEach(sub->{
                        sub.copy().setParent(this);
                    });

                }
            });
        }
        if(!StringUtils.isEmpty(modelObj.getRowKey()))
            this.setRowKey(modelObj.getRowKey());

    }

    public ModelObj()
    {

    }

    public ModelObj build(DataModel dataModel)
    {

        this.setDataModel(dataModel);


        if(this.getDataModel()!=null)
        {
            this.getDataModel().getObjectProperties().forEach(property -> {
                Object obj=this.get(property.getPropertyName());
                if(obj!=null&&obj instanceof EntityObj)
                {

                }
                else if(obj!=null&&obj instanceof Map)
                {
                    this.setEntity(property.getPropertyName(), JSON.parseObject(JSON.toJSONString(obj),EntityObj.class));
                }
            });

            this.getDataModel().getNestedDataModels().forEach(dm -> {
                Object list=this.get(dm.getDataModelName());
                if(list!=null&&list instanceof NestedArray)
                {

                }
                else if(list!=null&&list instanceof List)
                {
                    NestedArray nestedArray=new NestedArray().setParent(this);
                    this.set(dm.getDataModelName(),nestedArray);
                    ((List)list).forEach(sub->{
                        if(sub instanceof ModelObj)
                        {
                            nestedArray.add((ModelObj)sub);
                        }
                        else if(sub instanceof Map)
                        {
                            ModelObj subObj=JSON.parseObject(JSON.toJSONString(sub),ModelObj.class);
                            subObj.build(dm).setParent(this);
                        }
                    });

                }
            });
        }
        return this;
    }


    @JsonIgnore
    @JSONField(serialize = false)
    public ModelObj copy() {
        ModelObj copy=new ModelObj();
        if(this.dataModel!=null)
            copy.setDataModel(this.dataModel);


        if(copy.getDataModel()!=null)
        {
            copy.getDataModel().getObjectProperties().forEach(property -> {
                Object obj=this.get(property.getPropertyName());
                if(obj!=null&&obj instanceof EntityObj)
                {
                    copy.setEntity(property.getPropertyName(),((EntityObj)obj).copy());
                }
            });

            copy.getDataModel().getNestedDataModels().forEach(dm -> {
                Object list=this.get(dm.getDataModelName());
                if(list!=null&&list instanceof NestedArray)
                {
                    NestedArray nestedArray=(NestedArray) list;

                    nestedArray.forEach(sub->{
                        sub.copy().setParent(copy);
                    });

                }
            });
        }
        if(!StringUtils.isEmpty(this.getRowKey()))
            copy.setRowKey(this.getRowKey());

        return copy;
    }



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

    @JsonIgnore
    @JSONField(serialize = false)
    public DataModel getDataModel() {
        return dataModel;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public ModelObj setDataModel(DataModel dataModel) {
        if(dataModel!=null) {
            if (!StringUtils.isEmpty(dataModel.getDataModelId()))
                this.setDataModelId(dataModel.getDataModelId());
        }
        this.dataModel = dataModel;
        return this;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public String getDataModelId()
    {
        return this.getStringValue("_DATAMODELID","");
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public ModelObj setDataModelId(String dataModelId)
    {
        return this.set("_DATAMODELID",dataModelId);
    }


    @JsonIgnore
    @JSONField(serialize = false)
    private ModelObj parent;

    @JsonIgnore
    @JSONField(serialize = false)
    public ModelObj getParent() {
        return parent;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public ModelObj setParent(ModelObj parent) {
        this.parent = parent;
        if(parent!=null)
            parent.getNested(this.getDataModel().getDataModelName(),false).add(this);
        return this;
    }

    public EntityObj getEntity(String name)
    {
        return getEntity(name,true);
    }

    public EntityObj getEntity(String name,boolean recursion)
    {
        Object obj=this.get(name);
        if(obj!=null&&obj instanceof EntityObj)
            return (EntityObj) obj;

        Property property=this.getDataModel().getObjectProperty(name);
        if(property!=null) {
            return new EntityObj().setProperty(property).setRowKey(this.getRowKey());
        }

        if(recursion)
        {
            property=this.getDataModel().findObjectProperty(name,"UP");
            if(property!=null){
                int layerDiff=this.getDataModel().getLayerNo()-property.getLayerNo();
                ModelObj parentObj=this;
                for(int i=0;i<layerDiff;i++)
                    parentObj=parentObj.getParent();
                return parentObj.getEntity(name,false);
            }
        }
        
        return new EntityObj().setDstSystemId(this.getDataModel().getFactPorperty().getSystem()).setMetaEntityName(name).setRowKey(this.getRowKey());
    }

    public ModelObj setEntity(String name, EntityObj entityObj)
    {
        if(entityObj!=null)
        {
            if(entityObj.getModelObj()==null)
                entityObj.setModelObj(this);
            this.set(name, entityObj);
        }
        return this;
    }

    public ModelObj setEntityValue(String name, Object value)
    {
        if(name.indexOf(".")>0) {
            String[] epair = name.split("[.]");
            if (epair.length >= 2) {
                String propertyName = epair[0];
                String columnName = epair[1];
                return this.setEntityValue(propertyName,columnName,value);
            }
        }
        return this;
    }

    public ModelObj setEntityValue(String propertyName,String columnName, Object value)
    {
        EntityObj entityObj=null;
        Object obj=this.get(propertyName);
        if(obj!=null&&obj instanceof EntityObj)
            entityObj = (EntityObj) obj;
        else
        {
            Property property=this.getDataModel().getObjectProperty(propertyName);
            if(property!=null) {
                entityObj = new EntityObj().setProperty(property).setRowKey(this.getRowKey());
                this.setEntity(property.getPropertyName(),entityObj);
            }
            else
                return this;
        }
        entityObj.put(columnName,value);

        return this;
    }

    public ModelObj setFactEntity(EntityObj entityObj)
    {
        this.setEntity(this.getDataModel().getFactPorperty().getPropertyName(),entityObj);
        return this;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public EntityObj getFactEntity()
    {
        return this.getEntity(this.getDataModel().getFactPorperty().getPropertyName(),false);
    }

    public NestedArray getNested(String name)
    {
        return getNested(name,true);
    }

    public NestedArray getNested(String name,boolean recursion)
    {
        Object list=this.get(name);
        if(list!=null&&list instanceof NestedArray)
        {
            NestedArray nestedArray=(NestedArray) list;
            if(nestedArray.getParent()==null)
                nestedArray.setParent(this);
            return nestedArray;
        }

        DataModel model=this.getDataModel().getNestedDataModel(name);
        if(model!=null)
        {
            NestedArray array = new NestedArray().setParent(this);
            this.set(model.getDataModelName(),array);
            return array;
        }

        if(recursion)
        {
            model=this.getDataModel().findDataModel(name,"DOWN");
            if(model!=null)
            {
                List<String> steps=new ArrayList<>();
                DataModel tmpModel=model;
                while (tmpModel.getParentDataModel()!=null&&!tmpModel.getParentDataModel().getDataModelName().equalsIgnoreCase(this.getDataModel().getDataModelName()))
                {
                    tmpModel=model.getParentDataModel();
                    steps.add(0,tmpModel.getDataModelName());
                }
				steps.add(model.getDataModelName());
    
                if(steps.size()>1)
                {
                    NestedArray subarry=this.getNested(steps.get(0),false);
                    if(subarry.size()>0)
                    {
                        for(int i=1;i<steps.size();i++)
                        {
                            subarry=subarry.getSubNested(steps.get(i));
                            if(subarry.size()==0)
                                return new NestedArray().setParent(this);
                        }
                        return subarry;
                    }
                }
            }    
        }
        
        return new NestedArray().setParent(this);
    }


    @Override
    public Object get(Object key) {
        if(key==null)
            return null;

        if(key.toString().indexOf(".")>0)
        {
            String[] epair=key.toString().split("[.]");
            if(epair.length>=2)
            {
                String name=epair[0];
                String column=epair[1];
                return this.getEntity(name).get(column);
            }
        }
        return super.get(key);
    }

    @Override
    public ModelObj set(String key, Object value)
    {
        return super.set(key,value);
    }

    @Override
    public String getRowKey()
    {
        String rowKey=super.getRowKey();
        if(StringUtils.isEmpty(rowKey))
        {
            rowKey=this.getFactEntity().getRowKey();
            this.setRowKey(rowKey);
        }
        return rowKey;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    @Override
    public Timestamp getTimestamp()
    {
        Timestamp last = DataObject.getBeginDate();
        for(Object sub:this.values())
        {
            if(sub == null)
                continue;
            else if(sub instanceof EntityObj)
            {
                if(((EntityObj)sub).getTimestamp().getTime()>last.getTime())
                    last=((EntityObj)sub).getTimestamp();
            }
            else if(sub instanceof NestedArray)
            {
                if(((NestedArray)sub).getTimestamp().getTime()>last.getTime())
                    last=((NestedArray)sub).getTimestamp();
            }
        }
        this.setTimestamp(last);
        return last;
    }

    public ModelObj findParent(List<ModelObj> uplayerObjs)
    {
        if(uplayerObjs.size()==1)
        {
            this.setParent(uplayerObjs.iterator().next());
            return this;
        }
        HashMap<String, ModelObj> tmps=new HashMap<String, ModelObj>();
        uplayerObjs.forEach(obj->tmps.put(obj.getRowKey(),obj));
        this.getDataModel().getLayerMappings().forEach(layerMapping->{
            String selfVal=this.getFactEntity().getStringValue(layerMapping.getSelfPropertyColumn());
            uplayerObjs.forEach(parent->{
                String parentVal=parent.getFactEntity().getStringValue(layerMapping.getParentPropertyColumn());
                if((StringUtils.isEmpty(parentVal))||(!parentVal.equals(selfVal)))
                    tmps.remove(parent.getRowKey());
            });  
        });
        if(tmps.size()==1)
        {
            this.setParent(tmps.values().iterator().next());
        }
        else if(tmps.size()>1)
        {
            tmps.values().forEach(parent -> {
                ModelObj copy = this.copy().setTimestamp(this.getTimestamp());
                copy.setParent(parent);
            });
        }
        return this;
    }



    public FieldObj $(String key)
    {
        if(key.toString().indexOf(".")>0)
        {
            String[] epair=key.toString().split("[.]");
            if(epair.length>=2)
            {
                String name=epair[0];
                String column=epair[1];
                Property property=this.getDataModel().findObjectProperty(name,"UP");
                if(property!=null) {
                    Object val=this.getEntity(name).get(column);
                    return new FieldObj().setValue(val).setProperty(property);
                }

                property=this.getDataModel().findObjectProperty(name,"DOWN");
                if(property!=null)
                {
                    Object val=this.getNested(property.getOwnerDataModel().getDataModelName()).get(key);
                    return new FieldObj().setValue(val).setProperty(property);
                }

                if(this.getParent()!=null)
                    return this.getParent().$(key);

            }
        }

        return new FieldObj().setValue(get(key)).setProperty(this.getDataModel().getFactPorperty());
    }

    @JsonIgnore
    @JSONField(serialize = false)
    private FieldObj empty;

    @JsonIgnore
    @JSONField(serialize = false)
    public FieldObj getEmpty()
    {
        if(empty==null)
            empty=new FieldObj().setValue(null).setProperty(this.getDataModel().getFactPorperty());
        return empty;
    }

    public FieldObj $S(String val)
    {
        return new FieldObj().setValue(val).setProperty(this.getDataModel().getFactPorperty());
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public FieldObj $K()
    {
        return new FieldObj().setValue(this.getRowKey()).setProperty(this.getDataModel().getFactPorperty());
    }


    public boolean IF(String cond)
    {
        String[] arr=cond.split("\\s+");
        String option="";
        String members="";
        String exp="";
        if(arr.length>=2)
        {
            members=arr[0];
            option=arr[1];
        }


        Object val=null;
        String[] ms=members.split("[.]");
        if(ms.length>=2&&(!StringUtils.isEmpty(ms[0])))
        {
            EntityObj sub=this.getEntity(ms[0]);
            if(sub.isDelete())
                return false;
            if(sub!=null)
            {
                val=sub.get(ms[1]);
            }
        }

        if(arr.length>=3)
        {
            exp=arr[2];
            if(exp.startsWith("%"))
            {

                return RuleUtils.test(val, option, execFunc(exp));
            }
        }

        return RuleUtils.test(val,option, exp);
    }

    private Object execFunc(String exp)
    {
        exp=exp.replace("%", "").trim();
        if(exp.toUpperCase().startsWith("DAY("))
        {
            Calendar calendar = Calendar.getInstance();
            String quStr=exp.substring(exp.indexOf("(")+1,exp.indexOf(")"));
            quStr=quStr.replace("+","").trim();
            int day=0;
            if(!StringUtils.isEmpty(quStr)){
                day= Integer.parseInt(quStr);
            }

            calendar.add(Calendar.DATE, day);
            return new Timestamp(calendar.getTime().getTime());
        }
        else if(exp.toUpperCase().startsWith("MONTH("))
        {
            Calendar calendar = Calendar.getInstance();
            String quStr=exp.substring(exp.indexOf("(")+1,exp.indexOf(")"));
            quStr=quStr.replace("+","").trim();
            int month=0;
            if(!StringUtils.isEmpty(quStr)){
                month= Integer.parseInt(quStr);
            }

            calendar.add(Calendar.MONTH, month);
            return new Timestamp(calendar.getTime().getTime());
        }
        else if(exp.toUpperCase().startsWith("YEAR("))
        {
            Calendar calendar = Calendar.getInstance();
            String quStr=exp.substring(exp.indexOf("(")+1,exp.indexOf(")"));
            quStr=quStr.replace("+","").trim();
            int year=0;
            if(!StringUtils.isEmpty(quStr)){
                year= Integer.parseInt(quStr);
            }

            calendar.add(Calendar.MONTH, year);
            return new Timestamp(calendar.getTime().getTime());
        }
        else if(exp.endsWith(")"))
        {

            String quStr=exp.substring(exp.indexOf("(")+1,exp.indexOf(")"));

            exp = exp.substring(0,exp.indexOf("(")).trim();
            quStr=quStr.replace("+","").trim();
            Integer day=0;
            if(!StringUtils.isEmpty(quStr)){
                day=  Integer.parseInt(quStr.toUpperCase().replace("Y","").replace("M",""));
            }


            Object val = this.get(exp);

            if(val!=null)
            {
                if(val instanceof Timestamp)
                {
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime((Timestamp)val);
                    if((!StringUtils.isEmpty(quStr))&&(quStr.toUpperCase().indexOf("Y")>=0))
                        calendar.add(Calendar.YEAR, day);
                    else if((!StringUtils.isEmpty(quStr))&&(quStr.toUpperCase().indexOf("M")>=0))
                        calendar.add(Calendar.MONTH, day);
                    else
                        calendar.add(Calendar.DATE, day);
                    return new Timestamp(calendar.getTime().getTime());
                }
                else if(val instanceof Integer)
                {
                    return (int)val+day;
                }
                else if(val instanceof BigDecimal)
                {
                    return ((BigDecimal)val).intValue()+day;
                }
            }

            return val;

        }

        return null;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public boolean isEmpty()
    {
        int cnt=0;

        for(String key:keySet())
        {
            if(key.startsWith("_"))
                continue;
            Object obj=this.get(key);
            if(obj!=null && obj instanceof EntityObj &&((EntityObj)obj).isEmpty())
                continue;
            cnt++;
        }
        if(this.get("_IGNO")!=null)
            return true;
        return cnt==0;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    public Boolean isDelete()
    {
        if(dataModel!=null&&dataModel.getFactPorperty()!=null)
        {
            return getFactEntity().isDelete();
        }
        return false;
    }



}
