/*
 * 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;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jbosson.plugins.fuse.utils.CuratorFabricRegistryImpl;
import org.jbosson.plugins.fuse.utils.DataStore;
import org.jbosson.plugins.fuse.utils.FabricRegistry;
import org.jbosson.plugins.fuse.utils.GitDataStoreImpl;
import org.jbosson.plugins.fuse.utils.LinkedInFabricRegistryImpl;
import org.jbosson.plugins.fuse.utils.ZooKeeperDataStoreImpl;
import org.rhq.core.domain.cloud.composite.FailoverListComposite;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.measurement.MeasurementDataTrait;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.measurement.MeasurementFacet;

/**
 * Manages Profiles as Resource Groups in RHQ
 *
 * @author dbokde
 */
public class JBossFuseProfileGroupManager implements MeasurementFacet {

    public static final String PARENTS_PROPERTY = "parents";
    private static final Log LOG = LogFactory.getLog(JBossFuseProfileGroupManager.class);

    // TODO these constants are used in both fabric-plugin and fabric-groups-plugin, move them to a common Enum
    private static final String PROFILES_TRAIT = "profiles";
    private static final String PARENT_PROFILES_TRAIT = "parentProfiles";
    private static final String MQ_CLUSTERS_TRAIT = "mqClusters";
    private static final String CONTAINER_VERSION_TRAIT = "container.version";

    private static final String ZOOKEEPER_TIMEOUT = "zookeeper.timeout";
    public static final Pattern ENSEMBLE_PROFILE_PATTERN = Pattern.compile("fabric-ensemble-[0-9]+|fabric-ensemble-[0-9]+-[0-9]+");
    public static final String MQ_GROUP_PROPERTY = "group";
    public static final String CONTAINER_VERSION_PATH_FORMAT = "/fabric/configs/containers/%s";
    public static final String CONTAINER_PROFILES_PATH_FORMAT = "/fabric/configs/versions/%s/containers/%s";
    public static final String FABRIC_REGISTRY_CLUSTERS_JON = "/fabric/registry/clusters/jon";
    public static final String JBOSS_IDENTITY_DIR = "jboss.identity.dir";
    public static final String FAILOVER_LIST_DAT = "failover-list.dat";

    private final ResourceContext context;

    public JBossFuseProfileGroupManager(ResourceContext context) {
        this.context = context;
    }

