/*
 * Copyright 2000-2013 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.clouds.vmware;

import com.intellij.openapi.diagnostic.Logger;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import jetbrains.buildServer.clouds.*;
import jetbrains.buildServer.clouds.vmware.settings.VMWareSettings;
import jetbrains.buildServer.clouds.vmware.vmrun.VMRun;
import jetbrains.buildServer.clouds.vmware.vmrun.VMRunFactory;
import jetbrains.buildServer.clouds.vmware.vmrun.remote.server.ClientState;
import jetbrains.buildServer.serverSide.AgentDescription;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * @author Eugene Petrenko
 *         Created: 07.12.2009 19:19:16
 */
public class VMWareCloudClientEx extends ErrorHolder implements CloudClientEx {
  private static final Logger LOG = Logger.getInstance(VMWareCloudClientEx.class.getName());
  private final ScheduledExecutorService myExecutor;
  private final Map<String, VMRemoteHost> myHosts = new HashMap<String, VMRemoteHost>();
  private final AtomicBoolean myHostsInSync = new AtomicBoolean(false);

  public VMWareCloudClientEx(final VMWareSettings settings, final VMRunFactory vmRunFactory, @NotNull final ExecutorServiceFactory factory) {
    myExecutor = factory.createExecutorService("VMRunClients");

    //TODO: watch each image in separate thread!
    //TODO: represent available (image,machine) pairs as permanent instnaces

    myExecutor.scheduleWithFixedDelay(wrap("Update running VMRunClients", new Runnable() {
      public void run() {
        final Collection<VMRun> vmRuns = vmRunFactory.getAvailableRunners();
        Set<String> obsoleteKeys = new HashSet<String>(myHosts.keySet());

        //TODO: too much work for one lock
        synchronized (VMWareCloudClientEx.this) {
          for (VMRun _vmRun : vmRuns) {
            final VMRun vmRun = new CachedVMRun(_vmRun);
            try {
              final String locationId = vmRun.getLocationId();
              obsoleteKeys.remove(locationId);

              if (!myHosts.containsKey(locationId)) {
                VMRemoteHost host = new VMRemoteHost(vmRun, (ClientState)_vmRun, factory.createExecutorService(vmRun.getLocationId()));
                myHosts.put(locationId, host);
              }
            } catch (Throwable t) {
              LOG.warn("Failed to communicate with a client. " + t.getMessage());
            }
          }

          for (String obsoleteKey : obsoleteKeys) {
            final VMRemoteHost host = myHosts.remove(obsoleteKey);
            if (host != null) {
              host.dispose();
            }
          }
        }

        myHostsInSync.set(true);
      }
    }), 30, 30, TimeUnit.SECONDS);
  }

  @Nullable
  private synchronized VMRemoteHost findHostByImage(@NotNull final CloudImage image) {
    final VMWareImage img = (VMWareImage)image;
    return myHosts.get(img.getRemoteHostId());
  }

  @NotNull
  public CloudInstance startNewInstance(@NotNull final CloudImage image, @NotNull final CloudInstanceUserData tag) throws QuotaException {
    final VMRemoteHost host = findHostByImage(image);
    if (host == null) {
      throw new CloudException("Failed to start image. Image is gone");
    }
    return host.startNewInstance((VMWareImage)image, tag);
  }

  public boolean canStartNewInstance(@NotNull final CloudImage image) {
    final VMRemoteHost host = findHostByImage(image);
    if (host == null) {
      throw new CloudException("Failed to start image. Image is gone");
    }

    return host.canStartNewInstance((VMWareImage)image);
  }

  public void restartInstance(@NotNull final CloudInstance instance) {
  }

  public void terminateInstance(@NotNull final CloudInstance instance) {
    final VMRemoteHost host = findHostByImage(instance.getImage());
    if (host == null) {
      throw new CloudException("Failed to start image. Image is gone");
    }
    host.terminateInstance((VMWareInstance)instance);
  }

  public synchronized void dispose() {
    for (VMRemoteHost host : myHosts.values()) {
      host.dispose();
    }
    myHosts.clear();
    myExecutor.shutdownNow();
  }

  public synchronized boolean isInitialized() {
    if (!myHostsInSync.get()) return false;

    for (VMRemoteHost host : myHosts.values()) {
      if (!host.isInitialized()) return false;
    }
    return true;
  }

  @NotNull
  private synchronized Collection<VMRemoteHost> getHosts() {
    return new ArrayList<VMRemoteHost>(myHosts.values());
  }

  public CloudImage findImageById(@NotNull final String imageId) throws CloudException {
    for (VMRemoteHost host : getHosts()) {
      final CloudImage image = host.findImageById(imageId);
      if (image != null) return image;
    }
    return null;
  }


  public CloudInstance findInstanceByAgent(@NotNull final AgentDescription agent) {
    final Map<String, String> map = agent.getDefinedParameters();
    final String imageId = map.get(VMWareConstants.WM_IMAGE_ID);
    final String instanceId = map.get(VMWareConstants.WM_INSTANCE_ID);

    if (imageId == null || instanceId == null) return null;

    final CloudImage id = findImageById(imageId);
    if (id == null) return null;

    return id.findInstanceById(instanceId);
  }

  @NotNull
  public Collection<? extends CloudImage> getImages() throws CloudException {
    List<CloudImage> result = new ArrayList<CloudImage>();
    for (VMRemoteHost host : getHosts()) {
      result.addAll(host.getImages());
    }
    return result;
  }

  @Nullable
  public String generateAgentName(@NotNull final AgentDescription agent) {
    return agent.getDefinedParameters().get(VMWareConstants.VMWARE_NAME_PROPOSAL);
  }
}
