# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright (C) 2015-2020 Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""The cmake plugin is useful for building cmake based parts.

These are projects that have a CMakeLists.txt that drives the build.
The plugin requires a CMakeLists.txt in the root of the source tree.

If the part has a list of build-snaps listed, the part will be set up in
such a way that the paths to those snaps are used as paths for find_package
and find_library by use of `CMAKE_FIND_ROOT_PATH``.
This plugin uses the common plugin keywords as well as those for "sources".
For more information check the 'plugins' topic for the former and the
'sources' topic for the latter.

Additionally, this plugin uses the following plugin-specific keywords:

    - configflags:
      (list of strings)
      configure flags to pass to the build using the common cmake semantics.
"""

import logging
import os
from typing import List, Optional

from snapcraft.plugins.v1 import PluginV1

logger = logging.getLogger(name=__name__)


class _Flag:
    def __str__(self) -> str:
        if self.value is None:
            flag = self.name
        else:
            flag = "{}={}".format(self.name, self.value)
        return flag

    def __init__(self, flag: str) -> None:
        parts = flag.split("=")
        self.name = parts[0]
        try:
            self.value: Optional[str] = parts[1]
        except IndexError:
            self.value = None


class CMakePlugin(PluginV1):
    @classmethod
    def schema(cls):
        schema = super().schema()
        schema["properties"]["configflags"] = {
            "type": "array",
            "minitems": 1,
            "uniqueItems": True,
            "items": {"type": "string"},
            "default": [],
        }
        # For backwards compatibility
        schema["properties"]["make-parameters"] = {
            "type": "array",
            "minitems": 1,
            "uniqueItems": True,
            "items": {"type": "string"},
            "default": [],
        }
        schema["required"] = ["source"]

        return schema

    @classmethod
    def get_build_properties(cls):
        # Inform Snapcraft of the properties associated with building. If these
        # change in the YAML Snapcraft will consider the build step dirty.
        return super().get_build_properties() + ["configflags"]

    def __init__(self, name, options, project):
        super().__init__(name, options, project)
        self.build_packages.append("cmake")
        self.out_of_source_build = True

        if options.make_parameters:
            logger.warning("make-paramaters is deprecated, ignoring.")

    def build(self):
        source_subdir = getattr(self.options, "source_subdir", None)
        if source_subdir:
            sourcedir = os.path.join(self.sourcedir, source_subdir)
        else:
            sourcedir = self.sourcedir

        env = self._build_environment()
        configflags = self._get_processed_flags()

        self.run(["cmake", sourcedir, "-DCMAKE_INSTALL_PREFIX="] + configflags, env=env)

        # TODO: there is a better way to specify the job count on newer versions of cmake
        # https://github.com/Kitware/CMake/commit/1ab3881ec9e809ac5f6cad5cd84048310b8683e2
        self.run(
            ["cmake", "--build", ".", "--", "-j{}".format(self.parallel_build_count)],
            env=env,
        )

        self.run(["cmake", "--build", ".", "--target", "install"], env=env)

    def _get_processed_flags(self) -> List[str]:
        # Return the original if no build_snaps are in options.
        if not self.options.build_snaps:
            return self.options.configflags

        build_snap_paths = [
            os.path.join(os.path.sep, "snap", snap_name.split("/")[0], "current")
            for snap_name in self.options.build_snaps
        ]

        flags = [_Flag(f) for f in self.options.configflags]
        for flag in flags:
            if flag.name == "-DCMAKE_FIND_ROOT_PATH":
                flag.value = "{};{}".format(flag.value, ";".join(build_snap_paths))
                break
        else:
            flags.append(
                _Flag("-DCMAKE_FIND_ROOT_PATH={}".format(";".join(build_snap_paths)))
            )

        return [str(f) for f in flags]

    def _build_environment(self):
        env = os.environ.copy()
        env["DESTDIR"] = self.installdir
        env["CMAKE_PREFIX_PATH"] = "$CMAKE_PREFIX_PATH:{}".format(
            self.project.stage_dir
        )
        env["CMAKE_INCLUDE_PATH"] = "$CMAKE_INCLUDE_PATH:" + ":".join(
            ["{0}/include", "{0}/usr/include", "{0}/include/{1}", "{0}/usr/include/{1}"]
        ).format(self.project.stage_dir, self.project.arch_triplet)
        env["CMAKE_LIBRARY_PATH"] = "$CMAKE_LIBRARY_PATH:" + ":".join(
            ["{0}/lib", "{0}/usr/lib", "{0}/lib/{1}", "{0}/usr/lib/{1}"]
        ).format(self.project.stage_dir, self.project.arch_triplet)

        return env
