from conans.client.generators.cmake_common import cmake_dependencies, cmake_dependency_vars, \
    cmake_global_vars, cmake_macros, cmake_package_info, cmake_settings_info, cmake_user_info_vars, \
    generate_targets_section, CMakeCommonMacros
from conans.model import Generator
from conans.paths import BUILD_INFO_CMAKE


class DepsCppCmake(object):
    def __init__(self, cpp_info, generator_name):
        def join_paths(paths):
            """
            Paths are doubled quoted, and escaped (but spaces)
            e.g: set(LIBFOO_INCLUDE_DIRS "/path/to/included/dir" "/path/to/included/dir2")
            """
            return "\n\t\t\t".join('"%s"'
                                   % p.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')
                                   for p in paths)

        def join_flags(separator, values):
            # Flags have to be escaped
            return separator.join(v.replace('\\', '\\\\').replace('$', '\\$').replace('"', '\\"')
                                  for v in values)

        def join_defines(values, prefix=""):
            # Defines have to be escaped, included spaces
            return "\n\t\t\t".join('"%s%s"' % (prefix, v.replace('\\', '\\\\').replace('$', '\\$').
                                   replace('"', '\\"'))
                                   for v in values)

        def join_paths_single_var(values):
            """
            semicolon-separated list of dirs:
            e.g: set(LIBFOO_INCLUDE_DIR "/path/to/included/dir;/path/to/included/dir2")
            """
            return '"%s"' % ";".join(p.replace('\\', '/').replace('$', '\\$') for p in values)

        def format_link_flags(link_flags):
            # Trying to mess with - and / => https://github.com/conan-io/conan/issues/8811
            return link_flags

        self.include_paths = join_paths(cpp_info.include_paths)
        self.include_path = join_paths_single_var(cpp_info.include_paths)
        self.lib_paths = join_paths(cpp_info.lib_paths)
        self.res_paths = join_paths(cpp_info.res_paths)
        self.bin_paths = join_paths(cpp_info.bin_paths)
        self.build_paths = join_paths(cpp_info.build_paths)
        self.src_paths = join_paths(cpp_info.src_paths)
        self.framework_paths = join_paths(cpp_info.framework_paths)
        self.libs = join_flags(" ", cpp_info.libs)
        self.system_libs = join_flags(" ", cpp_info.system_libs)
        self.frameworks = join_flags(" ", cpp_info.frameworks)
        self.defines = join_defines(cpp_info.defines, "-D")
        self.compile_definitions = join_defines(cpp_info.defines)

        self.cxxflags = join_flags(" ", cpp_info.cxxflags)
        self.cflags = join_flags(" ", cpp_info.cflags)
        self.sharedlinkflags = join_flags(" ", cpp_info.sharedlinkflags)
        self.exelinkflags = join_flags(" ", cpp_info.exelinkflags)

        # For modern CMake targets we need to prepare a list to not
        # loose the elements in the list by replacing " " with ";". Example "-framework Foundation"
        # Issue: #1251
        self.cxxflags_list = join_flags(";", cpp_info.cxxflags)
        self.cflags_list = join_flags(";", cpp_info.cflags)
        self.sharedlinkflags_list = join_flags(";", format_link_flags(cpp_info.sharedlinkflags))
        self.exelinkflags_list = join_flags(";", format_link_flags(cpp_info.exelinkflags))

        self.rootpath = join_paths([cpp_info.rootpath])
        self.build_modules_paths = join_paths(cpp_info.build_modules_paths.get(generator_name, []))


class CMakeGenerator(Generator):
    name = "cmake"

    @property
    def filename(self):
        return BUILD_INFO_CMAKE

    @property
    def content(self):
        sections = ["include(CMakeParseArguments)"]
        sections.append(CMakeCommonMacros.apple_frameworks_macro)

        # Per requirement variables
        for _, dep_cpp_info in self.deps_build_info.dependencies:
            dep_name = dep_cpp_info.get_name(self.name)
            deps = DepsCppCmake(dep_cpp_info, self.name)
            dep_flags = cmake_dependency_vars(dep_name, deps=deps)
            sections.append(dep_flags)

            for config, cpp_info in dep_cpp_info.configs.items():
                deps = DepsCppCmake(cpp_info, self.name)
                dep_flags = cmake_dependency_vars(dep_name, deps=deps, build_type=config)
                sections.append(dep_flags)

        # GENERAL VARIABLES
        sections.append("\n### Definition of global aggregated variables ###\n")
        sections.append(cmake_package_info(name=self.conanfile.name,
                                           version=self.conanfile.version))
        sections.append(cmake_settings_info(self.conanfile.settings))
        all_flags = cmake_dependencies(dependencies=self.deps_build_info.deps)
        sections.append(all_flags)
        deps = DepsCppCmake(self.deps_build_info, self.name)
        all_flags = cmake_global_vars(deps=deps)
        sections.append(all_flags)

        for config, cpp_info in self.deps_build_info.configs.items():
            deps = DepsCppCmake(cpp_info, self.name)
            dep_flags = cmake_global_vars(deps=deps, build_type=config)
            sections.append(dep_flags)

        # TARGETS
        sections.extend(generate_targets_section(self.deps_build_info.dependencies, self.name))

        # MACROS
        sections.append(cmake_macros)

        # USER DECLARED VARS
        sections.append("\n### Definition of user declared vars (user_info) ###\n")
        sections.append(cmake_user_info_vars(self.conanfile.deps_user_info))

        return "\n".join(sections)
