"""
Common functions

callable functions from this file:

    check_file_exists
    data_read
    dew_point
    time_axis_adjustment
    timestamp_to_utc

data structures:

    ANSIColours
    I2C_LOCK_FILE
    UNIT
"""

import argparse
import bz2
import datetime
import math
import os
import pickle
import sys
import types

import numpy as np


##############################################################################
# data structures
##############################################################################


I2C_LOCK_FILE = os.path.expanduser('~/.mmcb_i2c_lock')


UNIT = types.SimpleNamespace(
    calculated_dew_point='DP\u00b0C',
    flow_rate='lps',
    pressure='hPa',
    relative_humidity='RH%',
    strain='arbitrary',
    temperature='\u00b0C',
    vacuum='kPa',
)


class ANSIColours:
    """
    ANSI 3/4 bit terminal escape sequences

    https://en.wikipedia.org/wiki/ANSI_escape_code
    """
    # foreground colours
    FG_BRIGHT_BLACK = '\033[90m'
    FG_BRIGHT_RED = '\033[91m'
    FG_BRIGHT_GREEN = '\033[92m'
    FG_BRIGHT_YELLOW = '\033[93m'
    FG_BRIGHT_BLUE = '\033[94m'
    FG_BRIGHT_MAGENTA = '\033[95m'
    FG_BRIGHT_CYAN = '\033[96m'
    FG_BRIGHT_WHITE = '\033[97m'

    FG_BLACK = '\033[30m'
    FG_RED = '\033[31m'
    FG_GREEN = '\033[32m'
    FG_YELLOW = '\033[33m'
    FG_BLUE = '\033[34m'
    FG_CYAN = '\033[35m'
    FG_MAGENTA = '\033[36m'
    FG_WHITE = '\033[37m'

    # background colours
    BG_BRIGHT_BLACK = '\033[100m'
    BG_BRIGHT_RED = '\033[101m'
    BG_BRIGHT_GREEN = '\033[102m'
    BG_BRIGHT_YELLOW = '\033[103m'
    BG_BRIGHT_BLUE = '\033[104m'
    BG_BRIGHT_MAGENTA = '\033[105m'
    BG_BRIGHT_CYAN = '\033[106m'
    BG_BRIGHT_WHITE = '\033[107m'

    BG_BLACK = '\033[40m'
    BG_RED = '\033[41m'
    BG_GREEN = '\033[42m'
    BG_YELLOW = '\033[43m'
    BG_BLUE = '\033[44m'
    BG_MAGENTA = '\033[45m'
    BG_CYAN = '\033[46m'
    BG_WHITE = '\033[47m'

    # attributes
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    BLINK = '\033[5m'
    UNDERLINE = '\033[4m'


##############################################################################
# file i/o
##############################################################################

def _file_is_compressed(filename):
    """
    Determine if a data file is compressed from its extension.

    --------------------------------------------------------------------------
    args
        filename : string
    --------------------------------------------------------------------------
    returns
        True if the filename extension indicates a compressed file, False
        otherwise
    --------------------------------------------------------------------------
    """
    return 'bz2' in os.path.splitext(filename)[-1].lower()


def data_read(filename):
    """
    Retrieve previously stored data from file.

    --------------------------------------------------------------------------
    args
        filename : string
    --------------------------------------------------------------------------
    returns
        restored_progress : unpickled data or None
    --------------------------------------------------------------------------
    """
    restored_progress = None

    if os.path.isfile(filename):
        file_read = bz2.open if _file_is_compressed(filename) else open

        with file_read(filename, 'rb') as infile:
            try:
                restored_progress = pickle.load(infile)
            except (pickle.UnpicklingError, AttributeError, EOFError,
                    ImportError, IndexError, TypeError):
                sys.exit(f'exiting: problem reading data from {filename}')

    return restored_progress


##############################################################################
# utilities
##############################################################################


def dew_point(tdc, rhp):
    """
    Calculate the dew point given temperature and humidity.

    https://en.wikipedia.org/wiki/Dew_point

    Murray, F. W., 1967, On the Computation of Saturation Vapor Pressure

    Magnus, Tetens : pressure in mbar
    t = 20.1 (deg C)
    u = 7.5
    v = 237.3
    w = 0.7858 (vapor pressure in mbar)

    In [25]: math.pow(10, (t * u)/(t + v) + w)
    Out[25]: 23.521463268069194

    For low temperature and low pressure with accuracy, use Goff-Gratch.

    Note that for the code below, we could reduce the computation time by 10%
    and still have values agree to within 6e-15:

    ymtrh = math.log(rhp / 100) + (b - tdc / d) * (tdc / (c + tdc))

    This function needs to use numpy instead of standard library math, since
    it's used for creating a new column in a Pandas DataFrame in dat2plot.

    --------------------------------------------------------------------------
    args
        tdc : float
            temperature degrees Celsius
        rhp : float
            relative humidity percentage
    --------------------------------------------------------------------------
    returns : float
        dew point degrees Celsius
    --------------------------------------------------------------------------
    """
    # constants
    # a = 6.1121 # mbar
    b = 18.678
    c = 257.14  # °C
    d = 234.5   # °C

    ymtrh = np.log((rhp / 100) * np.exp((b - tdc / d) * (tdc / (c + tdc))))
    tdp = (c * ymtrh) / (b - ymtrh)

    return tdp


def timestamp_to_utc(tref):
    """
    Converts a timestamp into a string in UTC to the nearest second.

    e.g. 1567065212.1064236 converts to '20190829_075332'

    --------------------------------------------------------------------------
    args
        tref : float
            time in seconds since the epoch
    --------------------------------------------------------------------------
    returns : string
    --------------------------------------------------------------------------
    """
    utc = datetime.datetime.utcfromtimestamp(tref).isoformat().split('.')[0]
    return utc.replace('-', '').replace(':', '').replace('T', '_')


def time_axis_adjustment(data):
    """
    Generate the parameters necessary to transform absolute UNIX-style epoch
    timestamps to relative timestamps with human-readable units.

    --------------------------------------------------------------------------
    args
        data : pandas.DataFrame
    --------------------------------------------------------------------------
    returns
        units : string
        data : pandas.DataFrame
            no explicit return, mutable type amended in place
    --------------------------------------------------------------------------
    """
    earliest_timestamp = data['timestamp'].min()
    latest_timestamp = data['timestamp'].max()

    minutes_of_available_data = (latest_timestamp - earliest_timestamp) / 60

    if minutes_of_available_data > 180:
        units = 'hours'
        scale = 3600
    else:
        units = 'minutes'
        scale = 60

    data['timestamp'] = (data['timestamp'] - earliest_timestamp) / scale

    return units


##############################################################################
# command line argument processing
##############################################################################


def check_file_exists(filename):
    """
    Check if file exists.

    --------------------------------------------------------------------------
    args
        filename : string
    --------------------------------------------------------------------------
    returns
        filename : string
    --------------------------------------------------------------------------
    """
    if not os.path.isfile(filename):
        raise argparse.ArgumentTypeError(f'{filename}: file does not exist')

    return filename