    public void getValues(MeasurementReport measurementReport,
                          Set<MeasurementScheduleRequest> measurementScheduleRequests) throws Exception {

        // get Container resource key
        final String resourceKey = context.getResourceKey();

        // get server's ZK url and password
        Configuration pluginConfiguration = context.getPluginConfiguration();
        final String zookeeperUrl = pluginConfiguration.getSimpleValue(
            JBossFuseContainerDiscoveryComponent.ZOOKEEPER_URL_PROPERTY);

        if (zookeeperUrl != null) {

            final String containerName = pluginConfiguration.getSimpleValue(
                    JBossFuseContainerDiscoveryComponent.KARAF_NAME_PROPERTY);
            final String zookeeperPassword = pluginConfiguration.getSimpleValue(
                JBossFuseContainerDiscoveryComponent.ZOOKEEPER_PASSWORD_PROPERTY);

            LOG.debug("Processing Fabric Groups for Server " + resourceKey);

            // connect to ZK and get Profiles used by this server
            final Long timeoutSeconds = Long.valueOf(pluginConfiguration.getSimpleValue(ZOOKEEPER_TIMEOUT));

            FabricRegistry registry;
            DataStore dataStore = null;

            if (LinkedInFabricRegistryImpl.isSupported()) {
                registry = new LinkedInFabricRegistryImpl(zookeeperUrl, zookeeperPassword, timeoutSeconds,
                        containerName);
            } else if (CuratorFabricRegistryImpl.isSupported()) {
                registry = new CuratorFabricRegistryImpl(zookeeperUrl, zookeeperPassword, timeoutSeconds,
                        containerName);
            } else {
                throw new IllegalStateException("Neither LinkedIn ZooKeeper nor Apache Curator library was found");
            }

            try {
                // need to call lifecycle methods
                registry.connect();

                // get container version and profiles
                final String containerVersion = registry.getContainerVersion();
                final String[] profiles = registry.getContainerProfiles(containerVersion);

                // process each profile to add Container traits
                final JBossFuseContainerTraits traits = new JBossFuseContainerTraits();
                // don't forget container version
                traits.setContainerVersion(containerVersion);
                // set container profiles
                traits.setProfiles(profiles);

                // check whether we need to use ZooKeeper or JGit to extract profile data
                final String karafBasePath = pluginConfiguration.getSimpleValue(
                    JBossFuseContainerDiscoveryComponent.KARAF_BASE_PROPERTY);
                // get git repo path from plugin configuration
                final String gitRepoPath = pluginConfiguration.getSimpleValue(
                    JBossFuseContainerDiscoveryComponent.GIT_REPO_PROPERTY);
                final File gitRepo = gitRepoPath != null ? new File(gitRepoPath) : null;
                if (gitRepo != null && gitRepo.exists() && gitRepo.isDirectory()) {
                    dataStore = new GitDataStoreImpl(gitRepo, containerVersion);
                } else {
                    dataStore = new ZooKeeperDataStoreImpl(
                            (((LinkedInFabricRegistryImpl)registry).getClient()), containerVersion);
                }


                String zkClusterLocation = FABRIC_REGISTRY_CLUSTERS_JON;

                if(registry.getTextNode(zkClusterLocation) == null) {
                    Properties props = System.getProperties();

                    String configFilePath = "" + props.getProperty(JBOSS_IDENTITY_DIR);
                    File configFile = new File(configFilePath + props.getProperty("file.separator") + FAILOVER_LIST_DAT);

                    String jsonEntry = getJsonEntry(configFile);

                    LOG.info("Adding rest API endpoint to zk registry: " + jsonEntry);
                    registry.writeTextNode(zkClusterLocation, jsonEntry);
                }


                for (String profile : profiles) {
                    LOG.debug("Processing Server profile " + profile + ":" + containerVersion);
                    try {
                        // add Fabric traits for profile
                        dataStore.getFabricMetadata(profile, traits);
                    } catch (Exception e) {
                        throw new IllegalArgumentException("Error processing Server Profile " +
                            profile + ":" + containerVersion + ": " + e.getMessage(), e);
                    }
                }

                // add required traits for measurements
                for (MeasurementScheduleRequest request : measurementScheduleRequests) {
                    final String metricName = request.getName();
                    if (metricName.equals(PROFILES_TRAIT)) {
                        measurementReport.addData(new MeasurementDataTrait(request, traits.getProfileTrait()));
                    } else if (metricName.equals(PARENT_PROFILES_TRAIT)) {
                        measurementReport.addData(new MeasurementDataTrait(request, traits.getParentProfilesTrait()));
                    } else if (metricName.equals(MQ_CLUSTERS_TRAIT)) {
                        measurementReport.addData(new MeasurementDataTrait(request, traits.getClustersTrait()));
                    } else if (metricName.equals(CONTAINER_VERSION_TRAIT)) {
                        measurementReport.addData(new MeasurementDataTrait(request, traits.getContainerVersion()));
                    } else {
                        throw new IllegalArgumentException(
                            String.format("Unknown metric %s for resource %s", metricName, resourceKey));
                    }
                }

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (Exception ex) {
                throw new IllegalArgumentException("Error processing Profile metadata for [" +
                    context.getResourceKey() + "]: " + ex.getMessage(), ex);
            } finally {
                if (dataStore != null) {
                    try {
                        dataStore.close();
                    } catch (Throwable ignore) {}
                }
                try {
                    registry.disconnect();
                } catch (Throwable ignore) {}
            }

        } else {
            LOG.debug("Plugin property " +
                    JBossFuseContainerDiscoveryComponent.ZOOKEEPER_URL_PROPERTY +
                    " is not set for [" + context.getResourceKey() + "], no Fabric data to collect");
        }

    }

    String getJsonEntry(File configFile) throws IOException {
        String fileAsString = FileUtils.readFileToString(configFile, CharEncoding.UTF_8);
        FailoverListComposite failoverListComposite = FailoverListComposite.readAsText(fileAsString);
        List<String> failOverList = new ArrayList<String>();
        for (int i = 0; i < failoverListComposite.size(); i++) {
            String failOverAddress = failoverListComposite.next().toString();
            failOverList.add("http(s)://" + failOverAddress + " /rest");    
        }
        //use JSON serializer, this way we are sure it can be deserialized without error 
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final ObjectMapper mapper = new ObjectMapper();
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("id","jon");
        map.put("services",failOverList);
        mapper.writeValue(out, map);

        return new String(out.toByteArray());
    }

}
