"""
utilities and wrappers for working with HDF archives.

* an archive contains one of more datasets

* a dataset contains

  - an array X: the data
  - an optional array Y: the raw response
  - an optional array T: labels (targets) associated with the response

a dataset specification (dset_path) is the concatenation
(`:` is used as a separator)
of the path to the HDF file and the dataset name. All datasets hang on the root.
"""


import logging, datetime, os

import pandas as pd

import filelock
from . import deprecated

log = logging.getLogger(__name__)
log.setLevel(logging.INFO)

import re
__HDF_SUFFIX__ = "h5"
__SPEC_SEP__ = ":"

DSPEC_MSG = ("dataset specification; of the form "
             "<hdf_archive_path>%s<dataset_name>" % __SPEC_SEP__)


def __parse(path, PATT = re.compile("(.+[.]%s)[%s](.+)" % (__HDF_SUFFIX__, __SPEC_SEP__))):
    try:
        return PATT.match( path ).groups()
    except AttributeError:
        raise AttributeError("cannot parse (pattern %s) path (%s)" % (PATT.pattern, path))

def basename(path):
    """a dataset has the form <archname>:<basename>

    @return: the archive (inside the file) part of the path
    """

    return __parse(path)[1]

def archname(path):
    """a dataset has the form <archname>:<basename>

    @return: the archname part of the path
    """
    return __parse(path)[0]

def split(path):
    """a dataset has the form <archname>:<basename>

    @return: (archname, basename)
    """
    return __parse(path)
    

def join(path, dsname):
    "join archname with basename"

    return __SPEC_SEP__.join( (path, dsname) )

@deprecated
def this_train_name(cfg_file):
    """train subdir inside archive"""

    return "train_%s_%s" % (os.path.splitext(os.path.basename(cfg_file))[0],
            datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))


def save_object( file_path, key, obj ):
    "atomic save operation"
    log.debug("writing to %s:%s", file_path, key)
    with filelock.FileLock(file_path) as lock:
        store = pd.HDFStore(  file_path )
        store[key] = obj
        store.close()

        ## check that it is there
        store = pd.HDFStore(  file_path )
        if key in store:
            log.debug("object in %s:%s", file_path, key)
        else:
            log.error("POSSIBLE DATA LOSS: could save object in %s:%s", file_path, key)
        store.close()

def load_object( file_path, key ):
    "atomic load operation"
    log.debug("reading from %s:%s", file_path, key)
    with filelock.FileLock(file_path) as lck:
        store = pd.HDFStore(  file_path )
        obj = store[key]
        store.close()
    return obj


def dset_path(path):
    "a checker for correct specification of an archive path"

    return join( *__parse(path) )


@deprecated
def get_target_dataset(path, low_idx, high_idx):
    "load (X,Y) as numpy arrays from the archive"

    log.info("loading data (and targets) %s ...", path)

    store = pd.HDFStore( archname(path) )
    x = store[basename(path)+"/X"].values
    y = store[basename(path)+"/T"]['label_code'].values
    if high_idx > low_idx:
        x = x[low_idx:high_idx]
        y = y[low_idx:high_idx]
    store.close()
    return (x, y)

@deprecated
def get_dataset(path, low_idx, high_idx):
    "load X as a numpy array from the archive"

    log.info("loading data %s ...",  path)

    store = pd.HDFStore( archname(path) )
    x = store[basename(path)+"/X"].values
    if high_idx > low_idx:
        x = x[low_idx:high_idx]
    store.close()
    return x

@deprecated
def get_batchX(store, exp_name, _idx, batch_size, which):
    "load a batch from the archive"
    
    low = batch_size * _idx
    high = low + batch_size

    return store[exp_name+"/"+which].values[low : high]


