import platform
import sys

from bson.py3compat import string_type

from . import errors, version
from .base import BaseObject, ClientOptions
from .configure import provide_storage, provide_repository
from .database import MontyDatabase


class MontyClient(BaseObject):

    def __init__(self,
                 repository=None,
                 document_class=dict,
                 tz_aware=None,
                 **kwargs):
        """Client for a MontyDB instance

        The `repository` argument can be a montydb URI. A montydb URI is simply
        a repository path that prefixed with montydb scheme.
        For example:
            `montydb:///foo/bar/db_repo` -> Point to a absolute dir path
            `montydb://db_repo` -> Point to a relative dir path
            `montydb://` -> Point to current working dir or pinned dir
            `montydb://:memory:` -> Use memory storage

        Args:
            repository (str): A dir path for on-disk storage or `:memory:` for
                memory storage only, or a montydb URI.
            document_class (cls, optional): default class to use for documents
                returned from queries on this client. Default class is `dict`.
            tz_aware (bool, optional): if `True`, `datetime.datetime` instances
                returned as values in document by this client will be timezone
                awared (otherwise they will be naive).
            **kwargs: Other optional keyword arguments will pass into storage
                engine as write concern arguments.

        """
        repository = provide_repository(repository)
        storage_cls = provide_storage(repository)
        storage_instance = storage_cls.launch(repository)

        self._storage = storage_instance
        wconcern = self._storage.wconcern_parser(**kwargs)

        options = kwargs
        options["document_class"] = document_class
        options["tz_aware"] = tz_aware or False
        self.__options = ClientOptions(options, wconcern)
        super(MontyClient, self).__init__(self.__options.codec_options,
                                          self.__options.write_concern)

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.address == other.address
        return NotImplemented

    def __ne__(self, other):
        return not self == other

    def __repr__(self):
        return ("MontyClient({})".format(
            ", ".join([
                "repository={!r}".format(
                    self.address
                ),
                "document_class={}.{}".format(
                    self.__options._options["document_class"].__module__,
                    self.__options._options["document_class"].__name__
                ),
                "storage_engine={}".format(
                    self._storage
                ),
            ]))
        )

    def __getattr__(self, name):
        if name.startswith('_'):
            raise AttributeError(
                "MontyClient has no attribute {0!r}. To access the {0}"
                " database, use client[{0!r}].".format(name))
        return self.get_database(name)

    def __getitem__(self, key):
        return self.get_database(key)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        if self._storage.is_open:
            self.close()

    @property
    def address(self):
        return self._storage.repository

    def close(self):
        self._storage.close()

    def database_names(self):
        """
        Return a list of database names.
        """
        return self._storage.database_list()

    def drop_database(self, name_or_database):
        """
        Remove database.
        # Could raise OSError: Device or resource busy
        if db file is locked by other connection...
        """
        name = name_or_database
        if isinstance(name_or_database, MontyDatabase):
            name = name_or_database.name
        elif not isinstance(name_or_database, string_type):
            raise TypeError("name_or_database must be an instance of "
                            "basestring or a Database")

        self._storage.database_drop(name)

    def get_database(self, name):
        """
        Get a database, create one if not exists.
        """
        # verify database name
        if platform.system() == "Windows":
            is_invaild = set('/\. "$*<>:|?').intersection(set(name))
        else:
            is_invaild = set('/\. "$').intersection(set(name))

        if is_invaild or not name:
            raise errors.OperationFailure("Invaild database name.")
        else:
            return MontyDatabase(self, name)

    def server_info(self):
        return {
            "version": version.version,
            "versionArray": list(version.version_info),
            "mongoVersion": version.mongo_version,
            "mongoVersionArray": list(version.mongo_version_info),
            "storageEngine": repr(self._storage),
            "python": sys.version,
            "platform": platform.platform(),
            "machine": platform.machine(),
        }
