/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.apache.ambari.server.controller.internal;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.google.gson.JsonSyntaxException;
import com.google.inject.Injector;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.PropertyProvider;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.SystemException;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.apache.ambari.server.controller.utilities.StreamProvider;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
 * Property provider that is used to read HTTP data from another server.
 */
public class HttpProxyPropertyProvider extends BaseProvider implements PropertyProvider {

  protected final static Logger LOG =
      LoggerFactory.getLogger(HttpProxyPropertyProvider.class);

  private static final Map<String, String> URL_TEMPLATES = new HashMap<String, String>();
  private static final Map<String, String> MAPPINGS = new HashMap<String, String>();
  private static final Map<String, String> PROPERTIES_TO_FILTER = new HashMap<String, String>();

  private static final String COMPONENT_RESOURCEMANAGER = "RESOURCEMANAGER";
  private static final String CONFIG_YARN_SITE = "yarn-site";
  private static final String CONFIG_CORE_SITE = "core-site";
  private static final String PROPERTY_YARN_HTTP_POLICY = "yarn.http.policy";
  private static final String PROPERTY_HADOOP_SSL_ENABLED = "hadoop.ssl.enabled";
  private static final String PROPERTY_YARN_HTTP_POLICY_VALUE_HTTPS_ONLY = "HTTPS_ONLY";
  private static final String PROPERTY_HADOOP_SSL_ENABLED_VALUE_TRUE = "true";

  static {
    URL_TEMPLATES.put(COMPONENT_RESOURCEMANAGER, "http://%s:8088/ws/v1/cluster/info");
    
    MAPPINGS.put(COMPONENT_RESOURCEMANAGER, PropertyHelper.getPropertyId("HostRoles", "ha_state"));

    PROPERTIES_TO_FILTER.put(COMPONENT_RESOURCEMANAGER, "clusterInfo/haState");
  }

  private final ComponentSSLConfiguration configuration;

  private StreamProvider streamProvider = null;
  // !!! not yet used, but make consistent
  private String clusterNamePropertyId = null;
  private String hostNamePropertyId = null;
  private String componentNamePropertyId = null;

  private Injector injector;
  private Clusters clusters;
  
  public HttpProxyPropertyProvider(
      StreamProvider stream,
      ComponentSSLConfiguration configuration,
      Injector inject,
      String clusterNamePropertyId,
      String hostNamePropertyId,
      String componentNamePropertyId) {

    super(new HashSet<String>(MAPPINGS.values()));
    this.streamProvider = stream;
    this.configuration = configuration;
    this.clusterNamePropertyId = clusterNamePropertyId;
    this.hostNamePropertyId = hostNamePropertyId;
    this.componentNamePropertyId = componentNamePropertyId;
    this.injector = inject;
    this.clusters = injector.getInstance(Clusters.class);
  }

  /**
   * This method only checks if an HTTP-type property should be fulfilled.  No
   * modification is performed on the resources.
   */
  @Override
  public Set<Resource> populateResources(Set<Resource> resources,
      Request request, Predicate predicate) throws SystemException {
    
    Set<String> ids = getRequestPropertyIds(request, predicate);
    
    if (0 == ids.size())
      return resources;

    for (Resource resource : resources) {
      
      Object hostName = resource.getPropertyValue(hostNamePropertyId);
      Object componentName = resource.getPropertyValue(componentNamePropertyId);
      Object clusterName = resource.getPropertyValue(clusterNamePropertyId);

      if (null != hostName && null != componentName &&
          MAPPINGS.containsKey(componentName.toString()) &&
          URL_TEMPLATES.containsKey(componentName.toString())) {
        
        String template = getTemplate(componentName.toString(), clusterName.toString());
        String propertyId = MAPPINGS.get(componentName.toString());
        String url = String.format(template, hostName);
        
        getHttpResponse(resource, url, propertyId);
      }
    }
    
    return resources;
  }

  private String getTemplate(String componentName, String clusterName) throws SystemException {
    String template = URL_TEMPLATES.get(componentName);

    if (componentName.equals(COMPONENT_RESOURCEMANAGER)) {
      try {
        Cluster cluster = this.clusters.getCluster(clusterName);
        Map<String, String> yarnConfigProperties = cluster.getDesiredConfigByType(CONFIG_YARN_SITE).getProperties();
        Map<String, String> coreConfigProperties = cluster.getDesiredConfigByType(CONFIG_CORE_SITE).getProperties();
        String yarnHttpPolicy = yarnConfigProperties.get(PROPERTY_YARN_HTTP_POLICY);
        String hadoopSslEnabled = coreConfigProperties.get(PROPERTY_HADOOP_SSL_ENABLED);
        if ((yarnHttpPolicy != null && yarnHttpPolicy.equals(PROPERTY_YARN_HTTP_POLICY_VALUE_HTTPS_ONLY)) ||
             hadoopSslEnabled != null && hadoopSslEnabled.equals(PROPERTY_HADOOP_SSL_ENABLED_VALUE_TRUE)) {
          template = template.replace("http", "https");
        }
      } catch (AmbariException e) {
          LOG.debug(String.format("Could not load cluster with name %s. %s", clusterName, e.getMessage()));
          throw new SystemException(String.format("Could not load cluster with name %s.", clusterName),e);
      }
    }
    return template;
  }

  private Object getPropertyValueToSet(Map<String, Object> propertyValueFromJson, Object componentName) throws SystemException {
    Object result = propertyValueFromJson;
    //TODO need refactoring for universalization
    try {
      if (PROPERTIES_TO_FILTER.get(componentName) != null) {
        for (String key : PROPERTIES_TO_FILTER.get(componentName).split("/")) {
          result = ((Map)result).get(key);
        }
      }
    } catch (ClassCastException e) {
        LOG.error(String.format("Error getting property value for %s. %s", PROPERTIES_TO_FILTER.get(componentName),
              e.getMessage()));
        throw new SystemException(String.format("Error getting property value for %s.",
                PROPERTIES_TO_FILTER.get(componentName)),e);
    }
    return result;
  }

  private void getHttpResponse(Resource r, String url, String propertyIdToSet) throws SystemException {
    InputStream in = null;
    try {
      in = streamProvider.readFrom(url);
      Type mapType = new TypeToken<Map<String, Object>>(){}.getType();
      Map<String, Object> propertyValueFromJson = new Gson().fromJson(IOUtils.toString(in, "UTF-8"), mapType);
      Object propertyValueToSet = getPropertyValueToSet(propertyValueFromJson,
              r.getPropertyValue(componentNamePropertyId));
      r.setProperty(propertyIdToSet, propertyValueToSet);
    }
    catch (IOException ioe) {
      LOG.debug("Error reading HTTP response from " + url);
      r.setProperty(propertyIdToSet, null);
    } catch (JsonSyntaxException jse) {
      LOG.error("Error parsing HTTP response from " + url);
      r.setProperty(propertyIdToSet, null);
    } finally {
      if (in != null) {
        try {
          in.close();
        }
        catch (IOException ioe) {
          LOG.error("Error closing HTTP response stream " + url);
        }
      }
    }
    
  }

}
