#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    timehash.py - A library by Abe Usher to help compute variable precision time intervals,
    for use in Big Data analysis, spatial-temporal computation, and other quantitative data analysis.
    Copyright (C) 2010  Abe Usher abe.usher@gmail.com
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright
          notice, this list of conditions and the following disclaimer in the
          documentation and/or other materials provided with the distribution.
        * Neither the name of Abe Usher nor the
          names of its contributors may be used to endorse or promote products
          derived from this software without specific prior written permission.
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL ABE USHER BE LIABLE FOR ANY
    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import time
import datetime
import calendar

__base32 = '01abcdef'
__before = 'f01abcde'
__after = '1abcdef0'
__decodemap = { }
__neighbormap = { }

for i in range(len(__base32)):
    __decodemap[__base32[i]] = i
    __neighbormap[__base32[i]] = (__before[i], __after[i])
del i
print('__decodemap', len(__decodemap.keys()))
print('__neighbormap', __neighbormap)

"""
This module creates a fuzzy precision representation of a time interval.
It makes calculations based on a 64 year representation of time from January 1, 1970 to January 1, 2098.
Values are encoded with a number of bits(represented by an ASCII character) that indicate the amount of time to add to 1970.
Times prior to 1970 or after 2098 are not accounted for by this scale.
Each character added to the timehash reduces the time interval ambiguity by a factor of 8.
Valid characters for encoding the floating point time into ASCII characters include {01abcdef}
0 +/- 64 years
1 +/- 8 years
2 +/- 1 years
3 +/- 45.65625 days
4 +/- 5.707 days
5 +/- 0.71337 days = 17.121 hours
6 +/- 2.14013671875 hours
7 +/- 0.26751708984375 hours = 16.05 minutes
8 +/- 2.006378173828125 minutes
9 +/- 0.2507 minutes = 15 seconds
10 +/- 1.88097 seconds"""


def decode_exactly(timehash):
    """
    Decode the timehash to its exact values, including the error
    margins of the result.  Returns two float values: timehash
    and the plus/minus error for epoch seconds.
    """
    time_interval = (0.0, 4039372800.0)#from January 1, 1970 to January 1, 2098
    time_error = (time_interval[0] + time_interval[1])/2  #this constant is used to calculate the potential time error defined by a particular number of characters
    for c in timehash:
        cd = __decodemap[c]
        for mask in [4, 2, 1]:
            time_error /=2
            mid = (time_interval[0] + time_interval[1])/2
            if cd & mask:
                time_interval = (mid, time_interval[1])
            else:
                time_interval = (time_interval[0], mid)
    time_value = (time_interval[0] + time_interval[1])/2
    return (time_value, time_error)

def decode(timehash):
    """
    Decode timehash, returning a single floating point value for epoch seconds.
    """
    epoch_seconds, time_error = decode_exactly(timehash)
    #drop the time_error for now
    return epoch_seconds

def encode(timeseconds, precision=10):
    """
    Encode a timestamp given as a floating point epoch time to
    a timehash which will have the character count precision.
    Fixed a nasty logic error in the timehash encode function, credit to LC3.
    """
    time_interval = (0.0, 4039372800.0)#from January 1, 1970 to January 1, 2098
    timehash = []
    bits = [4, 2, 1]
    bit = 0
    ch = 0
    while len(timehash) < precision:
        mid = (time_interval[0] + time_interval[1])/2
        if timeseconds > mid:
            ch |= bits[bit]
            time_interval = (mid, time_interval[1])
        else:
            time_interval = (time_interval[0], mid)
        if bit < 2:
            bit += 1
        else:
            timehash += __base32[ch]
            bit = 0
            ch = 0
    return ''.join(timehash)

def before(hashcode):
    """
    Extract the hashcode for the preceding time-window.
    """
    i = 1
    for c in reversed(hashcode):
        padding = (i - 1) * 'f'
        pos = len(hashcode) - i
        if c != '0':
            ret = hashcode[:pos] + __neighbormap[c][0] + padding
            return ret
        else:
            i += 1

def after(hashcode):
    """
    Extract the hashcode for the succeeding time-window.
    """
    i = 1
    for c in reversed(hashcode):
        padding = (i - 1) * '0'
        pos = len(hashcode) - i
        if c != 'f':
            ret = hashcode[:pos] + __neighbormap[c][1] + padding
            return ret
        else:
            i += 1

