import os
import time
from collections import OrderedDict

from conans.paths import (CONANFILE, CONANINFO, CONANFILE_TXT, BUILD_INFO, build_exists)
from conans.client.loader import ConanFileLoader
from conans.client.export import export_conanfile
from conans.client.deps_builder import DepsBuilder
from conans.client.userio import UserIO
from conans.client.installer import ConanInstaller
from conans.util.files import save, load, rmdir, normalize
from conans.util.log import logger
from conans.client.uploader import ConanUploader
from conans.client.printer import Printer
from conans.errors import NotFoundException, ConanException
from conans.client.generators import write_generators
from conans.client.importer import FileImporter
from conans.model.ref import ConanFileReference, PackageReference
from conans.client.remover import ConanRemover
from conans.model.info import ConanInfo
from conans.model.values import Values
from conans.model.options import OptionsValues
from conans.model.build_info import DepsCppInfo, CppInfo
from conans.client import packager
from conans.client.detect import detected_os
from conans.client.package_copier import PackageCopier
from conans.client.output import ScopedOutput
from conans.client.proxy import ConanProxy
from conans.client.remote_registry import RemoteRegistry
from conans.client.file_copier import report_copied_files
from conans.model.scope import Scopes
from conans.client.client_cache import ClientCache
from conans.client.source import config_source
from conans.client.manifest_manager import ManifestManager
from conans.model.env_info import EnvInfo, DepsEnvInfo
from conans.tools import environment_append


def get_user_channel(text):
    tokens = text.split('/')
    try:
        user = tokens[0]
        channel = tokens[1]
    except IndexError:
        channel = "testing"
    return user, channel


class ConanManager(object):
    """ Manage all the commands logic  The main entry point for all the client
    business logic
    """
    def __init__(self, client_cache, user_io, runner, remote_manager, search_manager):
        assert isinstance(user_io, UserIO)
        assert isinstance(client_cache, ClientCache)
        self._client_cache = client_cache
        self._user_io = user_io
        self._runner = runner
        self._remote_manager = remote_manager
        self._current_scopes = None
        self._search_manager = search_manager

    def _loader(self, current_path=None, user_settings_values=None, user_options_values=None,
                scopes=None):

        # The disk settings definition, already including the default disk values
        settings = self._client_cache.settings

        options = OptionsValues()
        conaninfo_scopes = Scopes()

        if current_path:
            conan_info_path = os.path.join(current_path, CONANINFO)
            if os.path.exists(conan_info_path):
                existing_info = ConanInfo.load_file(conan_info_path)
                settings.values = existing_info.full_settings
                options = existing_info.full_options  # Take existing options from conaninfo.txt
                conaninfo_scopes = existing_info.scope

        if user_settings_values:
            aux_values = Values.from_list(user_settings_values)
            settings.values = aux_values

        if user_options_values is not None:  # Install will pass an empty list []
            # Install OVERWRITES options, existing options in CONANINFO are not taken
            # into account, just those from CONANFILE + user command line
            options = OptionsValues.from_list(user_options_values)

        if scopes:
            conaninfo_scopes.update_scope(scopes)

        self._current_scopes = conaninfo_scopes
        return ConanFileLoader(self._runner, settings, options=options, scopes=conaninfo_scopes)

    def export(self, user, conan_file_path, keep_source=False):
        """ Export the conans
        param conanfile_path: the original source directory of the user containing a
                           conanfile.py
        param user: user under this package will be exported
        param channel: string (stable, testing,...)
        """
        assert conan_file_path
        logger.debug("Exporting %s" % conan_file_path)
        user_name, channel = get_user_channel(user)
        conan_file = self._loader().load_class(os.path.join(conan_file_path, CONANFILE))
        url = getattr(conan_file, "url", None)
        license_ = getattr(conan_file, "license", None)
        if not url:
            self._user_io.out.warn("Conanfile doesn't have 'url'.\n"
                                   "It is recommended to add your repo URL as attribute")
        if not license_:
            self._user_io.out.warn("Conanfile doesn't have a 'license'.\n"
                                   "It is recommended to add the package license as attribute")

        conan_ref = ConanFileReference(conan_file.name, conan_file.version, user_name, channel)
        conan_ref_str = str(conan_ref)
        # Maybe a platform check could be added, but depends on disk partition
        refs = self._search_manager.search(conan_ref_str, ignorecase=True)
        if refs and conan_ref not in refs:
            raise ConanException("Cannot export package with same name but different case\n"
                                 "You exported '%s' but already existing '%s'"
                                 % (conan_ref_str, " ".join(str(s) for s in refs)))
        output = ScopedOutput(str(conan_ref), self._user_io.out)
        export_conanfile(output, self._client_cache, conan_file.exports, conan_file_path,
                         conan_ref, conan_file.short_paths, keep_source)

    def download(self, reference, package_ids, remote=None):
        """ Download conanfile and specified packages to local repository
        @param reference: ConanFileReference
        @param package_ids: Package ids or empty for download all
        @param remote: install only from that remote
        """
        assert(isinstance(reference, ConanFileReference))
        remote_proxy = ConanProxy(self._client_cache, self._user_io, self._remote_manager, remote)

        if package_ids:
            remote_proxy.download_packages(reference, package_ids)
        else:  # Not specified packages, download all
            packages_props = remote_proxy.search_packages(reference, None)
            if not packages_props:  # No filter by properties
                raise ConanException("'%s' not found in remote" % str(reference))

            remote_proxy.download_packages(reference, list(packages_props.keys()))

    def _get_graph(self, reference, current_path, remote, options, settings, filename, update,
                   check_updates, manifest_manager, scopes):

        loader = self._loader(current_path, settings, options, scopes)
        # Not check for updates for info command, it'll be checked when dep graph is built

        remote_proxy = ConanProxy(self._client_cache, self._user_io, self._remote_manager, remote,
                                  update=update, check_updates=check_updates,
                                  manifest_manager=manifest_manager)

        if isinstance(reference, ConanFileReference):
            project_reference = None
            conanfile = loader.load_virtual(reference, current_path)