package net.northking.cctp.element.core.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.annotation.*;
import net.northking.cctp.element.core.type.EnumClass;
import net.northking.cctp.element.core.type.NullEnum;
import net.northking.cctp.element.validation.ViolateComponentStandardsException;
import org.apache.commons.lang3.StringUtils;

import net.northking.cctp.element.core.keyword.KeywordArgument;
import net.northking.cctp.element.core.keyword.KeywordReturn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Keyword invoker
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
public class KeywordInvoker implements IKeywordInvoker {

    private final static Logger logger = LoggerFactory.getLogger(KeywordInvoker.class);
    /**
     * java class method name
     **/
    private final Method method;
    /**
     * bean object of library keyword class
     **/
    private final Object obj;

    public KeywordInvoker(Object obj, Method method) {
        this.obj = obj;
        this.method = method;
    }

    @Override
    public List<KeywordArgument> getArguments() {
        Parameter[] parameters = method.getParameters();
        List<KeywordArgument> keywordArguments = new ArrayList<KeywordArgument>();
        if (parameters.length == 1) {
            ArgumentBusMap argumentBusMap = parameters[0].getAnnotation(ArgumentBusMap.class);
            if (argumentBusMap != null) {
                return keywordArguments;
            }
        }
        for (Parameter parameter : parameters) {
            if (parameter.getType() == IExecuteContext.class) {
                continue;
            }
            Argument atpArgument = parameter.getAnnotation(Argument.class);
            if (atpArgument == null) {
                throw new ViolateComponentStandardsException("Keyword parameters must be annotated with @Argument.");
            }
            KeywordArgument keywordArgument = new KeywordArgument();
            keywordArgument.setName(atpArgument.name());
            keywordArgument.setComment(atpArgument.comment());
            keywordArgument.setType(atpArgument.type());
            keywordArgument.setScope(atpArgument.scope());
            keywordArgument.setInputType(atpArgument.inputType());
            keywordArgument.setDefaultDisplay(atpArgument.defaultDisplay());
            keywordArgument.setActualType(parameter.getParameterizedType().getTypeName());
            keywordArgument.setRequired(atpArgument.required());
            keywordArgument.setDefaultValue(atpArgument.defaultValue());
            keywordArgument.setEnumValues(getData(atpArgument));
            keywordArgument.setHideSensitive(atpArgument.hideSensitive());
            keywordArgument.setRelateEnv(atpArgument.relateEnv());
            keywordArgument.setExpression(atpArgument.expression());
            keywordArguments.add(keywordArgument);
        }
        return keywordArguments;
    }

    private String getData(Argument atpArgument) {
        String result = "";
        try {
            Class<? extends EnumClass> clz = atpArgument.enumValues();
            if (NullEnum.class != clz) {
                Method method = clz.getDeclaredMethod("getEnumData");
                Object obj = method.invoke(clz.getEnumConstants()[0]);
                result = obj.toString();
            }
        } catch (Exception e) {
            logger.error("",e);
        }
        return result;
    }

    @Override
    public Object invoke(Map<String, Object> args, IExecuteContext context) throws Exception {
        try {
            Object[] groupedArguments = createArgumentGrouper().groupArguments(args, context);
            Object[] convertedArguments = createArgumentConverter()
                    .convertArguments(groupedArguments);
            return method.invoke(obj, convertedArguments);
        } catch (Exception e) {
            throw e;
        }
    }

    @Override
    public String getDocumentation() {
        return method.getAnnotation(Keyword.class).value();
    }

    @Override
    public boolean isDeprecated() {
        return method.getAnnotation(Deprecated.class) != null;
    }

    @Override
    public boolean isBizHidden() {
        return method.getAnnotation(Keyword.class).hidden();
    }

    @Override
    public String getCategory() {
        String category = method.getAnnotation(Keyword.class).category();
        if (StringUtils.isBlank(category)) {
            return obj.getClass().getAnnotation(Keywords.class).value();
        }
        return category;
    }

    @Override
    public String getAttributes() {
        String attributes = method.getAnnotation(Keyword.class).attributes();
        if (StringUtils.isBlank(attributes)) {
            return "0";
        }
        return attributes;
    }

    @Override
    public boolean isCommonlyUse() {
        return method.getAnnotation(Keyword.class).commonlyUse();
    }

    @Override
    public String getName() {
        return method.getName();
    }

    @Override
    public String getAlias() {
        String alias = method.getAnnotation(Keyword.class).alias();
        return StringUtils.isBlank(alias) ? method.getName() : alias;
    }

    IArgumentConverter createArgumentConverter() {
        return new ArgumentConverter(method.getParameterTypes());
    }

    IArgumentGrouper createArgumentGrouper() {
        return new ArgumentGrouper(method.getParameters());
    }

    @Override
    public KeywordReturn getReturn() {
        Return an = method.getAnnotation(Return.class);
        if (an != null) {
            KeywordReturn kr = new KeywordReturn();
            kr.setName(an.name());
            kr.setComment(an.comment());
            kr.setType(an.type());
            String actualType = method.getGenericReturnType().getTypeName();
            kr.setActualType(actualType);
            return kr;
        }
        return null;
    }
}