def neighbors(hashcode):
    """
    Extract the hashcodes for the preceding and succeeding time-windows,
    excluding the hashcode for the current time-window.
    """
    return [before(hashcode), after(hashcode)]

def expand(hashcode):
    """
    Extract the hashcodes for the preceding and succeeding time-windows,
    including the hashcode for the current time-window.
    """
    return [before(hashcode), hashcode, after(hashcode)]

def encode_from_datetime(datetime_object, precision=10):
    """
    Converts a Python datetime object into a timehash value.
    For alternate ways to do datetime conversions, see also: http://stackoverflow.com/questions/6999726/how-can-i-convert-a-datetime-object-to-milliseconds-since-epoch-unix-time-in-p
    """
    milliseconds = calendar.timegm(datetime_object.timetuple())
    return encode(milliseconds, precision)



if __name__ == "__main__":
    """
    Main function - entry point into script.  Used for examples and testing.
    """
    print(encode(0,1),encode(0,10),encode(0,8))
    print(encode(4039372800.0,1),encode(4039372800.0,10),encode(4039372800.0,8))
    print(decode('0000000000'), decode('ffffffffff'))
    print(decode_exactly('0000000000'),decode_exactly('ffffffffff'))
    print(before('fffffffff'),before('fffffffffe'))
    print(after('0000000000'),after('0000000001'))
    print(neighbors('000000000a'))
    print(expand('000000000a'))
    print(datetime.datetime.utcfromtimestamp(0), 
        datetime.datetime.utcfromtimestamp(4039372800.0/2),
        datetime.datetime.utcfromtimestamp(4039372800.0)
    )
    half_date = datetime.datetime.utcfromtimestamp(4039372800.0/2)
    quarter_date = datetime.datetime.utcfromtimestamp(4039372800.0/4)
    print('half_date', half_date, encode_from_datetime(half_date), decode(encode_from_datetime(half_date)))
    print('half_neighbors', neighbors('bfffffffff'))
    print('quarter_date', quarter_date, encode_from_datetime(quarter_date))


    print('afee0dccff', decode('afee0dccff'))
    print('afaadbcd01', decode('afaadbcd01'))
    # Examples of encoding
    rightnow = time.time()
    rightnow_hash = encode(rightnow, precision=10)

    rightnow60 = rightnow + 60
    rightnow60_hash = encode(rightnow60)

    rightnow3600 = rightnow + 3600
    rightnow3600_hash = encode(rightnow3600)

    previous3600 = rightnow - 3600
    previous3600_hash = encode(previous3600)

    previous60 = rightnow - 60
    previous60_hash = encode(previous60)

    previous86400 = rightnow - 86400
    previous86400_hash = encode(previous86400)

    year_future_from_now = rightnow + (86400*365.25)
    year_future_from_now_hash = encode(year_future_from_now)

    print("one day ago\t\t{}".format(previous86400_hash))
    print("one hour ago\t\t{}".format(previous3600_hash))
    print("60 seconds ago\t\t{}".format(previous60_hash))
    print("now\t\t\t{} ***".format(rightnow_hash))
    print("60 seconds future\t{}".format(rightnow60_hash))
    print("one hour in the future\t{}".format(rightnow3600_hash))
    print("one year from today\t{}".format(year_future_from_now_hash))
    print("\n")

    # Example of decoding and error check
    rightnow_calculated,time_error = decode_exactly(rightnow_hash)
    print("original rightnow = %.2f (%s)" % (rightnow,rightnow_hash))
    print("calculated rightnow = %.2f" % (rightnow_calculated))
    print("time error = +/- %.2f seconds." % (time_error))    #

    print("\n")
    print("\n")
    today = 'add0c'
    rightnow_calculated,time_error = decode_exactly(today)
    dt = datetime.datetime.fromtimestamp(rightnow_calculated)
    print(dt)
    print("original rightnow = %.2f (%s)" % (rightnow,rightnow_hash))
    print("calculated rightnow = %.2f" % (rightnow_calculated))
    print("time error = +/- %.2f seconds." % (time_error))
    print("\n")
    print("\n")

    #example of using timehash with a native datetime object
    dt_rightnow = datetime.datetime.now()
    sample_dt_hash = encode_from_datetime(dt_rightnow)
    print(sample_dt_hash)
