from __future__ import with_statement

from os import listdir
from os.path import join as join_paths, basename, isdir, isfile
from plistlib import readPlist

from biplist import readPlist as readBinaryPlist

from .conf import BACKUPS_PATH, BACKUP_DEFAULT_SETTINGS
from iosfu import utils


class BackupManager(object):
    # Path to backups
    path = None

    # Backups loaded
    backups = {}

    def __init__(self, path=BACKUPS_PATH):
        self.path = path

    def lookup(self):
        """
        Look for backup folders on PATH
        """
        folders = listdir(self.path)
        for dirname in folders:
            path = join_paths(self.path, dirname)
            if isdir(path):
                backup = Backup(path)
                self.backups[backup.id] = backup

    def get(self, backup_id):
        if backup_id in self.backups and self.backups[backup_id].valid:
            return self.backups[backup_id]
        else:
            raise Exception('Backup not registered')


class Backup(object):
    """
    Backup object
    """
    # Backup id
    id = None

    # Backup path
    path = None

    # Files
    files = []

    # bool if its valid -> self.init_check()
    valid = True

    # Required files to mark as valid
    _required_files = [
        'Info.plist', 'Manifest.mbdb', 'Manifest.plist', 'Status.plist'
    ]

    # File handlers to call methods
    _file_handlers = {
        '.plist': '_read_plist'
    }

    _plist = {}

    # Data
    _data_file = None
    _data = {}

    def __init__(self, path):
        self.path = path
        self.get_info()
        self._data_file = self.get_data_file()
        self.init_check()
        self.read_data_file()

    @property
    def name(self):
        name = self.data('name') or self.id
        return name

    def get_data_file(self):
        return "{}.iosfu".format(self.path)

    def read_data_file(self):
        try:
            handler = open(self._data_file)
        except (OSError, IOError):
            # Create default config file if non-existant
            handler = open(self._data_file, 'w+')
            handler.write(utils.serialize(BACKUP_DEFAULT_SETTINGS))
            handler.seek(0)
        finally:
            with handler as f:
                data_file = f.read()

            self._data = utils.deserialize(data_file)
            handler.close()

    def get_info(self):
        """
        Get all the basic info for the backup
        """
        self.id = basename(self.path)

        # Check all files
        for filename in listdir(self.path):
            if isfile(join_paths(self.path, filename)):
                self.files.append(filename)

                # Check handlers
                for match in self._file_handlers.keys():
                    if match in filename:
                        handler = getattr(self, self._file_handlers[match])
                        handler(filename)

    def init_check(self):
        """
        Check if the needed stuff are there to consider this a backup
        """
        for required_file in self._required_files:
            # Check if required files are there
            # FIXME Sometimes it doesn't work :?
            if required_file not in self.files:
                self.valid = False

    def exists(self, filename):
        """
        Check if the given file exists
        """
        return filename in self.files

    def get_file(self, filename, handler=False):
        """
        Returns given file path
        - handler (bool) - Returns handler instead of path
        """
        result = None
        if self.exists(filename):
            file_path = join_paths(self.path, filename)
            if handler:
                result = open(file_path, 'rb')
            else:
                result = file_path
        return result

    #
    #   File handlers
    #
    def _read_plist(self, filename):
        """
        Handler for .plist files
        Reads them and stores on self._plist for plugin access
        """
        file_path = self.get_file(filename)
        try:
            self._plist[filename] = readPlist(file_path)
        except:
            # Is binaryPlist?
            try:
                self._plist[filename] = readBinaryPlist(file_path)
            except:
                # What is it?
                pass

    #
    #   Backup data file
    #
    def data(self, key, value=None):
        result = value
        if value:
            self._data[key] = value
        elif key in self._data:
            result = self._data[key]

        return result

    def cache(self, key, value=None):
        result = value
        if value:
            self._data['cache'][key] = value
        elif key in self._data['cache']:
            result = self._data['cache'][key]

        return result

    def clear_cache(self):
        self._data['cache'] = {}
        self.write_data_file()

    def write_data_file(self):
        handler = open(self._data_file, 'w+')
        handler.write(utils.serialize(self._data))
        handler.close()
