from contextlib import contextmanager
import logging
import os
import json
import shutil
import base64
import lockfile
import cacheregistry
import argparse
import sys
import glob
import re
import errno


class FileCache(object):

    def __init__(self, cacheDir):
        self._cacheDir = cacheDir
        self._ensure_dir(self._cacheDir)

    def _ensure_dir(self, d):
        try:
            if not os.path.exists(d):
                os.makedirs(d)
        except OSError, e:
            if e.errno != errno.EEXIST:
                raise

    def _generatePath(self, key, suffix):
        return "%s/%s.%s" % (self._cacheDir, key, suffix)

    def _lockFileName(self, key):
        return self._generatePath(key, 'lock')

    def _manifestFileName(self, key):
        return self._generatePath(key, 'manifest')

    def _seedFileName(self, key):
        return self._generatePath(key, 'code')

    def _sanitizeKeyForFileName(self, key):
        return base64.urlsafe_b64encode(key)

    @contextmanager
    def lock(self, key):
        sanitizedKey = key.hash
        lock = lockfile.LockFile(self._lockFileName(sanitizedKey))
        while True:
            try:
                lock.acquire(timeout=60)
                break
            except lockfile.LockTimeout:
                logging.warning('Lock timed out for key %(key)s try to heal cache', dict(key=key))
                if self._isLockingProcessAliveForLockFile(lock):
                    raise
                logging.warning('Cache is locked by dead process.'
                                'unlock and drop key %(key)s', dict(key=key))
                self.removeKey(sanitizedKey)
                continue
        try:
            yield
        finally:
            lock.release()

    def _validateDependencies(self, key):
        dependencies = self._loadManifest(key)['deps']
        for depPath, mTime in dependencies.iteritems():
            fileMtime = int(os.path.getmtime(depPath))
            if fileMtime != int(mTime):
                logging.debug('Mismatched timestemp on artifact %(depPath)s'
                              'current %(current)d registered %(registered)d',
                              dict(depPath=depPath, current=fileMtime, registered=mTime))
                return False
        return True

    def _isLockingProcessAliveForLockFile(self, lock):
        lockFileStat = os.stat(lock.lock_file)

        def _isSameUniqueLock(uniqueFile):
            fullPath = os.path.join(self._cacheDir, uniqueFile)
            return lockFileStat.st_ino == os.stat(fullPath).st_ino

        def _getPidFromUniqueFile(uniqueLockFile):
            return uniqueLockFile.split('.')[1]

        def _existsPid(pid):
            try:
                os.kill(pid, 0)
            except OSError:
                return False
            else:
                return True

        uniqueLockFiles = [f for f in os.listdir(self._cacheDir)
                           if not (f.endswith('.code') or f.endswith('.deps') or f.endswith('.lock'))]
        for uniqueLockFile in uniqueLockFiles:
            if _isSameUniqueLock(uniqueLockFile):
                pid = _getPidFromUniqueFile(uniqueLockFile)
                return _existsPid(int(pid))

    def _loadManifest(self, key):
        path = self._manifestFileName(key)
        with open(path, 'r') as f:
            return json.loads(f.read())

    def _storeManifest(self, key, manifest):
        path = self._manifestFileName(key)
        with open(path, 'w') as f:
            f.write(json.dumps(manifest))