# Miscellaneous utilities

# Copyright (c) 2009-2021 Andreas Gustafsson.  All rights reserved.
# Please refer to the file COPYRIGHT for detailed copyright information.

import errno
import os
import sys
import shutil

from datetime import datetime, tzinfo, timedelta
from calendar import timegm
from itertools import cycle, islice, tee

ZERO = timedelta(0)

class UTC(tzinfo):
    def utcoffset(self, dt):
        return ZERO

    def tzname(self, dt):
        return "Z"

    def dst(self, dt):
        return ZERO

def totimestamp(td):
    #return int(td.strftime("%s"))
    return timegm(td.utctimetuple())

utc = UTC()

# Convert a timestamp into an RCS date string like "2011.01.04.08.21.18".
# This is the format used internally in the CVS repository.

def ts2rcs(ts):
    return datetime.fromtimestamp(ts, UTC()).strftime("%Y.%m.%d.%H.%M.%S")

# Convert a timestamp into a date string that CVS will accept in command
# line options.  Ironically enough, recent versions of CVS no longer accept
# its own internal date format.

def ts2cvs(ts):
    return datetime.fromtimestamp(ts, UTC()).strftime("%Y-%m-%d %H:%M:%S Z")

# Convert an RCS date string into a timestamp

def rcs2ts(rcsdate):
    (y,m,d,H,M,S) = [int(s) for s in rcsdate.split('.')]
    if y < 1900:
        y = y + 1900
    return totimestamp(datetime(y,m,d,H,M,S,0,utc))

# Convert a timestamp into a Python datetime object

def ts2py(ts):
    return datetime.utcfromtimestamp(ts)

def py2ts(t):
    return int(t.strftime("%s"))

def write_file(fn, data, mode = 'b'):
    f = open(fn, "w" + mode)
    f.write(data)
    f.close()

def append_to_file(fn, data, mode = 'b'):
    f = open(fn, "a" + mode)
    f.write(data)
    f.close()

def read_file(fn, mode = 'b'):
    f = open(fn, 'r' + mode)
    contents = f.read()
    f.close()
    return contents

def mkdir_p(path):
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST:
            pass
        else:
            raise

def rm_f(path):
    try:
        os.unlink(path)
    except:
        pass

# Iterator tools

# Iterate over the pairs of adjacent elements of a sequence.
# From https://docs.python.org/3/library/itertools.html
# which calls it "pairwise".

def adjacent_pairs(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

# Take elements from a set of iterables in a round-robin fashion.
# Code taken from the Python itertools documentation

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    if sys.version_info[0] < 3:
        nexts = cycle(iter(it).next for it in iterables)
    else:
        nexts = cycle(iter(it).__next__ for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))

# Iterate over an integer range "middle first": first return the
# middle element, leaving two ranges of remaining elements.  Then
# return the middle elements of each of those ranges, etc.

def mid_first(min, max):
    if min != max:
        mid = (min + max) // 2
        yield mid
        for next in roundrobin(mid_first(min,mid), mid_first(mid+1,max)):
            yield next

# Test for the above
# assert(sorted(mid_first(range(100))) == range(100))

def none2empty(x):
    if x is None:
        return ""
    else:
        return x

# Like shutil.rmtree, but works even if the tree contains directories
# with no write permission.

def rmtree_harder(path):
    def onerror(function, path, excinfo):
        os.chmod(path, 0o777)
        rmtree_harder(path)
    shutil.rmtree(path, onerror = onerror)
