/**
 * 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.hadoop.mapreduce.v2.app.webapp.dao;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.hadoop.mapreduce.JobACL;
import org.apache.hadoop.mapreduce.v2.api.records.JobReport;
import org.apache.hadoop.mapreduce.v2.api.records.JobState;
import org.apache.hadoop.mapreduce.v2.api.records.TaskAttemptId;
import org.apache.hadoop.mapreduce.v2.api.records.TaskId;
import org.apache.hadoop.mapreduce.v2.app.job.Job;
import org.apache.hadoop.mapreduce.v2.app.job.Task;
import org.apache.hadoop.mapreduce.v2.app.job.TaskAttempt;
import org.apache.hadoop.mapreduce.v2.util.MRApps;
import org.apache.hadoop.mapreduce.v2.util.MRApps.TaskAttemptStateUI;
import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.hadoop.util.StringUtils;
import org.apache.hadoop.yarn.util.Times;

@XmlRootElement(name = "job")
@XmlAccessorType(XmlAccessType.FIELD)
public class JobInfo {

  // ok for any user to see
  protected long startTime;
  protected long finishTime;
  protected long elapsedTime;
  protected String id;
  protected String name;
  protected String user;
  protected JobState state;
  protected int mapsTotal;
  protected int mapsCompleted;
  protected int reducesTotal;
  protected int reducesCompleted;
  protected float mapProgress;
  protected float reduceProgress;

  @XmlTransient
  protected String mapProgressPercent;
  @XmlTransient
  protected String reduceProgressPercent;

  // these should only be seen if acls allow
  protected int mapsPending;
  protected int mapsRunning;
  protected int reducesPending;
  protected int reducesRunning;
  protected boolean uberized;
  protected String diagnostics;
  protected int newReduceAttempts = 0;
  protected int runningReduceAttempts = 0;
  protected int failedReduceAttempts = 0;
  protected int killedReduceAttempts = 0;
  protected int successfulReduceAttempts = 0;
  protected int newMapAttempts = 0;
  protected int runningMapAttempts = 0;
  protected int failedMapAttempts = 0;
  protected int killedMapAttempts = 0;
  protected int successfulMapAttempts = 0;
  protected ArrayList<ConfEntryInfo> acls;

  public JobInfo() {
  }

  public JobInfo(Job job, Boolean hasAccess) {
    this.id = MRApps.toString(job.getID());
    JobReport report = job.getReport();
    this.startTime = report.getStartTime();
    this.finishTime = report.getFinishTime();
    this.elapsedTime = Times.elapsed(this.startTime, this.finishTime);
    if (this.elapsedTime == -1) {
      this.elapsedTime = 0;
    }
    this.name = job.getName().toString();
    this.user = job.getUserName();
    this.state = job.getState();
    this.mapsTotal = job.getTotalMaps();
    this.mapsCompleted = job.getCompletedMaps();
    this.mapProgress = report.getMapProgress() * 100;
    this.mapProgressPercent =
        StringUtils.formatPercent(report.getMapProgress(), 2);
    this.reducesTotal = job.getTotalReduces();
    this.reducesCompleted = job.getCompletedReduces();
    this.reduceProgress = report.getReduceProgress() * 100;
    this.reduceProgressPercent =
        StringUtils.formatPercent(report.getReduceProgress(), 2);

    this.acls = new ArrayList<ConfEntryInfo>();
    if (hasAccess) {
      this.diagnostics = "";
      countTasksAndAttempts(job);

      this.uberized = job.isUber();

      List<String> diagnostics = job.getDiagnostics();
      if (diagnostics != null && !diagnostics.isEmpty()) {
        StringBuffer b = new StringBuffer();
        for (String diag : diagnostics) {
          b.append(diag);
        }
        this.diagnostics = b.toString();
      }

      Map<JobACL, AccessControlList> allacls = job.getJobACLs();
      if (allacls != null) {
        for (Map.Entry<JobACL, AccessControlList> entry : allacls.entrySet()) {
          this.acls.add(new ConfEntryInfo(entry.getKey().getAclName(), entry
              .getValue().getAclString()));
        }
      }
    }
  }

  public int getNewReduceAttempts() {
    return this.newReduceAttempts;
  }

  public int getKilledReduceAttempts() {
    return this.killedReduceAttempts;
  }

  public int getFailedReduceAttempts() {
    return this.failedReduceAttempts;
  }

  public int getRunningReduceAttempts() {
    return this.runningReduceAttempts;
  }

  public int getSuccessfulReduceAttempts() {
    return this.successfulReduceAttempts;
  }

  public int getNewMapAttempts() {
    return this.newMapAttempts;
  }

  public int getKilledMapAttempts() {
    return this.killedMapAttempts;
  }

  public ArrayList<ConfEntryInfo> getAcls() {
    return acls;
  }

  public int getFailedMapAttempts() {
    return this.failedMapAttempts;
  }

  public int getRunningMapAttempts() {
    return this.runningMapAttempts;
  }

  public int getSuccessfulMapAttempts() {
    return this.successfulMapAttempts;
  }

  public int getReducesCompleted() {
    return this.reducesCompleted;
  }

  public int getReducesTotal() {
    return this.reducesTotal;
  }

  public int getReducesPending() {
    return this.reducesPending;
  }

  public int getReducesRunning() {
    return this.reducesRunning;
  }

  public int getMapsCompleted() {
    return this.mapsCompleted;
  }

  public int getMapsTotal() {
    return this.mapsTotal;
  }

  public int getMapsPending() {
    return this.mapsPending;
  }

  public int getMapsRunning() {
    return this.mapsRunning;
  }

  public String getState() {
    return this.state.toString();
  }

  public String getUserName() {
    return this.user;
  }

  public String getName() {
    return this.name;
  }

  public String getId() {
    return this.id;
  }

  public long getStartTime() {
    return this.startTime;
  }

  public long getElapsedTime() {
    return this.elapsedTime;
  }

  public long getFinishTime() {
    return this.finishTime;
  }

  public boolean isUberized() {
    return this.uberized;
  }

  public String getdiagnostics() {
    return this.diagnostics;
  }

  public float getMapProgress() {
    return this.mapProgress;
  }

  public String getMapProgressPercent() {
    return this.mapProgressPercent;
  }

  public float getReduceProgress() {
    return this.reduceProgress;
  }

  public String getReduceProgressPercent() {
    return this.reduceProgressPercent;
  }

  /**
   * Go through a job and update the member variables with counts for
   * information to output in the page.
   *
   * @param job
   *          the job to get counts for.
   */
  private void countTasksAndAttempts(Job job) {
    final Map<TaskId, Task> tasks = job.getTasks();
    if (tasks == null) {
      return;
    }
    for (Task task : tasks.values()) {
      switch (task.getType()) {
      case MAP:
        // Task counts
        switch (task.getState()) {
        case RUNNING:
          ++this.mapsRunning;
          break;
        case SCHEDULED:
          ++this.mapsPending;
          break;
        default:
          break;
        }
        break;
      case REDUCE:
        // Task counts
        switch (task.getState()) {
        case RUNNING:
          ++this.reducesRunning;
          break;
        case SCHEDULED:
          ++this.reducesPending;
          break;
        default:
          break;
        }
        break;
      default:
        throw new IllegalStateException(
            "Task type is neither map nor reduce: " + task.getType());
      }
      // Attempts counts
      Map<TaskAttemptId, TaskAttempt> attempts = task.getAttempts();
      int newAttempts, running, successful, failed, killed;
      for (TaskAttempt attempt : attempts.values()) {

        newAttempts = 0;
        running = 0;
        successful = 0;
        failed = 0;
        killed = 0;
        if (TaskAttemptStateUI.NEW.correspondsTo(attempt.getState())) {
          ++newAttempts;
        } else if (TaskAttemptStateUI.RUNNING.correspondsTo(attempt.getState())) {
          ++running;
        } else if (TaskAttemptStateUI.SUCCESSFUL.correspondsTo(attempt
            .getState())) {
          ++successful;
        } else if (TaskAttemptStateUI.FAILED.correspondsTo(attempt.getState())) {
          ++failed;
        } else if (TaskAttemptStateUI.KILLED.correspondsTo(attempt.getState())) {
          ++killed;
        }

        switch (task.getType()) {
        case MAP:
          this.newMapAttempts += newAttempts;
          this.runningMapAttempts += running;
          this.successfulMapAttempts += successful;
          this.failedMapAttempts += failed;
          this.killedMapAttempts += killed;
          break;
        case REDUCE:
          this.newReduceAttempts += newAttempts;
          this.runningReduceAttempts += running;
          this.successfulReduceAttempts += successful;
          this.failedReduceAttempts += failed;
          this.killedReduceAttempts += killed;
          break;
        default:
          throw new IllegalStateException("Task type neither map nor reduce: " + 
              task.getType());
        }
      }
    }
  }

}
