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.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.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;

public class ModelObj extends DataObj<String,Object> {


    @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 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());
        }
        return this;
    }



    public Object $(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)
                    return this.getEntity(name).get(column);

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

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

            }
        }

        return get(key);
    }

    
    public Double $D(String key)
    {
        Object val=this.get(key);
        if(val==null)
            return 0d;
        else if((val instanceof Integer)||(val instanceof Double)||(val instanceof Long)||(val instanceof BigDecimal)||(val instanceof BigInteger)||(val instanceof Float))
            return (double)val;

        return 1d;
    }



    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.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.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);
            }


            Object val = this.get(exp);

            if(val!=null)
            {
                if(val instanceof Timestamp)
                {
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime((Timestamp)val);
                    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;
    }



}
