#!/usr/bin/env python
# encoding: utf-8
"""
$Id: adl_common.py 2196 2014-07-15 13:43:48Z scottm $

Purpose: Common routines for ADL XDR handling and ancillary data caching.

Requires: adl_asc

Created Oct 2011 by R.K.Garcia <rayg@ssec.wisc.edu>
Copyright (c) 2011 University of Wisconsin Regents.
Licensed under GNU GPLv3.
"""

import os
import sys
import logging
import log_common
import time
import types
import fileinput
from subprocess import Popen, CalledProcessError, call, PIPE

import datetime

LOG = logging.getLogger(__name__)

PROFILING_ENABLED = os.environ.get('CSPP_PROFILE', None) is not None
STRACE_ENABLED = os.environ.get('CSPP_STRACE', None) is not None


class SingleLevelFilter(logging.Filter):
    """
     ref: http://stackoverflow.com/questions/1383254/logging-streamhandler-and-standard-streams
    """

    def __init__(self, passlevels, reject):
        """


            :rtype : object
            :param passlevels:
            :param reject:
            """
        super(SingleLevelFilter, self).__init__()
        self.passlevels = set(passlevels)
        self.reject = reject

    def filter(self, record):
        """

        :param record:
        :return:
        """
        if self.reject:
            return record.levelno not in self.passlevels
        else:
            return record.levelno in self.passlevels


def split_search_path(s):
    """break a colon-separated list of directories into a list of directories, else empty-list"""
    if not s:
        return []

    back_list = []
    for path in s.split(':'):
        back_list.append(os.path.abspath(path))

    return back_list


def _replaceAll(intputfile, searchExp, replaceExp):
    """

    :param intputfile:
    :param searchExp:
    :param replaceExp:
    """
    for line in fileinput.input(intputfile, inplace=1):
        if searchExp in line:
            line = line.replace(searchExp, replaceExp)
        sys.stdout.write(line)
    fileinput.close()


# ("RangeDateTime" DATETIMERANGE EQ "2014-01-13 11:22:39.900000" "2014-01-13 11:22:59.900000")


class AscLineParser(object):
    def time_range(self, ascLine):
        """

        :param ascLine:
        :return:
        """
        day, time = self.extract_time_range_tokens(ascLine)
        return self.time_from_tokens(day, time)

    def extract_time_range_tokens(self, ascLine):
        return ascLine.split('"')[3:4][0].split(' ')

    def time_from_tokens(self, day, time):
        dt = datetime.datetime.strptime(day + time, '%Y-%m-%d%H:%M:%S.%f')
        return dt


def _testParser():
    """


    """
    dt = AscLineParser().time_range(
            '("RangeDateTime" DATETIMERANGE EQ "2014-01-13 11:22:39.900000" "2014-01-13 11:22:59.900000")')
    print (dt)


