/*
 * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.synapse.commons.resolvers;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.ServiceLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *  Resolver Factory which can be used to register resolvers and retrieve a resolver for a given type.
 */
public class ResolverFactory {

    private static final Log LOG = LogFactory.getLog(ResolverFactory.class);
    private static final int RESOLVER_INDEX = 2;
    private static final ResolverFactory resolverFactory = new ResolverFactory();
    private final Pattern rePattern = Pattern.compile("(\\$)([_a-zA-Z0-9]+):([_a-zA-Z0-9]+)");
    private final Pattern configPattern = Pattern.compile("(\\$\\{)([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)(})");
    private static final String SYSTEM_VARIABLE_PREFIX = "$SYSTEM";
    private static final String FILE_PROPERTY_VARIABLE_PREFIX = "$FILE";
    private static final String CUSTOM_PROPERTY_VARIABLE_PREFIX = "$CUSTOM_";
    private static final String CONFIGURABLE_VARIABLE_PREFIX = "${configs.";

    private final Map<String, Class<? extends Resolver>> resolverMap = new HashMap<>();

    /**
     * This function return an object of Resolver factory
     * @return resolverFactory
     */
    public static ResolverFactory getInstance() {
        return resolverFactory;
    }

    private ResolverFactory() {
        registerResolvers();
        registerExterns();
    }

    /**
     * This function returns a resolver based on the variable passed
     * @param input variable which is used decode and retrieve the resolver
     * @return resolver object
     */
    public Resolver getResolver(String input) {

        if (input == null) {
            return null;
        }
        Matcher matcher;
        if (input.startsWith(CONFIGURABLE_VARIABLE_PREFIX)) {
            matcher = configPattern.matcher(input);
            if (matcher.find()) {
                return getResolver(matcher.group(RESOLVER_INDEX).toLowerCase(), matcher);
            }
        } else if (input.startsWith(SYSTEM_VARIABLE_PREFIX)) {
            matcher = rePattern.matcher(input);
            if (matcher.find()) {
                return getResolver(matcher.group(RESOLVER_INDEX).toLowerCase(), matcher);
            }
        } else if(input.startsWith(FILE_PROPERTY_VARIABLE_PREFIX)) {
            matcher = rePattern.matcher(input);
            if (matcher.find()) {
                return getResolver(matcher.group(RESOLVER_INDEX).toLowerCase(), matcher);
            }
        } else if(input.startsWith(CUSTOM_PROPERTY_VARIABLE_PREFIX)) {
            matcher = rePattern.matcher(input);
            if (matcher.find()){
                String nameWithPlaceholder = matcher.group(RESOLVER_INDEX).toLowerCase();
                String className = nameWithPlaceholder.substring(CUSTOM_PROPERTY_VARIABLE_PREFIX.length() - 1);
                if (matcher.find()) {
                    return getResolver(className, matcher);
                }
            }
        }

        Resolver resolver = new DefaultResolver();
        resolver.setVariable(input);
        return resolver;

    }

    private void registerResolvers() {
        resolverMap.put("system", SystemResolver.class);
        resolverMap.put("file", FilePropertyResolver.class);
        resolverMap.put("configs", ConfigResolver.class);
    }

    private void registerExterns() {
        ServiceLoader<Resolver> loaders = ServiceLoader.load(Resolver.class);
        for (Resolver resolver : loaders) {
            String className = resolver.getClass().getName();
            String[] packageList = className.split("\\.");
            className = packageList[packageList.length - 1];
            if (resolverMap.get(className.toLowerCase()) == null) {
                resolverMap.put(className.toLowerCase(), resolver.getClass());
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Added Resolver " + className + " to resolver factory ");
                }
            }
            else {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Failed to Resolver " + className + " to resolver factory. Already exist");
                }
            }
        }
    }

    private Resolver getResolver(String className, Matcher matcher) {
        Class<? extends Resolver> resolverClass = resolverMap.get(className);
        if (resolverClass != null) {
            try {
                Resolver resolverObject = resolverClass.newInstance();
                resolverObject.setVariable(matcher.group(3));
                return resolverObject;
            } catch (IllegalAccessException | InstantiationException e) {
                throw new ResolverException("Resolver could not be initialized", e);
            }
        } else {
            throw new ResolverException("Resolver could not be found");
        }
    }
}
