/*-
 * -\-\-
 * Helios Tools
 * --
 * Copyright (C) 2016 Spotify AB
 * --
 * 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.spotify.helios.cli.command;

import static java.lang.String.format;
import static net.sourceforge.argparse4j.impl.Arguments.storeTrue;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import com.spotify.helios.cli.Target;
import com.spotify.helios.client.HeliosClient;
import com.spotify.helios.common.descriptors.Job;
import com.spotify.helios.common.descriptors.JobId;
import com.spotify.helios.common.descriptors.JobStatus;
import com.spotify.helios.common.descriptors.TaskStatus;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import net.sourceforge.argparse4j.inf.Argument;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class JobWatchCommand extends MultiTargetControlCommand {

  private final Argument prefixesArg;
  private final Argument jobsArg;
  private final Argument intervalArg;
  private final Argument exactArg;

  public JobWatchCommand(Subparser parser) {
    super(parser);
    parser.help("watch jobs");

    jobsArg = parser.addArgument("job")
        .help("Job reference");

    intervalArg = parser.addArgument("--interval")
        .type(Integer.class)
        .setDefault(1)
        .help("polling interval, default 1 second");

    prefixesArg = parser.addArgument("hosts")
        .nargs("*")
        .help("The hostname prefixes to watch the job on.");

    exactArg = parser.addArgument("--exact")
        .action(storeTrue())
        .help("Show status of job for every host in hosts");

  }

  @Override
  int run(final Namespace options, final List<TargetAndClient> clients,
          final PrintStream out, final boolean json, final BufferedReader stdin)
      throws ExecutionException, InterruptedException, IOException {
    final boolean exact = options.getBoolean(exactArg.getDest());
    final List<String> prefixes = options.getList(prefixesArg.getDest());
    final String jobIdString = options.getString(jobsArg.getDest());
    final List<ListenableFuture<Map<JobId, Job>>> jobIdFutures = Lists.newArrayList();
    for (final TargetAndClient cc : clients) {
      jobIdFutures.add(cc.getClient().jobs(jobIdString));
    }

    final Set<JobId> jobIds = Sets.newHashSet();
    for (final ListenableFuture<Map<JobId, Job>> future : jobIdFutures) {
      jobIds.addAll(future.get().keySet());
    }

    watchJobsOnHosts(out, exact, prefixes, jobIds, options.getInt(intervalArg.getDest()),
        clients);
    return 0;
  }

  public static void watchJobsOnHosts(final PrintStream out, final boolean exact,
                                      final List<String> resolvedHosts, final List<JobId> jobIds,
                                      final Integer interval, final HeliosClient client)
      throws InterruptedException, ExecutionException {
    watchJobsOnHosts(out, exact, resolvedHosts, Sets.newHashSet(jobIds), interval,
        ImmutableList.of(new TargetAndClient(client)));
  }

  static void watchJobsOnHosts(final PrintStream out, final boolean exact,
                               final List<String> prefixes, final Set<JobId> jobIds,
                               final int interval, final List<TargetAndClient> clients)
      throws InterruptedException, ExecutionException {
    out.println("Control-C to stop");
    out.println("JOB                  HOST                           STATE    THROTTLED?");
    final DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss");
    while (true) {

      final Instant now = new Instant();
      out.printf("-------------------- ------------------------------ -------- "
                 + "---------- [%s UTC]%n", now.toString(formatter));
      for (final TargetAndClient cc : clients) {
        final Optional<Target> target = cc.getTarget();
        if (clients.size() > 1) {
          final String header;
          if (target.isPresent()) {
            final List<URI> endpoints = target.get().getEndpointSupplier().get();
            header = format(" %s (%s)", target.get().getName(), endpoints);
          } else {
            header = "";
          }
          out.printf("---%s%n", header);
        }
        showReport(out, exact, prefixes, jobIds, formatter, cc.getClient());
      }
      if (out.checkError()) {
        break;
      }
      Thread.sleep(1000 * interval);
    }
  }

  private static void showReport(PrintStream out, boolean exact, final List<String> prefixes,
                                 final Set<JobId> jobIds, final DateTimeFormatter formatter,
                                 final HeliosClient client)
      throws ExecutionException, InterruptedException {
    final Map<JobId, JobStatus> statuses = client.jobStatuses(jobIds).get();

    for (final JobId jobId : jobIds) {
      final JobStatus jobStatus = statuses.get(jobId);
      if (jobStatus == null) {
        continue;
      }
      final Map<String, TaskStatus> taskStatuses = jobStatus.getTaskStatuses();
      if (exact) {
        for (final String host : prefixes) {
          final TaskStatus ts = taskStatuses.get(host);
          out.printf("%-20s %-30s %-8s %s%n",
              chop(jobId.toShortString(), 20),
              chop(host, 30),
              ts != null ? ts.getState() : "UNKNOWN",
              ts != null ? ts.getThrottled() : "UNKNOWN");
        }
      } else {
        for (final String host : taskStatuses.keySet()) {
          if (!hostMatches(prefixes, host)) {
            continue;
          }
          final TaskStatus ts = taskStatuses.get(host);
          out.printf("%-20s %-30s %-8s %s%n",
              chop(jobId.toShortString(), 20),
              chop(host, 30), ts.getState(), ts.getThrottled());
        }
      }
    }
  }

  private static boolean hostMatches(final List<String> prefixes, final String host) {
    if (prefixes.isEmpty()) {
      return true;
    }
    for (final String prefix : prefixes) {
      if (host.startsWith(prefix)) {
        return true;
      }
    }
    return false;
  }

  private static String chop(final String str, final int len) {
    if (str.length() <= len) {
      return str;
    }
    return str.substring(0, len);
  }

  private static Map<JobId, JobStatus> getStatuses(final HeliosClient client,
                                                   final Set<JobId> jobIds)
      throws ExecutionException, InterruptedException {
    return client.jobStatuses(jobIds).get();
  }
}
