package com.brilliance.mda.runtime.mda.driver;

import com.brilliance.mda.runtime.annotation.Transaction;
import com.brilliance.mda.runtime.mda.*;
import com.brilliance.mda.runtime.mda.impl.EnvConfig;

import java.util.*;

import static com.brilliance.mda.runtime.mda.Constants.NO_ERROR;


public class MdaContext implements IContext {

    private transient final IDaoSession daoSession;
    private transient final EnvConfig config;
    private transient final IDisplay display;
    private transient final ILocker locker;

    private Map<String, Object> params = new HashMap<String, Object>();
    private String displayStr;

    private transient IRuleEmitter emmiter;
    private Map<String, Object> modifiedMap = new HashMap<String, Object>();
    private Map<String, String> errorsMap = new HashMap<String, String>();
    private Map<String, Pagination> pages = new HashMap<String, Pagination>();

    private Object vo;
    private String errorMessage = "";
    private String message = "";
    private String errorNo = "";
    private IModule root;
    private int errorCode = NO_ERROR;

    private String transName;
    private String CallStackRootContext = "";
    private Deque<String> ConcurrentCallStack = new LinkedList<>();
    private final Stack<String> rulePathStack = new Stack<>();

    //支持setValues函数
    private Map<String, CodeEntity> valuesSet = new LinkedHashMap<>();


    public MdaContext(IModule root) {
        this.root = root;
        Class<?> rootClass = root.getClass();
        String transName = "";
        if (rootClass.isAnnotationPresent(Transaction.class)) {
            Transaction trnAno = rootClass.getAnnotation(Transaction.class);
            transName = trnAno.value();
        }
        if (MdaEnv.isEmpty(transName))
            transName = rootClass.getSimpleName().toLowerCase();
        MdaEnv.setContext(this); //注册至环境中
        this.transName = transName;
        this.daoSession = MdaEnv.getBean(IDaoSession.class);
        this.config = MdaEnv.getBean(EnvConfig.class);
        this.display = MdaEnv.getBean(IDisplay.class);
        this.locker = MdaEnv.getBean(ILocker.class);
    }

    public MdaContext(IRuleEmitter emitter, IModule root) {
        this(root);
        this.emmiter = emitter;

    }


    public IDaoSession getDaoSession() {
        return daoSession;
    }

    public EnvConfig getEnvConfig() {
        return config;
    }

    public String getTransName() {
        return transName;
    }

    public Map<String, Object> getModified() {
        return modifiedMap;
    }

    public Map<String, String> getFieldErrors() {
        return errorsMap;
    }

    public String getMessage() {
        return message == null ? "" : message;
    }

    public String getErrorNo() {
        return errorNo == null ? "" : errorNo;
    }

    public void setModified(String path, Object value) {
        this.modifiedMap.put(path, value);
    }

    public void setFieldError(String target, String errMsg) {
        target = emmiter.getRealPath(target);
        this.errorsMap.put(target, errMsg);
    }

    public IRuleEmitter getEmitter() {
        return this.emmiter;
    }

    public void setFieldError(IBaseObject baseobj, String errMsg) {
        setFieldError(baseobj.getPath(), errMsg);
    }

    @Override
    public void setFieldError(IBaseObject baseobj, String format, Object... obj) {

    }

    public void setMessage(String errorNo, String message) {
        if (errorNo != null && errorNo.matches("E\\d+")) {
            this.errorNo = errorNo;
            this.message = message;
        }
    }

    public boolean checkAll(IModule module) {
        return this.postCheck(module.getPath());
    }

    public <T> T getDao(Class<T> clazz) {
        return null;
    }

    @Override
    public boolean postRule(RuleType ruleType, String target) {
        if (RuleType.RULE == ruleType)
            return emmiter.executeRule(target, -1);
        else if (RuleType.CHECK == ruleType)
            return emmiter.executeCheck(target, -1);
        else
            return emmiter.executeInit(-1);
    }


    public boolean postRule(IModule m, String target) {
        return postRule(emmiter.getRealPath(target));
    }

    @Override
    public boolean postRule(IModule m, String target, boolean isInModuleList) {
        return false;
    }

    public boolean postRule(String target) {
        return this.postRule(RuleType.RULE, target);
    }

    @Override
    public boolean postRule(IBaseObject baseObject) {
        return postRule(baseObject.getPath());
    }

    @Override
    public boolean postCheck(String target) {
        return this.postRule(RuleType.CHECK, target);
    }

    @Override
    public boolean postInit() {
        return this.postRule(RuleType.INIT, null);
    }

    @Override
    public void setParams(Map<String, Object> params) {
        if (params != null)
            this.params.putAll(params);
    }

