# standard imports
import os
import re

# local imports
from .base import (
        re_processedname,
        StoreFactory,
        )
from shep.error import StateLockedKey


class SimpleFileStore:
    """Filesystem store of contents for state, with one directory per state.

    :param path: Filesystem base path for all state directory
    :type path: str
    """
    def __init__(self, path, binary=False, lock_path=None):
        self.__path = path
        os.makedirs(self.__path, exist_ok=True)
        if binary:
            self.__m = ['rb', 'wb']
        else:
            self.__m = ['r', 'w']
        self.__lock_path = lock_path
        if self.__lock_path != None:
            os.makedirs(lock_path, exist_ok=True)


    def __lock(self, k):
        if self.__lock_path == None:
            return
        fp = os.path.join(self.__lock_path, k)
        f = None
        try:
            f = open(fp, 'x')
        except FileExistsError:
            pass
        if f == None:
            raise StateLockedKey(k)
        f.close()


    def __unlock(self, k):
        if self.__lock_path == None:
            return
        fp = os.path.join(self.__lock_path, k)
        try:
            os.unlink(fp)
        except FileNotFoundError:
            pass
        

    def put(self, k, contents=None):
        """Add a new key and optional contents 

        :param k: Content key to add 
        :type k: str
        :param contents: Optional contents to assign for content key
        :type contents: any
        """
        self.__lock(k)
        fp = os.path.join(self.__path, k)
        if contents == None:
            if self.__m[1] == 'wb':
                contents = b''
            else:
                contents = ''

        f = open(fp, self.__m[1])
        f.write(contents)
        f.close()
        self.__unlock(k)


    def remove(self, k):
        """Remove a content key from a state.

        :param k: Content key to remove from the state
        :type k: str
        :raises FileNotFoundError: Content key does not exist in the state
        """
        self.__lock(k)
        fp = os.path.join(self.__path, k)
        os.unlink(fp)
        self.__unlock(k)

    
    def get(self, k):
        """Retrieve the content for the given content key.

        :param k: Content key to retrieve content for
        :type k: str
        :raises FileNotFoundError: Content key does not exist for the state
        :rtype: any
        :return: Contents
        """
        self.__lock(k)
        fp = os.path.join(self.__path, k)
        f = open(fp, self.__m[0])
        r = f.read()
        f.close()
        self.__unlock(k)
        return r


    def list(self):
        """List all content keys persisted for the state.

        :rtype: list of str
        :return: Content keys in state
        """
        self.__lock('.list')
        files = []
        for p in os.listdir(self.__path):
            fp = os.path.join(self.__path, p)
            f = None
            try:
                f = open(fp, self.__m[0])
            except FileNotFoundError:
                continue
            r = f.read()
            f.close()
            if len(r) == 0:
                r = None
            files.append((p, r,))
        self.__unlock('.list')
        return files


    def path(self, k=None):
        """Return filesystem path for persisted state or state item.

        :param k: If given, will return filesystem path to specified content key
        :type k: str
        :rtype: str
        :return: File path
        """
        if k == None:
            return self.__path
        return os.path.join(self.__path, k)


    def replace(self, k, contents):
        """Replace persisted content for persisted content key.

        :param k: Content key to replace contents for
        :type k: str
        :param contents: Contents
        :type contents: any
        """
        self.__lock(k)
        fp = os.path.join(self.__path, k)
        os.stat(fp)
        f = open(fp, self.__m[1])
        r = f.write(contents)
        f.close()
        self.__unlock(k)


    def modified(self, k):
        self.__lock(k)
        path = self.path(k)
        st = os.stat(path)
        self.__unlock(k)
        return st.st_ctime


    def register_modify(self, k):
        pass


class SimpleFileStoreFactory(StoreFactory):
    """Provide a method to instantiate SimpleFileStore instances that provide persistence for individual states.

    :param path: Filesystem path as base path for states
    :type path: str
    """
    def __init__(self, path, binary=False, use_lock=False):
        self.__path = path
        self.__binary = binary
        self.__use_lock = use_lock


    def add(self, k):
        """Create a new SimpleFileStore for a state.

        :param k: Identifier for the state
        :type k: str
        :rtype: SimpleFileStore
        :return: A filesystem persistence instance with the given identifier as subdirectory
        """
        lock_path = None
        if self.__use_lock:
            lock_path = os.path.join(self.__path, '.lock')

        k = str(k)
        store_path = os.path.join(self.__path, k)
        return SimpleFileStore(store_path, binary=self.__binary, lock_path=lock_path)


    def ls(self):
        r = []
        for v in os.listdir(self.__path):
            if re.match(re_processedname, v):
                r.append(v)
        return r


    def close(self):
        pass