class CsppEnvironment(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


def check_and_convert_path(key, a_path, check_write=False):
    """
    Make sure the path or paths specified exist
    Return the path or list of absolute paths that exist
    """

    try:
        basestring
    except NameError:
        basestring = str

    abs_locations = []
    if ":" in a_path:
        paths = a_path.split(":")
    elif isinstance(a_path, basestring):

        # elif isinstance(a_path, types.StringTypes):
        paths = [a_path]
    else:
        paths = a_path

    for path in paths:
        if not os.path.exists(path):
            if key:
                msg = "Environment variable %s refers to a path that does not exists.  %s=%s" % (key, key, path)
            else:
                msg = "Required path %s does not exist.  " % (path)

            raise CsppEnvironment(msg)
        else:
            LOG.debug("Found: %s at %s %s" % (key, path, os.path.abspath(path)))
            abs_locations.append(os.path.abspath(path))

        if check_write:
            if not os.access(path, os.W_OK):
                msg = "Path exists but is not writable %s=%s" % (key, path)
                raise CsppEnvironment(msg)
                sys.exit(2)

    # return a string if only one and an array if more
    if len(abs_locations) == 1:
        return abs_locations[0]
    else:
        # return abs_locations
        # return a :-joined string for use in an env variable
        return ':'.join(abs_locations)


def check_existing_env_var(varname, default_value=None):
    """
    Check for vaiable if it exists use vale otherwise use default
    """

    if varname in os.environ:
        value = os.environ.get(varname)
    else:
        if default_value is not None:
            value = default_value
        else:
            print("ERROR: %s is not set, please update environment and re-try" % varname) >> sys.stderr
            LOG.error("Environment variable missing. %s" % varname)
            sys.exit(9)

    return value


def check_and_convert_env_var(varname, check_write=False, default_value=None):
    value = check_existing_env_var(varname, default_value=default_value)
    path = check_and_convert_path(varname, value, check_write=check_write)
    return path


def what_package_am_i():
    path = os.path.dirname(os.path.abspath(__file__))
    cspp_x = path.split("/common")
    cspp_x_home = cspp_x[0]

    return cspp_x_home


def _ldd_verify(exe):
    """check that a program is ready to run"""
    rc = call(['ldd', exe], stdout=os.tmpfile(), stderr=os.tmpfile())
    return rc == 0


def check_env():
    """ Check that needed environment variables are set"""

    for key in EXTERNAL_BINARY.iterkeys():
        if not _ldd_verify(EXTERNAL_BINARY[key]):
            LOG.warning("%r executable is unlikely to run, is LD_LIBRARY_PATH set?" % EXTERNAL_BINARY[key])


def env(**kv):
    """augment environment with new values"""
    zult = dict(os.environ)
    zult.update(kv)

    return zult


def execute_binary_captured_io(work_dir, cmd, **kv):
    """
    Execute the specifed ancillary script.
    process the ouptut and return the file names.
    """

    LOG.debug('executing %r with kv=%r' % (cmd, kv))
    pop = Popen(cmd,
                cwd=work_dir,
                env=env(**kv),
                shell=True,
                stdin=PIPE,
                stdout=PIPE,
                stderr=PIPE,
                close_fds=True)

    anc_stdout, anc_stderr = pop.communicate()
    rc = pop.returncode

    if rc == 0:
        LOG.debug("success " + cmd)

        LOG.info(anc_stdout.strip())
    elif rc == 1:
        LOG.info(anc_stdout)
        LOG.info(anc_stderr)
        LOG.error("stderr:" + anc_stderr)
    else:
        LOG.warn("what " + cmd)

    if rc != 0:
        LOG.debug(anc_stdout)
        LOG.error(anc_stderr)

    return None


def simple_sh(cmd, log_execution=True, *args, **kwargs):
    """like subprocess.check_call, but returning the pid the process was given"""
    if STRACE_ENABLED:
        strace = open('strace.log', 'at')
        print ("= " * 32) >> strace
        print(repr(cmd)) >> strace
        cmd = ['strace'] + list(cmd)
        pop = Popen(cmd, *args, stderr=strace, **kwargs)
    else:
        pop = Popen(cmd, *args, **kwargs)

    pid = pop.pid
    startTime = time.time()
    rc = pop.wait()

    endTime = time.time()
    delta = endTime - startTime
    LOG.debug('statistics for "%s"' % ' '.join(cmd))
    if log_execution:
        log_common.status_line('Execution Time: %f Sec Cmd "%s"' % (delta, ' '.join(cmd)))

    if rc != 0:
        exc = CalledProcessError(rc, cmd)
        exc.pid = pid
        raise exc

    return pid


def profiled_sh(cmd, log_execution=True, *args, **kwargs):
    """
    like subprocess.check_call, but returning the pid the process was given and logging as
    INFO the final content of /proc/PID/stat

    :param cmd:
    :param log_execution:
    :param args:
    :param kwargs:
    """
    pop = Popen(cmd, *args, **kwargs)
    pid = pop.pid
    fn = '/proc/%d/status' % pid
    LOG.debug('retrieving %s statistics to caller dictionary' % fn)
    proc_stats = '-- no /proc/PID/status data --'

    rc = 0
    startTime = time.time()
    while True:
        time.sleep(1.0)

        rc = pop.poll()
        if rc is not None:
            break

        try:
            proc = file(fn, 'rt')
            proc_stats = proc.read()
            proc.close()
            del proc
        except IOError:
            LOG.warning('unable to get stats from %s' % fn)

    endTime = time.time()
    delta = endTime - startTime
    LOG.debug('statistics for "%s"' % ' '.join(cmd))

    if log_execution:
        log_common.status_line('Execution Time:  "%f" Sec Cmd "%s"' % (delta, ' '.join(cmd)))

    LOG.debug(proc_stats)

    if rc != 0:
        exc = CalledProcessError(rc, cmd)
        exc.pid = pid
        raise exc

    return pid


# default sh() is to profile on linux systems
if os.path.exists('/proc') and PROFILING_ENABLED:
    sh = profiled_sh
else:
    sh = simple_sh

def hello():
    """
    """
    print('hello')
    test_vars={'fish':'food','hair':'brush'}
    work_dir='/work'
    cmd='echo "hello"'
    execute_binary_captured_io(work_dir, cmd,**test_vars)

if __name__ == '__main__':
    log_common.configure_logging(level=logging.DEBUG)

    hello()
# logging.basicConfig(level=logging.DEBUG) we don't want basicConfig anymore
#    log_common.configure_logging(level=logging.DEBUG, FILE="testlog.log")