#!/usr/bin/env python3

import os
import sys
import logging
import shutil


LOGGER = logging.getLogger()
LOGGER.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)8s: %(message)s')
handler.setFormatter(formatter)
LOGGER.addHandler(handler)


class DoxygenGenerator:
    def _construct_path(self, relative_to, *paths):
        if relative_to == "sources":
            return os.path.join(self.sources_dir, *paths)
        if relative_to == "binary":
            return os.path.join(self.working_dir, *paths)
        raise Exception("_path")

    def __init__(self, dir_sources, dir_binary, dir_tocopy):
        self.sources_dir = os.path.abspath(dir_sources)
        self.working_dir = os.path.abspath(dir_binary)
        self.dir_tocopy = dir_tocopy  # relative to sources
        self._check_valid()

        LOGGER.info("${CMAKE_SOURCES_DIR} = %s", self.sources_dir)
        LOGGER.info("working directory    = %s", self.working_dir)
        LOGGER.info("Module directories   = %s", ", ".join(self.dir_tocopy))

        LOGGER.info("Pruning contents of the working directory")
        for fs_obj in os.listdir(self.working_dir):
            to_delete = os.path.join(self.working_dir, fs_obj)
            shutil.rmtree(to_delete)

        if len(os.listdir(self.working_dir)) > 0:
            LOGGER.error("Pruning unsuccessful")
            raise Exception("something was left after the prune")

    def run(self):
        for tocopy in self.dir_tocopy:
            self._copy_single(tocopy)
            self._add_directives(tocopy)

    def _copy_single(self, directory):
        module_name = directory.split("/")[0]
        LOGGER.info("%s: Copying ${CMAKE_SOURCES_DIR}/%s -> CWD/%s", module_name, directory, directory)

        src = self._construct_path("sources", directory)
        dst = self._construct_path("binary",  directory)
        shutil.copytree(src, dst)

    def _add_directives(self, directory):
        module_name = directory.split("/")[0]
        LOGGER.info("%s: Rewriting sources in CWD/%s", module_name, directory)
        directory = self._construct_path("binary", directory)

        self._walk(directory, self._add_directive_callback, module_name=module_name)

    @staticmethod
    def _add_directive_callback(filepath, **kwargs):
        with open(filepath, 'r') as f:
            original_content = f.read()

        with open(filepath, 'w') as f:
            f.write("""
/** \\addtogroup %s
 *  @{
 */

""" % kwargs["module_name"])
            f.write(original_content)
            f.write("/** @}*/")

    def _walk(self, root, callback, **kwargs):
        for root, dirs, files in os.walk(root):
            # dirpath = root.split(os.sep)
            for file in files:
                filepath = os.path.join(root, file)
                LOGGER.debug("%s: Applying doxygen directives to %s", kwargs["module_name"], os.path.relpath(filepath, self.working_dir))
                callback(filepath, **kwargs)

    def _check_valid(self):
        EXCEPTION_MISSING = "Directory {} ({}) does not exist."
        EXCEPTION_SAME    = "Directories {} points to the same location ({})"

        if not os.path.isdir(self.sources_dir):
            raise Exception(EXCEPTION_MISSING.format("CMAKE_SOURCES_DIR", self.sources_dir))

        if not os.path.isdir(self.working_dir):
            raise Exception(EXCEPTION_MISSING.format("CMAKE_BINARY_DIR", self.working_dir))

        if self.sources_dir == self.working_dir:
            raise Exception(EXCEPTION_SAME.format(["CMAKE_BINARY_DIR", "CMAKE_SOURCES_DIR"], self.sources_dir))

        for path in self.dir_tocopy:
            if not os.path.isdir(os.path.join(self.sources_dir, path)):
                raise Exception(EXCEPTION_MISSING.format("INPUT_DIR", "$CMAKE_SOURCES_DIR/" + self.dir_tocopy))


if __name__ == '__main__':
    if len(sys.argv) != 4:
        sys.exit(1)

    dir_sources = sys.argv[1]
    dir_working = sys.argv[2]
    dir_tocopy = sys.argv[3].strip().split()

    DoxygenGenerator(dir_sources, dir_working, dir_tocopy).run()