/*
 * Copyright 2017 Google, Inc.
 *
 * 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 com.netflix.spinnaker.halyard.deploy.spinnaker.v1.service;

import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spinnaker.halyard.config.config.v1.HalconfigDirectoryStructure;
import com.netflix.spinnaker.halyard.config.model.v1.node.DeploymentConfiguration;
import com.netflix.spinnaker.halyard.core.error.v1.HalException;
import com.netflix.spinnaker.halyard.core.problem.v1.Problem;
import com.netflix.spinnaker.halyard.deploy.services.v1.ArtifactService;
import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerArtifact;
import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.SpinnakerRuntimeSettings;
import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.CustomProfileFactory;
import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.Profile;
import com.netflix.spinnaker.halyard.deploy.spinnaker.v1.profile.ProfileFactory;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.Yaml;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@Data
@Component
@Slf4j
abstract public class SpinnakerService<T> implements HasServiceSettings<T> {
  @Autowired
  ObjectMapper objectMapper;

  @Autowired
  ArtifactService artifactService;

  @Autowired
  Yaml yamlParser;

  @Autowired
  HalconfigDirectoryStructure halconfigDirectoryStructure;

  @Override
  public SpinnakerService<T> getService() {
    return this;
  }

  @Override
  public String getServiceName() {
    return getType().getServiceName();
  }

  public String getCanonicalName() {
    return getType().getCanonicalName();
  }

  public String getSpinnakerStagingPath(String deploymentName) {
    return halconfigDirectoryStructure.getStagingPath(deploymentName).toString();
  }

  public ServiceSettings getDefaultServiceSettings(DeploymentConfiguration deploymentConfiguration) {
    File userSettingsFile = new File(
        halconfigDirectoryStructure.getUserServiceSettingsPath(deploymentConfiguration.getName()).toString(),
        getCanonicalName() + ".yml"
    );

    if (userSettingsFile.exists() && userSettingsFile.length() != 0) {
      try {
        log.info("Reading user provided service settings from " + userSettingsFile);
        return objectMapper.convertValue(
            yamlParser.load(new FileInputStream(userSettingsFile)),
            ServiceSettings.class
        );
      } catch (FileNotFoundException e) {
        throw new HalException(Problem.Severity.FATAL, "Unable to read provided user settings: " + e.getMessage(), e);
      }
    } else {
      return new ServiceSettings();
    }
  }

  public boolean isInBillOfMaterials(DeploymentConfiguration deployment) {
    String version = getArtifactService().getArtifactVersion(deployment.getName(), getArtifact());
    return (version != null);
  }

  abstract public Type getType();
  abstract public Class<T> getEndpointClass();
  abstract public List<Profile> getProfiles(DeploymentConfiguration deploymentConfiguration, SpinnakerRuntimeSettings endpoints);

  abstract protected Optional<String> customProfileOutputPath(String profileName);

  public Optional<Profile> customProfile(DeploymentConfiguration deploymentConfiguration, SpinnakerRuntimeSettings runtimeSettings, Path profilePath, String profileName) {
    return customProfileOutputPath(profileName).flatMap(outputPath -> {
      SpinnakerArtifact artifact = getArtifact();
      ProfileFactory factory = new CustomProfileFactory() {
        @Override
        public SpinnakerArtifact getArtifact() {
          return artifact;
        }

        protected ArtifactService getArtifactService() {
          return artifactService;
        }

        @Override
        protected Path getUserProfilePath() {
          return profilePath;
        }
      };

      return Optional.of(factory.getProfile(profileName, outputPath, deploymentConfiguration, runtimeSettings));
    });
  }

  public boolean hasTypeModifier() {
    return getType().getModifier() != null;
  }

  public String getTypeModifier() {
    return getType().getModifier();
  }

  public enum Type {
    CLOUDDRIVER("clouddriver"),
    CLOUDDRIVER_BOOTSTRAP(CLOUDDRIVER, "bootstrap"),
    CLOUDDRIVER_CACHING(CLOUDDRIVER, "caching"),
    CLOUDDRIVER_RO(CLOUDDRIVER, "ro"),
    CLOUDDRIVER_RW(CLOUDDRIVER, "rw"),
    CONSUL_CLIENT("consul-client"),
    CONSUL_SERVER("consul-server"),
    DECK("deck"),
    ECHO("echo"),
    ECHO_SCHEDULER(ECHO, "scheduler"),
    ECHO_SLAVE(ECHO, "slave"),
    FIAT("fiat"),
    FRONT50("front50"),
    GATE("gate"),
    IGOR("igor"),
    KAYENTA("kayenta"),
    ORCA("orca"),
    ORCA_BOOTSTRAP(ORCA, "bootstrap"),
    REDIS("redis"),
    REDIS_BOOTSTRAP(REDIS, "bootstrap"),
    ROSCO("rosco"),
    MONITORING_DAEMON("monitoring-daemon"),
    VAULT_CLIENT("vault-client"),
    VAULT_SERVER("vault-server");

    @Getter
    final String canonicalName;
    @Getter
    final String serviceName;
    @Getter
    final String modifier;
    @Getter
    final Type baseType;

    Type(String canonicalName) {
      this.canonicalName = canonicalName;
      this.serviceName = "spin-" + canonicalName;
      this.modifier = null;
      this.baseType = this;
    }

    Type(Type baseType, String modifier) {
      this.canonicalName = baseType.getCanonicalName() + "-" + modifier;
      this.serviceName = "spin-" + this.canonicalName;
      this.baseType = baseType;
      this.modifier = modifier;
    }

    @Override
    @JsonValue
    public String toString() {
      return canonicalName;
    }

    private static String reduceName(String name) {
      return name.replace("-", "").replace("_", "");
    }

    public static Type fromCanonicalName(String canonicalName) {
      String finalName = reduceName(canonicalName);

      return Arrays.stream(values())
          .filter(t -> reduceName(t.getCanonicalName()).equalsIgnoreCase(finalName))
          .findFirst()
          .orElseThrow(() -> new IllegalArgumentException("No service with canonical name " + canonicalName + " exists."));
    }
  }
}

@@ -757,24 +757,66 @@
 jectMapper;%0A
+import com.google.common.base.CaseFormat;%0A
 import com.n
@@ -6360,30 +6360,16 @@
 %0A    %7D%0A%0A
-    @Override%0A
     @Jso
@@ -6397,33 +6397,177 @@
 ing 
-toString() %7B%0A      return
+asYamlKey() %7B%0A      // When SpinnakerRuntimeSettings is serialized, we expect its keys to be camel-cased.%0A      return CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL,
  can
@@ -6572,24 +6572,25 @@
 anonicalName
+)
 ;%0A    %7D%0A%0A   

Make sure spinnaker.yml services are camel-cased rather than hyphenated. (#1028)