/*
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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.jbosson.plugins.fuse.utils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FS;
import org.jbosson.plugins.fuse.JBossFuseContainerTraits;
import org.jbosson.plugins.fuse.JBossFuseProfileGroupManager;
import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException;

/**
 * Implements {@link DataStore} using JGit library.
 *
 * @author dbokde
 */
public class GitDataStoreImpl implements DataStore {

    private static final Log LOG = LogFactory.getLog(GitDataStoreImpl.class);
    private static final String UTF_8 = "UTF-8";

    private static final String MQ_SERVER_BROKER_PROPERTIES = "fabric/profiles/%s.profile/org.fusesource.mq.fabric.server-broker.properties";
    private static final String MQ_TEMPLATE_PROPERTIES = "fabric/profiles/%s.profile/org.fusesource.mq.fabric.template.properties";
    private static final String AGENT_PROPERTIES = "fabric/profiles/%s.profile/io.fabric8.agent.properties";
    private static final String ATTRIBUTES_PROPERTIES = "fabric/profiles/%s.profile/io.fabric8.profile.attributes.properties";
    private static final String AGENT_MVEL_PROPERTIES = AGENT_PROPERTIES + ".mvel";
    private static final String ATTRIBUTE_PREFIX = "attribute.";

    private Repository repository;
    private final RevTree masterTree;
    private final RevTree versionTree;

    public GitDataStoreImpl(File gitRepo, String containerVersion) throws InvalidPluginConfigurationException {

        // connect to Git repo
        try {
            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            RepositoryCache.FileKey key = RepositoryCache.FileKey.lenient(gitRepo, FS.DETECTED);
            repository = builder.setFS(FS.DETECTED)
                    .setGitDir(key.getFile())
                    .setMustExist(true)
                    .build();

            masterTree = getRevTree(Constants.MASTER);
            versionTree = getRevTree(containerVersion);

        } catch (IOException e) {
            throw new InvalidPluginConfigurationException(
                    String.format("Error opening Fabric Git Repo at [%s]: %s", gitRepo, e.getMessage()), e);
        }
    }

    public void close() throws Exception {
        // make sure we close the repo after we are done!
        if (repository != null) {
            repository.close();
        }
    }

    public void getFabricMetadata(String profileName, JBossFuseContainerTraits traits)
            throws InvalidPluginConfigurationException {

        if (!JBossFuseProfileGroupManager.ENSEMBLE_PROFILE_PATTERN.matcher(profileName).matches()) {
            // MQ broker cluster name for MQ Cluster Groups
            String brokerClusterName = null;
            try {

                // try the default properties name
                final String profilePathName = gitPathName(profileName);
                Properties brokerProperties = findProfileProperties(versionTree,
                        new PropertiesAcceptor() {
                            public boolean accept(Properties properties) {
                                return properties.containsKey(JBossFuseProfileGroupManager.MQ_GROUP_PROPERTY);
                            }
                        }, profilePathName, MQ_SERVER_BROKER_PROPERTIES, MQ_TEMPLATE_PROPERTIES);

                if (brokerProperties != null) {
                    LOG.debug("Broker properties found in profile " + profileName);
                    brokerClusterName = brokerProperties.getProperty(JBossFuseProfileGroupManager.MQ_GROUP_PROPERTY);
                    LOG.debug("Found Broker Cluster " + brokerClusterName);
                } else {
                    LOG.debug("Broker properties NOT found in profile " + profileName);
                }

            } catch (IOException e) {
                throw new InvalidPluginConfigurationException(
                        "Error looking up MQ broker properties: " + e.getMessage(), e);
            }

            // add the MQ Cluster to traits
            if (brokerClusterName != null) {
                traits.addCluster(brokerClusterName);
            }
        }

        // now do this all over for parent profiles
        // get parent profiles from Git DataStore
        try {
            String parentList = getParents(profileName);
            if (parentList != null) {
                String[] parents = parentList.split(" ");
                // add parents to trait, returns true if this is a new child
                if (traits.setParentProfiles(profileName, parents)) {
                    // recursively process parents of new child
                    for (String parent : parents) {
                        // make sure the parent has not been processed already
                        if (!traits.hasChildProfile(parent)) {
                            getFabricMetadata(parent, traits);
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new InvalidPluginConfigurationException(
                    "Error getting Parent profiles for " + profileName + ": " + e.getMessage(), e);
        }

    }

    // TODO refactor this to use the DataStore API
    protected String getParents(String profileName) throws IOException {
        final String profilePathName = gitPathName(profileName);

        RevTree tree;
        if (JBossFuseProfileGroupManager.ENSEMBLE_PROFILE_PATTERN.matcher(profileName).matches()) {
            tree = masterTree;
        } else {
            tree = versionTree;
        }

        Properties properties = findProfileProperties(tree,
                new PropertiesAcceptor() {
                    public boolean accept(Properties properties) {
                        return properties.containsKey(JBossFuseProfileGroupManager.PARENTS_PROPERTY) ||
                            properties.containsKey(ATTRIBUTE_PREFIX + JBossFuseProfileGroupManager.PARENTS_PROPERTY);
                    }
                },
                profilePathName, AGENT_PROPERTIES, ATTRIBUTES_PROPERTIES, AGENT_MVEL_PROPERTIES);

        String parents = null;
        if (properties != null) {
            parents = properties.getProperty(JBossFuseProfileGroupManager.PARENTS_PROPERTY);
            if (parents == null) {
                parents = properties.getProperty(ATTRIBUTE_PREFIX + JBossFuseProfileGroupManager.PARENTS_PROPERTY);
            }
        }
        return parents;
    }

    protected static String gitPathName(String profileName) {
        return profileName.replaceAll("-", "/");
    }

    protected static String gitPath(String pathFormat, String profilePathName) {
        return String.format(pathFormat, profilePathName);
    }

    protected Properties findProfileProperties(RevTree tree, PropertiesAcceptor acceptor,
                                               String profile, String... paths) throws IOException {
        for (String path : paths) {
            Properties properties = getPropertiesFile(tree, gitPath(path, profile));
            if (properties != null && acceptor.accept(properties)) {
                return properties;
            }
        }
        return null;
    }

    protected Properties getPropertiesFile(RevTree tree, String pathFilter) throws IOException {
        // try to find a specific file
        TreeWalk treeWalk = TreeWalk.forPath(repository, pathFilter, tree);
        if (treeWalk == null) {
            return null;
        }

        ObjectId objectId = treeWalk.getObjectId(0);
        ObjectLoader loader = repository.open(objectId);

        // use the loader to read the file
        final Properties properties = new Properties();
        properties.load(new InputStreamReader(new ByteArrayInputStream(loader.getBytes()), Charset.forName(UTF_8)));

        return properties;
    }

    protected RevTree getRevTree(String branch) throws IOException {
        final Ref ref = repository.getRef(Constants.R_HEADS + branch);
        if (ref == null) {
            throw new InvalidPluginConfigurationException("Missing Git branch " + branch);
        }
        ObjectId lastCommitId = ref.getObjectId();

        // a RevWalk allows to walk over commits based on some filtering that is defined
        RevWalk revWalk = new RevWalk(repository);
        RevCommit commit = revWalk.parseCommit(lastCommitId);
        // use commit's tree to find the path
        return commit.getTree();
    }

    /**
     * Interface to check whether a profile Property is acceptable, i.e. meets some search criteria.
     */
    private static interface PropertiesAcceptor {
        boolean accept(Properties properties);
    }

}
