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

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.reflect.IKeywordInvoker;
import net.northking.cctp.element.core.reflect.KeywordInvoker;
import org.slf4j.LoggerFactory;

/**
 * Keyword extractor
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
public class KeywordExtractor implements IKeywordExtractor<Keyword> {

    private static org.slf4j.Logger logger = LoggerFactory.getLogger(KeywordExtractor.class);

    @Override
    public Map<String, Keyword> extractKeywords(final Object keywordBean) {
        logger.debug("keyword class {}", keywordBean.getClass().getCanonicalName());
        Map<String, Keyword> extractedKeywords = new HashMap<>();
        Method[] methods = keywordBean.getClass().getMethods();
        for (final Method method : methods) {
            if (method.isAnnotationPresent(net.northking.cctp.element.core.annotation.Keyword.class)) {
                createOrAddKeyword(extractedKeywords, keywordBean, method);
            }
        }
        return extractedKeywords;
    }

    private void createOrAddKeyword(Map<String, Keyword> extractedKeywords, Object keywordBean,
                                    Method method) {
        String name = method.getName();
        if (extractedKeywords.containsKey(name)) {
            throw new AssertionError(
                    "Two keywords Method definition should not have the same name");
        } else {
            extractedKeywords.put(name, createKeyword(keywordBean, method));
        }
    }

    IKeywordInvoker createKeywordInvoker(Object keywordBean, Method method) {
        return new KeywordInvoker(keywordBean, method);
    }

    private Keyword createKeyword(Object keywordBean, Method method) {
        IKeywordInvoker keywordInvoker = createKeywordInvoker(keywordBean, method);
        return createKeyword(keywordInvoker);
    }

    private Keyword createKeyword(final IKeywordInvoker keywordInvoker) {
        return new DefaultKeyword(keywordInvoker);
    }

    static class DefaultKeyword implements Keyword {
        protected Map<String, KeywordArgument> argumentMap = new LinkedHashMap<>();
        protected IKeywordInvoker keywordInvoker;

        public DefaultKeyword(IKeywordInvoker keywordInvoker) {
            this.keywordInvoker = keywordInvoker;
            Map<String, KeywordArgument> map = getArguments().stream().collect(Collectors.toMap(r -> r.getName(), r -> r));
            this.argumentMap.putAll(map);
        }

        public Object execute(Map<String, Object> arguments, IExecuteContext context) throws Exception {
            return keywordInvoker.invoke(arguments, context);
        }

        public String getDocumentation() {
            return keywordInvoker.getDocumentation();
        }

        @Override
        public List<KeywordArgument> getArguments() {
            return keywordInvoker.getArguments();
        }

        @Override
        public Map<String, KeywordArgument> getArgumentMap() {
            return argumentMap;
        }

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

        @Override
        public String getAlias() {
            return keywordInvoker.getAlias();
        }

        @Override
        public KeywordReturn getReturn() {
            return keywordInvoker.getReturn();
        }

        @Override
        public boolean isDeprecated() {
            return keywordInvoker.isDeprecated();
        }

        @Override
        public boolean isHidden() {
            return keywordInvoker.isBizHidden();
        }

        @Override
        public String getCategory() {
            return keywordInvoker.getCategory();
        }

        @Override
        public String getAttributes() {
            return keywordInvoker.getAttributes();
        }

        @Override
        public boolean isCommonlyUse() {
            return keywordInvoker.isCommonlyUse();
        }
    }
}
