import os
import re

from .adb import adb

SQLITE_BIN = '/data/local/tmp/bin/sqlite3'


def build_db_path(package, db):
    """
    Build the full device path for a package's database

    :param package: The package's name
    :param db: The database's name
    """
    if db.endswith('.db'):
        db = db.rsplit('.', 1)[0]
    return '/data/data/{package}/databases/{db}.db'.format(package=package, db=db)


def execute(db_path, statement):
    """
    Execute an sqlite3 statement on a DB

    :param db_path: The DB to operate on.
    :param statement: The statement to execute
    """
    if not adb.file_exists(db_path, use_su=True):
        raise ValueError("DB file %s does not exist" % db_path)
    cmd = r'%s %s \"%s\"' % (SQLITE_BIN, db_path, statement)
    return adb.shell(cmd, use_su=True)


def databases_for_package(package):
    """
    List all available database for a package
    """
    db_glob_path = build_db_path(package, '*')
    db_paths = adb.shell('ls %s' % db_glob_path, use_su=True).strip().split()
    db_names = [os.path.splitext(os.path.basename(db_path))[0] for db_path in db_paths]
    return db_names


class Database(object):
    """
    A wrapper class for some useful DB operations.
    """

    def __init__(self, package, database):
        self.package = package
        self.database = database
        self.db_path = build_db_path(package, database)

    def execute(self, statement):
        """
        Execute an sqlite3 statement on a DB and return the raw result

        :param statement: The SQL statement to execute
        """
        return execute(self.db_path, statement).strip()

    @property
    def tables(self):
        return self.execute('.tables').strip().split()

    def schema(self, schema_pattern=None):
        """
        Get the schema for the db or a specific table.

        :param schema_pattern: An optional pattern to filter tables
        """
        if schema_pattern:
            stmt = ".schema %s" % schema_pattern
        else:
            stmt = '.schema'

        return self.execute(stmt).splitlines()

    def query(self, statement, expected_fields_number=-1):
        """
        Execute the query statement and return a formatted response where each raw from the response will be
        a list in the list of rows

        :param statement: The SQL statement to execute
        :param expected_fields_number: The number of expected fields, if unknown pass -1
        """
        rows = self.execute(statement).splitlines()
        maxsplit = expected_fields_number - 1 if expected_fields_number > 0 else -1
        return [row.split('|', maxsplit) for row in rows]


class KeyValueTable(object):
    """
    A container for key-value DB tables
    """

    def __init__(self, db, table, key='name', value='value'):
        """
        Initialize the container.

        :param db: The database that contains the Key-Value table.
        :type db: Database
        :param table: The table's name inside the DB
        :param key: The name of the key field in the table
        :param value: The name of the value field in the table
        """
        self.db = db
        self.table = table
        self._key = key
        self._value = value

    def _get_all_as_dict(self):
        query = 'select {key}, {value} from {table}'.format(key=self._key, value=self._value, table=self.table)
        return dict(self.db.query(query, 2))

    def __getitem__(self, item):
        query = r"select {key}, {value} from {table} " \
                r"where {key}='{item}';".format(key=self._key, value=self._value, table=self.table, item=item)
        result = self.db.query(query)
        if not result:
            raise KeyError(item)
        key, value = result[0]
        return value

    def has_key(self, key):
        return key in self

    def __contains__(self, item):
        stmt = "select {key_name} from {table} where {key_name}='{key}';".format(key_name=self._key,
                                                                                 table=self.table,
                                                                                 key=item)
        return self.db.execute(stmt) == item

    def __setitem__(self, key, value):
        if key in self:
            self._update_key(key, value)
        else:
            self._insert_key(key, value)

    def _update_key(self, key, value):
        stmt = "update {table} set {value_name}='{value}' where {key_name}='{key}'".format(table=self.table,
                                                                                           value_name=self._value,
                                                                                           key_value=self._key,
                                                                                           value=value,
                                                                                           key=key)
        self.db.execute(stmt)

    def _insert_key(self, key, value):
        stmt = "insert into {table} ({key_name}, {value_name}) " \
               "values ('{key}', '{value}')".format(table=self.table, key_name=self._key, value_name=self._value,
                                                key=key, value=value)
        self.db.execute(stmt)

    def items(self):
        return self._get_all_as_dict().items()

    def iteritems(self):
        return self._get_all_as_dict().iteritems()

    def keys(self):
        return self._get_all_as_dict().keys()

    def iterkeys(self):
        return self._get_all_as_dict().iterkeys()

    def values(self):
        return self._get_all_as_dict().values()

    def itervalues(self):
        return self._get_all_as_dict().itervalues()

    def __iter__(self):
        return iter(self._get_all_as_dict())

    def items_by_pattern(self, key_pattern):
        """
        Get all items filtered by a key pattern.

        :param key_pattern: The key pattern (a regex string)
        """
        items = self._get_all_as_dict().items()
        filtered_items = [(k, v) for k, v in items if re.search(key_pattern, k)]
        return filtered_items

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

