import os
import unittest

from conans.model.info import ConanInfo
from conans.model.ref import PackageReference
from conans.model.settings import undefined_value, bad_value_msg
from conans.paths import CONANFILE, CONANINFO
from conans.test.utils.tools import TestClient
from conans.util.files import save, load


class SettingsTest(unittest.TestCase):

    def wrong_settings_test(self):
        settings = """os:
    None:
        subsystem: [None, msys]
"""
        client = TestClient()
        save(client.paths.settings_path, settings)
        save(client.paths.default_profile_path, "")
        conanfile = """from conans import ConanFile
class Pkg(ConanFile):
    settings = "os", "compiler"
"""
        client.save({"conanfile.py": conanfile})
        error = client.run("create . Pkg/0.1@lasote/testing", ignore_error=True)
        self.assertTrue(error)
        self.assertIn("ERROR: settings.yml: None setting can't have subsettings", client.out)

    def custom_compiler_preprocessor_test(self):
        # https://github.com/conan-io/conan/issues/3842
        settings = """compiler:
    mycomp:
        version: ["2.3", "2.4"]
cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
"""
        client = TestClient()
        save(client.paths.settings_path, settings)
        save(client.paths.default_profile_path, """[settings]
compiler=mycomp
compiler.version=2.3
cppstd=11
""")
        conanfile = """from conans import ConanFile
class Pkg(ConanFile):
    settings = "compiler", "cppstd"
"""
        client.save({"conanfile.py": conanfile})
        client.run("create . Pkg/0.1@lasote/testing")
        self.assertIn("""Configuration:
[settings]
compiler=mycomp
compiler.version=2.3
cppstd=11""", client.out)
        self.assertIn("Pkg/0.1@lasote/testing: Package "
                      "'c2f0c2641722089d9b11cd646c47d239af044b5a' created",
                      client.out)

    def custom_settings_test(self):
        settings = """os:
    None:
    Windows:
        subsystem: [None, cygwin]
    Linux:
compiler: [gcc, visual]
"""
        client = TestClient()
        save(client.paths.settings_path, settings)
        save(client.paths.default_profile_path, "")
        conanfile = """from conans import ConanFile
class Pkg(ConanFile):
    settings = "os", "compiler"
"""
        client.save({"conanfile.py": conanfile})
        client.run("create . Pkg/0.1@lasote/testing -s compiler=gcc")
        self.assertIn("544c1d8c53e9d269737e68e00ec66716171d2704", client.out)
        client.run("search Pkg/0.1@lasote/testing")
        self.assertNotIn("os: None", client.out)
        package_reference = PackageReference.loads("Pkg/0.1@lasote/testing:"
                                                   "544c1d8c53e9d269737e68e00ec66716171d2704")
        info_path = os.path.join(client.paths.package(package_reference), CONANINFO)
        info = load(info_path)
        self.assertNotIn("os", info)
        # Explicitly specifying None, put it in the conaninfo.txt, but does not affect the hash
        client.run("create . Pkg/0.1@lasote/testing -s compiler=gcc -s os=None")
        self.assertIn("544c1d8c53e9d269737e68e00ec66716171d2704", client.out)
        client.run("search Pkg/0.1@lasote/testing")
        self.assertIn("os: None", client.out)
        info = load(info_path)
        self.assertIn("os", info)

    def test_update_settings(self):
        # This test is to validate that after adding a new settings, that allows a None
        # value, this None value does not modify exisisting packages SHAs
        file_content = '''
from conans import ConanFile

class ConanFileToolsTest(ConanFile):
    name = "test"
    version = "1.9"
    settings = "os", "compiler", "arch"

    def source(self):
        self.output.warn("Sourcing...")

    def build(self):
        self.output.warn("Building...")
    '''
        prev_settings = """
os: [Windows, Linux, Macos, Android, FreeBSD, SunOS]
arch: [x86, x86_64, armv6, armv7, armv7hf, armv8, sparc, sparcv9]
os_build: [Windows, Linux, Macos, Android, FreeBSD, SunOS]
arch_build: [x86, x86_64, armv6, armv7, armv7hf, armv8, sparc, sparcv9]

compiler:
    sun-cc:
        version: ["5.10", "5.11", "5.12", "5.13", "5.14"]
        libcxx: [libCstd, libstdcxx libstlport, libstdc++]
    gcc:
        version: ["4.4", "4.5", "4.6", "4.7", "4.8", "4.9", "5.1", "5.2", "5.3", "5.4", "6.1", "6.2", "6.3"]
        libcxx: [libstdc++, libstdc++11]
    Visual Studio:
        runtime: [None, MD, MT, MTd, MDd]
        version: ["8", "9", "10", "11", "12", "14"]
    clang:
        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8"]
        libcxx: [libstdc++, libstdc++11, libc++]
    apple-clang:
        version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.1", "7.2", "7.3", "8.0", "8.1"]
        libcxx: [libstdc++, libc++]

"""
        files = {"conanfile.py": file_content}
        client = TestClient()
        client.save(files)
        client.run("export . lasote/testing")
        save(client.paths.settings_path, prev_settings)
        client.client_cache.default_profile  # Generate the default
        conf = load(client.client_cache.default_profile_path)
        conf = conf.replace("build_type=Release", "")
        self.assertNotIn("build_type", conf)
        save(client.client_cache.default_profile_path, conf)

        client.run("install test/1.9@lasote/testing --build -s arch=x86_64 -s compiler=gcc "
                   "-s compiler.version=4.9 -s os=Windows -s compiler.libcxx=libstdc++")
        self.assertIn("390146894f59dda18c902ee25e649ef590140732", client.user_io.out)

        # Now the new one
        files = {"conanfile.py": file_content.replace('"arch"', '"arch", "build_type"')}
        client = TestClient()
        client.save(files)
        client.run("export . lasote/testing")

        client.run("install test/1.9@lasote/testing --build -s arch=x86_64 -s compiler=gcc "
                   "-s compiler.version=4.9 -s os=Windows -s build_type=None -s "
                   "compiler.libcxx=libstdc++")
        self.assertIn("build_type", load(client.paths.settings_path))
        self.assertIn("390146894f59dda18c902ee25e649ef590140732", client.user_io.out)

    def settings_constraint_error_type_test(self):
        # https://github.com/conan-io/conan/issues/3022
        conanfile = """from conans import ConanFile
class Test(ConanFile):
    settings = {"os": "Linux"}
    def build(self):
        self.output.info("OS!!: %s" % self.settings.os)
    """
        client = TestClient()
        client.save({"conanfile.py": conanfile})
        client.run("create . Pkg/0.1@user/testing -s os=Linux")
        self.assertIn("Pkg/0.1@user/testing: OS!!: Linux", client.out)

    def settings_constraint_test(self):
        conanfile = """from conans import ConanFile
class Test(ConanFile):
    name = "Hello"
    version = "0.1"
    settings = {"compiler": {"gcc": {"version": ["7.1"]}}}
    def build(self):
        self.output.info("Compiler version!: %s" % self.settings.compiler.version)
    """
        test = """from conans import ConanFile
class Test(ConanFile):
    requires = "Hello/0.1@user/channel"
    def test(self):
        pass
    """
        client = TestClient()
        client.save({"conanfile.py": conanfile,
                     "test_package/conanfile.py": test})
        default_profile = os.path.join(client.base_folder, ".conan/profiles/default")
        save(default_profile, "[settings]\ncompiler=gcc\ncompiler.version=6.3")
        error = client.run("create . user/channel", ignore_error=True)
        self.assertTrue(error)
        self.assertIn("Invalid setting '6.3' is not a valid 'settings.compiler.version'",
                      client.user_io.out)
        client.run("create . user/channel -s compiler=gcc -s compiler.version=7.1")
        self.assertIn("Hello/0.1@user/channel: Compiler version!: 7.1", client.user_io.out)
        self.assertIn("Hello/0.1@user/channel: Generating the package", client.user_io.out)

    def settings_as_a_str_test(self):
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = "os"
"""
        client = TestClient()
        client.save({CONANFILE: content})
        client.run("install . -s os=Windows --build missing")
        # Now read the conaninfo and verify that settings applied is only os and value is windows
        conan_info = ConanInfo.loads(load(os.path.join(client.current_folder, CONANINFO)))
        self.assertEquals(conan_info.settings.os, "Windows")

        client.run("install . -s os=Linux --build missing")
        # Now read the conaninfo and verify that settings applied is only os and value is windows
        conan_info = ConanInfo.loads(load(os.path.join(client.current_folder, CONANINFO)))
        self.assertEquals(conan_info.settings.os, "Linux")

    def settings_as_a_list_conanfile_test(self):
        # Now with conanfile as a list
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = "os", "arch"
"""
        client = TestClient()
        client.save({CONANFILE: content})
        client.run("install . -s os=Windows --build missing")
        conan_info = ConanInfo.loads(load(os.path.join(client.current_folder, CONANINFO)))
        self.assertEquals(conan_info.settings.os,  "Windows")
        self.assertEquals(conan_info.settings.fields, ["arch", "os"])

    def settings_as_a_dict_conanfile_test(self):
        # Now with conanfile as a dict
        # XXX: this test only works on machines w default arch "x86", "x86_64", "sparc" or "sparcv9"
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = {"os": ["Windows"], "arch": ["x86", "x86_64", "sparc", "sparcv9"]}
"""
        client = TestClient()
        client.save({CONANFILE: content})
        client.run("install . -s os=Windows --build missing")
        conan_info = ConanInfo.loads(load(os.path.join(client.current_folder, CONANINFO)))
        self.assertEquals(conan_info.settings.os,  "Windows")
        self.assertEquals(conan_info.settings.fields, ["arch", "os"])

    def invalid_settings_test(self):
        '''Test wrong values and wrong constraints'''
        client = TestClient()
        client.client_cache.default_profile
        default_conf = load(client.paths.default_profile_path)
        new_conf = default_conf.replace("\nos=", "\n# os=")
        save(client.paths.default_profile_path, new_conf)
        # MISSING VALUE FOR A SETTING
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = "os", "build_type"
"""

        client.save({CONANFILE: content})
        client.run("install . --build missing", ignore_error=True)
        self.assertIn(str(undefined_value("settings.os")), str(client.user_io.out))

    def invalid_settings_test2(self):
        # MISSING A DEFAULT VALUE BECAUSE ITS RESTRICTED TO OTHER, SO ITS REQUIRED
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = {"os": ["Windows", "Linux", "Macos", "FreeBSD", "SunOS"],
                "compiler": ["Visual Studio"]}
