/*
 * Copyright 2000-2011 JetBrains s.r.o.
 *
 * 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 jetbrains.buildServer.tools.installer;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import jetbrains.buildServer.core.runtime.IProgressMonitor;
import jetbrains.buildServer.core.runtime.osgx.IToolDescriptor;
import jetbrains.buildServer.core.runtime.osgx.ToolRepository;
import jetbrains.buildServer.serverSide.BuildFeature;
import jetbrains.buildServer.serverSide.BuildStartContext;
import jetbrains.buildServer.serverSide.BuildStartContextProcessor;
import jetbrains.buildServer.serverSide.SBuildFeatureDescriptor;
import jetbrains.buildServer.serverSide.SBuildServer;
import jetbrains.buildServer.serverSide.SBuildType;
import jetbrains.buildServer.serverSide.TeamCityProperties;
import jetbrains.buildServer.tools.ToolsConstants;
import jetbrains.buildServer.tools.ToolsUtil;
import jetbrains.buildServer.web.openapi.PluginDescriptor;

import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.intellij.openapi.util.io.StreamUtil;

public class ToolsInstallerFeature extends BuildFeature implements BuildStartContextProcessor {

  private static final Logger LOG = Logger.getLogger(ToolsInstallerFeature.class);
  private static final IProgressMonitor MONITOR = new ToolsUtil.LoggerProgressMonitor(LOG);

  private String myEditUrl;

  private static ToolRepository ourRepository;

  private static ToolsInstallerFeature ourInstance;

  public static ToolsInstallerFeature getInstance() {
    return ourInstance;
  }

  public ToolsInstallerFeature(final @NotNull SBuildServer server, @NotNull final PluginDescriptor descriptor) {
    myEditUrl = descriptor.getPluginResourcesPath("toolsinstallerSettings.jsp");
    ourInstance = this;
    
  }

  static ToolRepository getToolRepository() throws IOException {
    if (ourRepository == null) {
      //load bundled
      final ToolRepository primaryRepo = ToolRepository.getBundleRepository(MONITOR);
      if (primaryRepo != null) {
        ourRepository = primaryRepo;
        LOG.debug(String.format("Bundled repository loaded"));
      } else {
        LOG.error(String.format("Bundled is not found"));
      }
      //load propertied
      final ToolRepository customRepo = loadCustomRepository();
      if (customRepo != null) {
        if (ourRepository != null) {
          ourRepository.merge(customRepo, MONITOR);
        } else {
          ourRepository = customRepo;
        }
      }
    }
    return ourRepository;
  }

  static ToolRepository loadCustomRepository() {
    final String repoLocationsProp = TeamCityProperties.getProperty(ToolsConstants.TEAMCITY_TOOLS_REPOSITORY_LOCATION_URI);
    if (repoLocationsProp != null && repoLocationsProp.trim().length() > 0) {
      final String[] repoLocations = repoLocationsProp.split(";");
      if (repoLocations.length > 0) {
        try {
          ToolRepository current = new ToolRepository(new URI(repoLocations[0]));
          for (int i = 1; i < repoLocations.length; i++) {
            if (repoLocations[i].trim().length() > 0) {
              current = current.merge(new ToolRepository(new URI(repoLocations[i])), MONITOR);
            }
          }
          return current;
        } catch (URISyntaxException e) {
          LOG.error(e.getMessage(), e);
        }
      }
    }
    return null;
  }

  @Override
  public String getDisplayName() {
    return "Install software";
  }

  @Override
  public String getEditParametersUrl() {
    return myEditUrl;
  }

  @Override
  public String getType() {
    return "tool-installer";
  }

  @Override
  public boolean isMultipleFeaturesPerBuildTypeAllowed() {
    return true;
  }

  @Override
  public String describeParameters(Map<String, String> params) {
    try {
      final String idProp = params.get(ToolsConstants.TEAMCITY_REQUIRED_TOOL_ID_PROP_NAME);
      StringBuffer out = new StringBuffer();
      if (idProp != null) {
        final IToolDescriptor[] tools = getToolRepository().findTool(idProp, null, null, null);
        if (tools != null && tools.length > 0 && tools[0].getDescription() != null) {
          out.append(String.format("Install '%s'", tools[0].getDescription()));
        } else {
          out.append(String.format("Install '%s'", idProp));
        }
        final String versionProp = params.get(ToolsConstants.TEAMCITY_REQUIRED_TOOL_VERSION_PROP_NAME);
        if (versionProp != null) {
          out.append(String.format(" (%s)", versionProp));
        }
        final String installLocationProp = params.get(ToolsConstants.TEAMCITY_REQUIRED_TOOL_INSTALL_LOCATION);
        if (installLocationProp != null && installLocationProp.trim().length() > 0) {
          out.append(String.format(" to '%s'", installLocationProp));
        }
        return out.toString();
      }
    } catch (Exception e) {
      LOG.error(e.getMessage(), e);
    }
    return super.describeParameters(params);
  }

  public void updateParameters(final @NotNull BuildStartContext context) {
    SBuildType buildType = context.getBuild().getBuildType();
    if (buildType == null) {
      return;
    }
    try {
      Collection<SBuildFeatureDescriptor> buildFeatures = buildType.getBuildFeatures();
      for (SBuildFeatureDescriptor bf : buildFeatures) {
        if (bf.getType().equals(getType())) {
          final Map<String, String> bfParameters = bf.getParameters();
          final String requiredToolId = bfParameters.get(ToolsConstants.TEAMCITY_REQUIRED_TOOL_ID_PROP_NAME);
          if (requiredToolId != null) {
            scheduleInstallation(getToolRepository(), context, bf, requiredToolId, bfParameters.get(ToolsConstants.TEAMCITY_REQUIRED_TOOL_VERSION_PROP_NAME), bfParameters.get(ToolsConstants.TEAMCITY_REQUIRED_TOOL_INSTALL_LOCATION), MONITOR);
          }
        }
      }

    } catch (IOException e) {
      LOG.error(e.getMessage(), e);
    }

  }

  private void scheduleInstallation(final @NotNull ToolRepository repo, final @NotNull BuildStartContext context, final @NotNull SBuildFeatureDescriptor bf, final @NotNull String toolId, final @Nullable String toolVersion, final @Nullable String installLocation,
      final @NotNull IProgressMonitor monitor) throws IOException {
    String message = String.format("(%s) New tool installation request found: %s(%s)", bf.getId(), toolId, toolVersion);
    LOG.debug(message);
    final IToolDescriptor[] suitableTools = repo.findTool(toolId, null, null, toolVersion);
    if (suitableTools.length > 0) {
      message = String.format("Suitable tools found: %s", Arrays.asList(suitableTools));
      LOG.debug(message);
      //keep toolId
      final String reqiredToolIdSharedPropName = String.format("%s.%s", ToolsConstants.TEAMCITY_REQUIRED_TOOL_ID_PROP_NAME, bf.getId());
      context.addSharedParameter(reqiredToolIdSharedPropName, toolId);
      //manifest
      final String reqiredToolManifestSharedPropName = String.format("%s.%s", ToolsConstants.TEAMCITY_REQUIRED_TOOL_MANIFEST_PROP_NAME, bf.getId());
      //      final String srepo = new String(StreamUtil.loadFromStream(new BufferedInputStream(ToolsRepositoryAccess.toStream(suitableTools, monitor))));
      final String srepo = new String(StreamUtil.loadFromStream(new BufferedInputStream(new ToolRepository(repo.getSource(), suitableTools).toStream()/*ToolsRepositoryAccess.toStream(suitableTools, monitor)*/)));
      LOG.debug(String.format("Tools manifest loaded: '%s'", srepo));
      context.addSharedParameter(reqiredToolManifestSharedPropName, srepo);
      //version. can be null
      if (toolVersion != null) {
        final String reqiredToolVersionSharedPropName = String.format("%s.%s", ToolsConstants.TEAMCITY_REQUIRED_TOOL_VERSION_PROP_NAME, bf.getId());
        context.addSharedParameter(reqiredToolVersionSharedPropName, getToolVersion(suitableTools));
      }
      //location. can be null
      if (installLocation != null) {
        final String reqiredToolInstallLocationPropName = String.format("%s.%s", ToolsConstants.TEAMCITY_REQUIRED_TOOL_INSTALL_LOCATION, bf.getId());
        context.addSharedParameter(reqiredToolInstallLocationPropName, installLocation);
      }

    } else {
      message = String.format("No suitable tools found for '%s'", toolId);
      LOG.debug(message);
    }

  }

  private String getToolVersion(final @NotNull IToolDescriptor[] tools) {
    return tools[0].getVersion();
  }

  public static IToolDescriptor[] tools() throws IOException {
    IToolDescriptor[] allTools = getToolRepository().tools(MONITOR);
    final ArrayList<IToolDescriptor> out = new ArrayList<IToolDescriptor>();
    for (IToolDescriptor tool : allTools) {
      if (!tool.isSystem()) {
        out.add(tool);
      }
    }
    return out.toArray(new IToolDescriptor[out.size()]);
  }
}
