# Copyright 2017 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from importlib import resources
from io import StringIO
import os
import sys

import em
try:
    from em import Configuration
    em_has_configuration = True
except ImportError:
    em_has_configuration = False


def _expand_template(template_file, data, output_file):
    output = StringIO()
    if em_has_configuration:
        config = Configuration(
                defaultStdout=output,
                deleteOnError=True,
                rawErrors=True,
                useProxy=True)
        interpreter = em.Interpreter(
                config=config,
                dispatcher=False,
                globals=data)
    else:
        interpreter = em.Interpreter(
                output=output,
                options={
                    em.BUFFERED_OPT: True,
                    em.RAW_OPT: True,
                    },
                globals=data)

    with open(template_file, 'r') as h:
        try:
            interpreter.file(h)
            content = output.getvalue()
        except Exception as e:
            if os.path.exists(output_file):
                os.remove(output_file)
            print("Exception when expanding '%s' into '%s': %s" %
                  (template_file, output_file, e), file=sys.stderr)
            raise
        finally:
            interpreter.shutdown()

    if os.path.exists(output_file):
        with open(output_file, 'r') as h:
            if h.read() == content:
                return
    else:
        os.makedirs(os.path.dirname(output_file), exist_ok=True)

    with open(output_file, 'w') as h:
        h.write(content)


def _create_folder(folder_name, base_directory, exist_ok=True):
    folder_path = os.path.join(base_directory, folder_name)

    print('creating folder', folder_path)
    os.makedirs(folder_path, exist_ok=exist_ok)

    return folder_path


def _create_template_file(
        template_subdir, template_file_name, output_directory, output_file_name, template_config
        ):
    full_package = 'ros2pkg.resource.' + template_subdir
    with resources.path(full_package, template_file_name) as path:
        template_path = str(path)
    if not os.path.exists(template_path):
        raise FileNotFoundError('template not found:', template_path)

    output_file_path = os.path.join(output_directory, output_file_name)

    print('creating', output_file_path)
    _expand_template(template_path, template_config, output_file_path)


def create_package_environment(package, destination_directory):
    package_directory = _create_folder(package.name, destination_directory)

    package_xml_config = {
            'package_format': package.package_format,
            'package_name': package.name,
            'package_description': package.description,
            'maintainer_email': package.maintainers[0].email,
            'maintainer_name': package.maintainers[0].name,
            'package_license': package.licenses[0],
            'buildtool_dependencies': package.buildtool_depends,
            'dependencies': package.build_depends,
            'test_dependencies': package.test_depends,
            'exports': package.exports,
            }
    _create_template_file(
            'package_environment',
            'package.xml.em',
            package_directory,
            'package.xml',
            package_xml_config)

    source_directory = None
    include_directory = None
    if package.get_build_type() == 'cmake' or package.get_build_type() == 'ament_cmake':
        print('creating source and include folder')
        source_directory = _create_folder('src', package_directory)
        include_directory = _create_folder(package.name, package_directory + os.sep + 'include')
    if package.get_build_type() == 'ament_cargo':
        print('creating source folder')
        source_directory = _create_folder('src', package_directory)
    if package.get_build_type() == 'ament_python':
        print('creating source folder')
        source_directory = _create_folder(package.name, package_directory)

    return package_directory, source_directory, include_directory


def populate_ament_python(package, package_directory, source_directory, python_node_name):
    setup_py_config = {
            'project_name': package.name,
            'maintainer_email': package.maintainers[0].email,
            'maintainer_name': package.maintainers[0].name,
            'package_license': package.licenses[0],
            'node_name': python_node_name,
            'test_dependencies': package.test_depends,
            'package_description': package.description
            }

    _create_template_file('ament_python',
                          'setup.py.em',
                          package_directory,
                          'setup.py',
                          setup_py_config)

    setup_cfg_config = {'project_name': package.name}
    _create_template_file('ament_python',
                          'setup.cfg.em',
                          package_directory,
                          'setup.cfg',
                          setup_cfg_config)

    resource_directory = _create_folder('resource', package_directory)
    _create_template_file('ament_python',
                          'resource_file.em',
                          resource_directory,
                          package.name,
                          {})

    _create_template_file('ament_python',
                          'init.py.em',
                          source_directory,
                          '__init__.py',
                          {})
    _create_template_file('ament_python',
                          'py.typed.em',
                          source_directory,
                          'py.typed',
                          {})

    test_directory = _create_folder('test', package_directory)
    _create_template_file('ament_python',
                          'test_copyright.py.em',
                          test_directory,
                          'test_copyright.py',
                          {})
    _create_template_file('ament_python',
                          'test_flake8.py.em',
                          test_directory,
                          'test_flake8.py',
                          {})
    _create_template_file('ament_python',
                          'test_mypy.py.em',
                          test_directory,
                          'test_mypy.py',
                          {})
    _create_template_file('ament_python',
                          'test_pep257.py.em',
                          test_directory,
                          'test_pep257.py',
                          {})
    _create_template_file('ament_python',
                          'test_xmllint.py.em',
                          test_directory,
                          'test_xmllint.py',
                          {})


