import sys, os, datetime, json
import collections, itertools
from string import Template
import requests
from parse_uri import ParseUri
from boto.s3.connection import S3Connection
    
class Artifact:
  def uri(self):
    raise Exception("not implemented")

  def exists(self):
    raise Exception("not implemented")

  def delete(self):
    raise Exception("not implemented")

class S3Artifact(Artifact):
  def __init__(self, bucket, key):
    aws_credentials = json.load(open('aws_credentials.json'))
    self.access_id = aws_credentials['access-id']
    self.private_key = aws_credentials['private-key']
    self.bucket = bucket
    self.key = key

  def uri(self):
    return "s3://%s%s" % (self.bucket, self.key)

  def exists(self):
    from boto.s3.connection import S3Connection
    conn = S3Connection(self.access_id, self.private_key)
    b = conn.get_bucket(self.bucket)
    if b.get_key(self.key):
      return True
    else:
      return False

  def delete(self):
    from boto.s3.connection import S3Connection
    conn = S3Connection(self.access_id, self.private_key)
    b = conn.get_bucket(self.bucket)
    b.delete_key(self.key)

class HTTPArtifact(Artifact):
  def __init__(self, url):
    self.url = url

  def uri(self):
    return self.url

  def exists(self):
    r = requests.head(self.url)
    if r.status_code == 404:
      return False
    elif r.status_code == 200 or r.status_code == 302:
      return True
    else:
      raise Exception("Unexpected status code: %s" % r.status_code)

  def delete(self):
    r = requests.delete(self.url)

class SSHArtifact(Artifact):
  def __init__(self, host, path):
    self.host = host
    self.path = path

  def uri(self):
    return "ssh://%s/%s" % (self.host, self.path)
 
  def exists(self):
    # TODO: This should throw exceptions on any errors and only return False when we genuinely know the file is not there
    command = 'ssh %s "[ -f %s ]"' % (self.host, self.path)
    if not os.system(command):
      # file exists
      return True
    else:
      return False

  def delete(self):
    command = 'ssh %s "rm %s"' % (self.host, self.path)
    os.system(command)

class FileArtifact(Artifact):
  def __init__(self, path):
    self.path = path

  def uri(self):
    return "file://%s" % (self.path)

  def exists(self):
    if not self.path:
      raise Exception("invalid path " + self.path)
    command = '[ -f %s ]' % self.path
    print "exists command:", command
    if not os.system(command):
      return True
    else:
      return False

  def delete(self):
    command = 'rm %s' % self.path
    os.system(command)

def resolve_artifact(uri):
  uri_parser = ParseUri()
  parsed_uri = uri_parser.parse(uri)  
  if parsed_uri.protocol == "ssh":
    return SSHArtifact(parsed_uri.host, parsed_uri.path)
  elif parsed_uri.protocol == "file":
    return FileArtifact(parsed_uri.path)
  elif parsed_uri.protocol == "http":
    return HTTPArtifact(parsed_uri.source)
  elif parsed_uri.protocol == "s3":
    return S3Artifact(parsed_uri.host, parsed_uri.path)
  else:
    return Artifact()

def resolve_date(year,month,day,day_delta=0):
  dt = datetime.datetime(year,month,day) - datetime.timedelta(days=day_delta)
  return dt - delta

class JobFactory:
  def __init__(self, jobs_dir):
    self.jobs_dir = jobs_dir

  def get_job(self, group, job_id, parameters={}):
    jobs_filename = os.path.join(self.jobs_dir, group + ".job")
    jobs_file = open(jobs_filename)
    conf = json.load(jobs_file)
    for jobconf in conf:
      if jobconf['id'] == job_id:
        return Job(group, jobconf, parameters=parameters)
    raise Exception("Job id not found %s" % job_id)

job_factory = JobFactory("jobs")

class Job:
  def __init__(self, group, jobconf, parameters={}):
    self.group = group
    self.jobid = jobconf['id']
    self.parameters = parameters.get('parameters',{})
    self.parameters.update(parameters) # inherited parameters override
    self.command = Template(jobconf["command"]).substitute(self.parameters)
    self.artifact = resolve_artifact(Template(jobconf["artifact"]).substitute(self.parameters))

    self.dependencies = []
    for dependency_conf in jobconf.get("dependencies", []):
      dependency_group = dependency_conf.get("group", self.group)
      for params in self.resolve_dependency_parameters(dependency_conf.get('parameters',{})):
        job = job_factory.get_job(dependency_group, dependency_conf['id'], params)
        self.dependencies.append(job)

  def resolve_dependency_parameters(self, dependency_parameters):
    print self.jobid, "resolving parameters"
    templated_params = {}
    for key, values in dependency_parameters.items():
      for value in values:
        try:
          value = Template(value).substitute(self.parameters)
        except KeyError, e:
          print "Could not resolve template parameter", e
          raise
        templated_params[key] = templated_params.get(key, []) + [value]

    for point in itertools.product(*templated_params.values()):
      params = dict(zip(templated_params.iterkeys(), point))
      params.update(self.parameters)
      yield params

  def run(self):
    print self.jobid, "command:", self.command
    ret = os.system(self.command)
    if ret:
      print "Error",ret
      sys.exit(ret)

  def build(self):
    if self.artifact.exists():
      print self.jobid, "artifact present, nothing to do", self.artifact.uri()
    else:
      print self.jobid, "artifact not present", self.artifact.uri()
      print self.jobid, "checking dependencies"
      for dependency in self.dependencies:
        dependency.build()
    
      print self.jobid, "Starting with parameters", self.parameters
      self.run()
      print self.jobid, "finished"
  

if __name__ == "__main__":
  job_group = sys.argv[1]
  job_id = sys.argv[2] if len(sys.argv) >= 3 else None
  params = {}
  for arg in sys.argv[3:]:
    params.update([arg.split("=")])
  job = job_factory.get_job(job_group, job_id, params)
  job.build()




@@ -3829,37 +3829,16 @@
 rameters
-.get('parameters',%7B%7D)
 %0A    sel
@@ -3857,16 +3857,29 @@
 .update(
+jobconf.get('
 paramete
@@ -3880,16 +3880,21 @@
 rameters
+',%7B%7D)
 ) # inhe
@@ -3903,27 +3903,43 @@
 ted 
-p
 ar
-ameters
+e
  over
+w
 ri
-de
+tten by local parameters
 %0A   

make specified params override inherited params