"""
        client = TestClient()
        client.save({CONANFILE: content})
        client.run("install . -s compiler=gcc -s compiler.version=4.8 --build missing",
                   ignore_error=True)
        self.assertIn(bad_value_msg("settings.compiler", "gcc", ["Visual Studio"]),
                      str(client.user_io.out))

    def invalid_settings_test3(self):
        # dict without options
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = {"os": None, "compiler": ["Visual Studio"]}
"""
        client = TestClient()
        client.save({CONANFILE: content})
        client.run("install . -s compiler=gcc -s compiler.version=4.8 --build missing",
                   ignore_error=True)
        self.assertIn(bad_value_msg("settings.compiler", "gcc", ["Visual Studio"]),
                      str(client.user_io.out))

        # Test wrong settings in conanfile
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = invalid
"""

        client.save({CONANFILE: content})
        client.run("install . --build missing", ignore_error=True)
        self.assertIn("invalid' is not defined",
                      str(client.user_io.out))

        # Test wrong values in conanfile
    def invalid_settings_test4(self):
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = "os"
"""
        client = TestClient()
        client.save({CONANFILE: content})
        client.run("install . -s os=ChromeOS --build missing", ignore_error=True)
        self.assertIn(bad_value_msg("settings.os", "ChromeOS",
                                    ['Android', 'Arduino', 'FreeBSD', 'Linux', 'Macos', 'SunOS',
                                     'Windows', 'WindowsStore', 'iOS', 'tvOS', 'watchOS']),
                      str(client.user_io.out))

        # Now add new settings to config and try again
        config = load(client.paths.settings_path)
        config = config.replace("Windows:%s" % os.linesep,
                                "Windows:%s    ChromeOS:%s" % (os.linesep, os.linesep))

        save(client.paths.settings_path, config)
        client.run("install . -s os=ChromeOS --build missing")
        self.assertIn('Generated conaninfo.txt', str(client.user_io.out))

        # Settings is None
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = None
"""
        client.save({CONANFILE: content})
        client.run("install . --build missing")
        self.assertIn('Generated conaninfo.txt', str(client.user_io.out))
        conan_info = ConanInfo.loads(load(os.path.join(client.current_folder, CONANINFO)))
        self.assertEquals(conan_info.settings.dumps(), "")

        # Settings is {}
        content = """
from conans import ConanFile

class SayConan(ConanFile):
    name = "Say"
    version = "0.1"
    settings = {}
"""
        client.save({CONANFILE: content})
        client.run("install . --build missing")
        self.assertIn('Generated conaninfo.txt', str(client.user_io.out))
        conan_info = ConanInfo.loads(load(os.path.join(client.current_folder, CONANINFO)))
        self.assertEquals(conan_info.settings.dumps(), "")