def populate_python_node(package, source_directory, python_node_name):
    main_py_config = {
            'project_name': package.name
            }
    _create_template_file('ament_python',
                          'main.py.em',
                          source_directory,
                          python_node_name + '.py',
                          main_py_config)


def populate_python_libary(package, source_directory, python_library_name):
    library_directory = _create_folder(python_library_name, source_directory)
    _create_template_file('ament_python',
                          'init.py.em',
                          library_directory,
                          '__init__.py',
                          {})


def populate_cmake(package, package_directory, cpp_node_name, cpp_library_name):
    cmakelists_config = {
            'project_name': package.name,
            'dependencies': [str(dep) for dep in package.build_depends],
            'cpp_node_name': cpp_node_name,
            'cpp_library_name': cpp_library_name,
            }
    _create_template_file(
            'cmake',
            'CMakeLists.txt.em',
            package_directory,
            'CMakeLists.txt',
            cmakelists_config)

    cmake_config = {
            'project_name': package.name,
            'cpp_library_name': cpp_library_name,
            'cpp_node_name': cpp_node_name,
            }
    _create_template_file(
            'cmake',
            'Config.cmake.in.em',
            package_directory,
            package.name + 'Config.cmake.in',
            cmake_config)

    version_config = {
            'project_name': package.name,
            }
    _create_template_file(
            'cmake',
            'ConfigVersion.cmake.in.em',
            package_directory,
            package.name + 'ConfigVersion.cmake.in',
            version_config)


def populate_ament_cmake(package, package_directory, cpp_node_name, cpp_library_name):
    cmakelists_config = {
            'project_name': package.name,
            'dependencies': [str(dep) for dep in package.build_depends],
            'cpp_node_name': cpp_node_name,
            'cpp_library_name': cpp_library_name,
            }
    _create_template_file(
            'ament_cmake',
            'CMakeLists.txt.em',
            package_directory,
            'CMakeLists.txt',
            cmakelists_config)


def populate_cpp_node(package, source_directory, cpp_node_name):
    cpp_node_config = {
            'package_name': package.name,
            }
    _create_template_file(
            'cpp',
            'main.cpp.em',
            source_directory,
            cpp_node_name + '.cpp',
            cpp_node_config)


def populate_cpp_library(package, source_directory, include_directory, cpp_library_name):
    class_name = cpp_library_name.replace('_', ' ').title()
    class_name = ''.join(x for x in class_name if not x.isspace())
    cpp_header_config = {
            'package_name': package.name,
            'library_name': cpp_library_name,
            'class_name': class_name,
            }
    _create_template_file(
            'cpp',
            'header.hpp.em',
            include_directory,
            cpp_library_name + '.hpp',
            cpp_header_config)

    cpp_library_config = {
            'package_name': package.name,
            'library_name': cpp_library_name,
            'class_name': class_name
            }
    _create_template_file(
            'cpp',
            'library.cpp.em',
            source_directory,
            cpp_library_name + '.cpp',
            cpp_library_config)

    visibility_config = {
            'package_name': package.name.upper(),
            }
    _create_template_file(
            'cpp',
            'visibility_control.h.em',
            include_directory,
            'visibility_control.h',
            visibility_config)


def populate_ament_cargo(package, package_directory, cargo_library_name):
    cargo_toml_config = {
            'project_name': package.name,
            'dependencies': [str(dep) for dep in package.build_depends],
            'cargo_library_name': cargo_library_name,
            'maintainer_email': package.maintainers[0].email,
            'maintainer_name': package.maintainers[0].name,
            }
    _create_template_file(
            'ament_cargo',
            'Cargo.toml.em',
            package_directory,
            'Cargo.toml',
            cargo_toml_config)


def populate_rust_node(package, source_directory, node_name):
    if node_name is None:
        node_name = f'{package.name}_node'

    cargo_node_config = {
            'package_name': package.name,
            'node_name': node_name
            }

    _create_template_file(
            'rust',
            'main.rs.em',
            source_directory,
            'main.rs',
            cargo_node_config)
