# Copyright 2017 TensorHub, Inc.
#
# 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.

from __future__ import absolute_import
from __future__ import division

import os
import time
import uuid

from guild import util
from guild import yaml

class Run(object):

    __properties__ = [
        "id",
        "path",
        "short_id",
        "pid",
        "status"
    ]

    def __init__(self, id, path):
        self.id = id
        self.path = path
        self._guild_dir = os.path.join(self.path, ".guild")

    @property
    def short_id(self):
        return self.id[:8]

    @property
    def pid(self):
        lockfile = self.guild_path("LOCK")
        try:
            raw = open(lockfile, "r").read()
        except (IOError, ValueError):
            return None
        else:
            try:
                return int(raw)
            except ValueError:
                return None

    @property
    def status(self):
        if os.path.exists(self.guild_path("LOCK.remote")):
            return "running"
        else:
            remote_exit_status = self.get("exit_status.remote")
            if remote_exit_status is not None:
                if remote_exit_status == 0:
                    return "completed"
                elif remote_exit_status == 2:
                    return "terminated"
                else:
                    return "error"
            else:
                pid = self.pid # side effect so run once
                if pid is None:
                    if self.get("exit_status") == 0:
                        return "completed"
                    else:
                        return "error"
                elif util.pid_exists(pid):
                    return "running"
                else:
                    return "terminated"

    def get(self, name, default=None):
        try:
            val = self[name]
        except KeyError:
            return default
        else:
            return val if val is not None else default

    def attr_names(self):
        return sorted(os.listdir(self._attrs_dir()))

    def has_attr(self, name):
        return os.path.exists(self._attr_path(name))

    def iter_attrs(self):
        for name in self.attr_names():
            try:
                yield name, self[name]
            except KeyError:
                pass

    def __getitem__(self, name):
        try:
            f = open(self._attr_path(name), "r")
        except IOError:
            raise KeyError(name)
        else:
            return yaml.safe_load(f)

    def _attr_path(self, name):
        return os.path.join(self._attrs_dir(), name)

    def _attrs_dir(self):
        return os.path.join(self._guild_dir, "attrs")

    def __repr__(self):
        return "<guild.run.Run '%s'>" % self.id

    def init_skel(self):
        util.ensure_dir(self.guild_path("attrs"))
        util.ensure_dir(self.guild_path("logs"))

    def guild_path(self, subpath):
        return os.path.join(self._guild_dir, subpath)

    def write_attr(self, name, val):
        encoded = yaml.safe_dump(
            val,
            default_flow_style=False,
            indent=2).strip()
        if encoded.endswith("\n..."):
            encoded = encoded[:-4]
        with open(self._attr_path(name), "w") as f:
            f.write(encoded)
            f.close()

    def del_attr(self, name):
        try:
            os.remove(self._attr_path(name))
        except OSError:
            pass

    def iter_files(self, all_files=False, follow_links=False):
        for root, dirs, files in os.walk(self.path, followlinks=follow_links):
            if not all_files and root == self.path:
                try:
                    dirs.remove(".guild")
                except ValueError:
                    pass
            for name in dirs:
                yield os.path.join(root, name)
            for name in files:
                yield os.path.join(root, name)

def timestamp():
    """Returns an integer use for run timestamps."""
    return int(time.time() * 1000000)

def timestamp_seconds(ts):
    """Returns seconds float from value generated by `timestamp`."""
    return float(ts / 1000000)

def mkid():
    return uuid.uuid1().hex

@@ -1535,37 +1535,24 @@
     
-remote_exit_status = self.get
+if self.has_attr
 (%22ex
@@ -1569,16 +1569,17 @@
 remote%22)
+:
 %0A       
@@ -1587,95 +1587,216 @@
     
-if remote_exit_status is not None:%0A                if remote_exit_status == 0:%0A        
+    return self._remote_exit_status()%0A            else:%0A                return self._local_status()%0A%0A    def _remote_exit_status(self):%0A        status = self.get(%22exit_status.remote%22)%0A        if status == 0:%0A
     
@@ -1834,33 +1834,13 @@
     
-     
-   
 elif 
-remote_exit_
 stat
@@ -1848,24 +1848,16 @@
 s == 2:%0A
-        
         
@@ -1888,38 +1888,22 @@
         
-   
-     
 else:%0A
-        
         
@@ -1925,34 +1925,38 @@
 or%22%0A
+%0A
     
-        else:%0A        
+def _local_status(self):%0A
     
@@ -1984,36 +1984,27 @@
 side
- 
+-
 effect
- so run once%0A        
+, read once%0A
     
@@ -2031,34 +2031,37 @@
             
-        if
+exit_status =
  self.get(%22e
@@ -2072,31 +2072,27 @@
 status%22)
- == 0:%0A
+%0A  
         
         
@@ -2075,32 +2075,55 @@
 tus%22)%0A          
+  if exit_status == 0:%0A
                 
@@ -2145,32 +2145,94 @@
 ed%22%0A            
+elif exit_status %3C 0:%0A                return %22terminated%22%0A    
         else:%0A  
@@ -2225,32 +2225,24 @@
       else:%0A
-        
             
@@ -2264,32 +2264,24 @@
 or%22%0A        
-      
-  
 elif util.pi
@@ -2303,32 +2303,24 @@
             
-        
 return %22runn
@@ -2332,38 +2332,22 @@
         
-   
-     
 else:%0A
-        
         

Treat negative exit codes as 'terminated' status