/**
 * 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.hive.hcatalog.templeton.tool;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.common.JavaUtils;
import org.apache.hive.hcatalog.templeton.JsonBuilder;

/**
 * The persistent state of a job.  The state is stored in one of the
 * supported storage systems.
 */
public class JobState {

  private static final Logger LOG = LoggerFactory.getLogger(JobState.class);

  private String id;

  // Storage is instantiated in the constructor
  private TempletonStorage storage = null;

  private static TempletonStorage.Type type = TempletonStorage.Type.JOB;

  private Configuration config = null;

  public JobState(String id, Configuration conf)
    throws IOException {
    this.id = id;
    config = conf;
    storage = getStorage(conf);
  }

  public void delete()
    throws IOException {
    try {
      storage.delete(type, id);
    } catch (Exception e) {
      // Error getting children of node -- probably node has been deleted
      LOG.info("Couldn't delete " + id);
    }
  }

  /**
   * Get an instance of the selected storage class.  Defaults to
   * HDFS storage if none is specified.
   */
  public static TempletonStorage getStorageInstance(Configuration conf) {
    TempletonStorage storage = null;
    try {
      storage = (TempletonStorage)
          JavaUtils.loadClass(conf.get(TempletonStorage.STORAGE_CLASS))
          .newInstance();
    } catch (Exception e) {
      LOG.warn("No storage method found: " + e.getMessage());
      try {
        storage = new HDFSStorage();
      } catch (Exception ex) {
        LOG.error("Couldn't create storage.");
      }
    }
    return storage;
  }

  /**
   * Get an open instance of the selected storage class.  Defaults
   * to HDFS storage if none is specified.
   */
  public static TempletonStorage getStorage(Configuration conf) throws IOException {
    TempletonStorage storage = getStorageInstance(conf);
    storage.openStorage(conf);
    return storage;
  }

  /**
   * For storage methods that require a connection, this is a hint
   * that it's time to close the connection.
   */
  public void close() throws IOException {
    storage.closeStorage();
  }

  //
  // Properties
  //

  /**
   * This job id.
   */
  public String getId() {
    return id;
  }

  /**
   * The percent complete of a job
   */
  public String getPercentComplete()
    throws IOException {
    return getField("percentComplete");
  }

  public void setPercentComplete(String percent)
    throws IOException {
    setField("percentComplete", percent);
  }

  /**
   * Add a jobid to the list of children of this job.
   *
   * @param jobid
   * @throws IOException
   */
  public void addChild(String jobid) throws IOException {
    String jobids = "";
    try {
      jobids = getField("children");
    } catch (Exception e) {
      // There are none or they're not readable.
    }
    if (jobids==null) {
      jobids = "";
    }
    if (!jobids.equals("")) {
      jobids += ",";
    }
    jobids += jobid;
    setField("children", jobids);
  }

  /**
   * Set parent job of this job
   * @param id
   */
  public void setParent(String id) throws IOException {
    setField("parent", id);
  }

  public String getParent() throws IOException {
    return getField("parent");
  }

  /**
   * Get a list of jobstates for jobs that are children of this job.
   * @throws IOException
   */
  public List<JobState> getChildren() throws IOException {
    ArrayList<JobState> children = new ArrayList<JobState>();
    String childJobIDs = getField("children");
    if (childJobIDs != null) {
      for (String jobid : childJobIDs.split(",")) {
        children.add(new JobState(jobid, config));
      }
    }
    return children;
  }

  /**
   * The system exit value of the job.
   */
  public Long getExitValue()
    throws IOException {
    return getLongField("exitValue");
  }

  public void setExitValue(long exitValue)
    throws IOException {
    setLongField("exitValue", exitValue);
  }

  /**
   * When this job was created.
   */
  public Long getCreated()
    throws IOException {
    return getLongField("created");
  }

  public void setCreated(long created)
    throws IOException {
    setLongField("created", created);
  }

  /**
   * The user who started this job.
   */
  public String getUser()
    throws IOException {
    return getField("user");
  }

  public void setUser(String user)
    throws IOException {
    setField("user", user);
  }

  @SuppressWarnings("unchecked")
  public Map<String, Object> getUserArgs()
    throws IOException
  {
    String jsonString = getField("userArgs");
    return (Map<String, Object>)JsonBuilder.jsonToMap(jsonString);
  }
  public void setUserArgs(Map<String, Object> userArgs)
    throws IOException
  {
    String jsonString = JsonBuilder.mapToJson(userArgs);
    setField("userArgs", jsonString);
  }

  /**
   * The url callback
   */
  public String getCallback()
    throws IOException {
    return getField("callback");
  }

  public void setCallback(String callback)
    throws IOException {
    setField("callback", callback);
  }

  /**
   * The status of a job once it is completed.
   */
  public String getCompleteStatus()
    throws IOException {
    return getField("completed");
  }

  public void setCompleteStatus(String complete)
    throws IOException {
    setField("completed", complete);
  }

  /**
   * The time when the callback was sent.
   */
  public Long getNotifiedTime()
    throws IOException {
    return getLongField("notified");
  }

  public void setNotifiedTime(long notified)
    throws IOException {
    setLongField("notified", notified);
  }

  //
  // Helpers
  //

  /**
   * Fetch an integer field from the store.
   */
  public Long getLongField(String name)
    throws IOException {
    String s = storage.getField(type, id, name);
    if (s == null)
      return null;
    else {
      try {
        return new Long(s);
      } catch (NumberFormatException e) {
        LOG.error("templeton: bug " + name + " " + s + " : " + e);
        return null;
      }
    }
  }

  /**
   * Store a String field from the store.
   */
  public void setField(String name, String val)
    throws IOException {
    try {
      storage.saveField(type, id, name, val);
    } catch (NotFoundException ne) {
      throw new IOException(ne.getMessage());
    }
  }

  public String getField(String name)
    throws IOException {
    return storage.getField(type, id, name);
  }

  /**
   * Store a long field.
   *
   * @param name
   * @param val
   * @throws IOException
   */
  public void setLongField(String name, long val)
    throws IOException {
    try {
      storage.saveField(type, id, name, String.valueOf(val));
    } catch (NotFoundException ne) {
      throw new IOException("Job " + id + " was not found: " +
        ne.getMessage());
    }
  }

  /**
   * Get an id for each currently existing job, which can be used to create
   * a JobState object.
   *
   * @param conf
   * @throws IOException
   */
  public static List<String> getJobs(Configuration conf) throws IOException {
    try {
      return getStorage(conf).getAllForType(type);
    } catch (Exception e) {
      throw new IOException("Can't get jobs", e);
    }
  }
}