    public Map<String, Object> getRetMap() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("data", this.modifiedMap);
        map.put("errors", this.errorsMap);
        map.put("message", this.message);
        map.put("errorno", this.errorNo);
        return map;
    }

    public IModule getRoot() {
        return root;
    }

    public Pagination getPagination(String path) {
        return this.pages.get(path);
    }

    @Override
    public void storeCacheData(String key, Object data) {

    }

    @Override
    public void deleteCacheData(String key) {

    }

    @Override
    public void clearCacheData() {

    }

    @Override
    public Object restoreCacheData(String key) {
        return null;
    }

    public Object restoreData(String key) {
        return this.params.get(key);
    }

    @Override
    public void storeData(String key, Object data) {
        this.params.put(key, data);
    }

    @Override
    public void deleteData(String key) {

    }

    @Override
    public void clearData() {

    }

    public boolean saveDisplay(String filePathOrKey) {
        if (this.display != null && this.displayStr != null)
            return this.display.saveDisplay(filePathOrKey, this.displayStr);
        return false;
    }

    public int getErrorCode() {
        int temp = this.errorCode;
        this.errorCode = NO_ERROR;
        return temp;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    @Override
    public void setErrorCode(int errorCode, String message) {

    }

    @Override
    public void setErrorMessage(String errorMessage) {

    }

    @Override
    public void setErrorMessage(String format, Object... values) {

    }

    @Override
    public void setMessage(String format, Object... values) {

    }

    public void setFieldError(IModule m, String target, String message) {
        setFieldError(m.getFieldPath(target), message);
    }

    @Override
    public void setFieldError(IModule m, String path, String format, Object... obj) {

    }

    public ILocker getLocker() {
        return this.locker;
    }

    @Override
    public Locale getLocale() {
        return Locale.SIMPLIFIED_CHINESE;
    }

    @Override
    public void setLocale(Locale locale) {

    }

    @Override
    public void setEmitter(IRuleEmitter emitter) {
        this.emmiter = emitter;
    }

    @Override
    public void logout() {

    }

    public <T> T getVo() {
        return (T) vo;
    }

    public void setVo(Object vo) {
        this.vo = vo;
    }

    public <T> T absGet(Class<T> clazz) {
        String name = clazz.getSimpleName().toLowerCase();
        return absGet(clazz, name);
    }

    public <T> T absGet(Class<T> clazz, String name) {
        return (T) MdaDriver.getBaseObject(MdaEnv.getContext().getRoot(), name);
    }

    @Override
    public void setConcurrentCallStack(String concurrentCallStack) {
        concurrentCallStack = "\\" + concurrentCallStack.replaceAll("\\.", "\\\\");
//		concurrentCallStack = String.format("%68s", "") + concurrentCallStack;
        CallStackRootContext = concurrentCallStack;
    }


    @Override
    public String getConcurrentCallStack() {
        ConcurrentCallStack.clear();
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            String declaringClass = stackTraceElement.getClassName();
            String className = "";        //长度22
            String methodName = "";        //长度46
            if (declaringClass.startsWith("org")) {
                break;
            }
            if (declaringClass.startsWith("com.ceb") || "getContext".equals(stackTraceElement.getMethodName())) {
                String fileName = stackTraceElement.getFileName();
                className = fileName.contains(".") ? fileName.substring(0, fileName.indexOf(".")) : fileName;
                if ("Platform".equals(className) || "Dynamic".equals(className)) {
                    continue;
                }
                methodName = stackTraceElement.getMethodName();
                if ("init".equals(methodName)) {
                    break;
                }
                className = String.format("%-22s", className);
                methodName = String.format("%-46s", methodName);
                ConcurrentCallStack.addLast((className + methodName).toUpperCase());
            }
        }
        String rootPath = ConcurrentCallStack.removeLast();
        rootPath += CallStackRootContext;
        ConcurrentCallStack.addLast(rootPath);
        StringBuilder sb = new StringBuilder();
        for (String s : ConcurrentCallStack) {
            sb.append(s);
            sb.append("\r\n");
        }
        return sb.toString();
    }

    @Override
    public void pushEventPath(String path) {
        rulePathStack.push(path);
    }

    @Override
    public String getEventPath() {
        return rulePathStack.peek();
    }

    @Override
    public void popEventPath() {
        rulePathStack.pop();
    }

    @Override
    public void setError(IDatafield t) {

    }

    @Override
    public void setError(String str) {

    }

    @Override
    public void reraise() {

    }

    @Override
    public int getErrorState() {
        return 0;
    }

    @Override
    public void resetErrorState() {

    }

    @Override
    public void clearEntireError() {

    }

    @Override
    public Map<String, CodeEntity> getValuesSet() {
        return valuesSet;
    }

    public void visitValues() {
        valuesSet.forEach((key, codeEntity) -> {
            codeEntity.visited = true;
        });
    }


    @Override
    public String getErrorMessage() {
        return this.errorMessage;
    }

    @Override
    public void resetParams(Map<String, Object> params) {
        this.params.clear();
        if (params != null)
            this.params.putAll(params);
    }

    @Override
    public int assignSerialNo() {
        return 0;
    }


    @Override
    public <T> void setAttribute(Object parent, String prop, String ATTR, T val) {
        if (parent instanceof IModule) {
            IModule module = (IModule) parent;
            module.getAttrMaps().put(buildAttrKey(prop, ATTR), val);
        }
    }

    @Override
    public <T> T getAttribute(Object parent, String prop, String ATTR) {
        if (parent instanceof IModule) {
            IModule module = (IModule) parent;
            return (T) module.getAttrMaps().get(buildAttrKey(prop, ATTR));
        }
        return null;
    }

    @Override
    public void mallocPrintBuffer() {

    }

    @Override
    public String releasePrintBuffer() {
        return null;
    }

    @Override
    public StringBuilder getCurPrintBuffer() {
        return null;
    }

    @Override
    public PrintSegment getCurPrintSegment() {
        return null;
    }

    @Override
    public <T> T getParam(String key) {
        return (T) params.get(key);
    }

    @Override
    public <T> void setParam(String key, T t) {
        this.params.put(key, t);
    }


    private String buildAttrKey(String prop, String ATTR) {
        return prop + "/" + ATTR;
    }

    private String buildAttrKey(Object parent, String prop, String ATTR) {
        StringBuilder builder = new StringBuilder();
        if (parent instanceof IModule) {
            String serialNum = ((IModule) parent).getSerialNum();
            builder.append(serialNum).append("/");
        } else {
            builder.append(parent.toString()).append("/");
        }
        builder.append(ATTR).append("/");
        builder.append(prop);
        return builder.toString();
    }


}
