#!/usr/bin/env python2

import os, glob, shutil, sys, subprocess, re
import xml.etree.ElementTree as ET
import json, socket
# The directory of the current SCRAM project.
prefix = os.getcwd() + os.sep

# Location of this python script. Useful because we store
# some data files in the same directory.
script_dir = os.path.dirname(os.path.realpath(__file__))


# Returns the given text without the given prefix.
def remove_str_refix(text, prefix):
    return text[len(prefix):] if text.startswith(prefix) else text

def remove_prefix(path):
    path = os.path.realpath(path)
    if path.startswith(prefix):
        return path[len(prefix):]
    print('NOT IN CMS' + path)
    assert False

# Given an XML node with a 'file' attribute and a base_directory
# in which the BuildFile.xml containing this node,
def get_files(node, base_dir):
    result = []
    files = [x.strip() for x in node.attrib['file'].split(',') ]
    # Fallback in case we use space delimiters in the file lists...
    if len(files) == 1:
        files = files[0].split(' ')

    for file in files:
        cwd_bak = os.path.realpath(os.getcwd())
        os.chdir(base_dir)
        result += glob.glob(file)
        os.chdir(cwd_bak)

    for child_node in node:
        if child_node.tag == 'flags':
            if 'SKIP_FILES' in child_node.attrib:
                to_remove = child_node.attrib['SKIP_FILES']
                if to_remove in result:
                    result.remove(to_remove)
    return result



# Abstract base class for anything that can be built by SCRAM.
class ScramTargetBase(object):
    def __init__(self):
        # Link to the ScramProject that contains this target
        self.project = None
        # Paths to the source files that should be compiled
        # to generate this target.
        self.source_files = []
        self.classes_h = []
        # Unique name of this target.
        self.name = None
        # CMake-friendly name that is alphanumeric with '_'
        self.symbol = None
        # Dependencies of this target. Identifier by name.
        # Mostly useful before linking when `dependencies` is
        # not ready.
        self.dependencies_by_name = set()
        # Dependencies of this target. Direct references
        # to the relevant dependencies. This is generated
        # from the `dependencies_by_name` field.
        self.dependencies = set()
        # True iff this target builts a executable. Otherwise
        # this target builds a shared library.
        self.is_executable = False
        # True iff this target isn't built by this SCRAM project.
        # (e.g. boost, root, etc.)
        self.external = False
        # True iff this executable is a test that should be executed.
        self.is_test = False
        self.libs = set()
        self.cxx_flags = set()
        self.test_runner_args = []
        self.pretest = ''
        self.defines = ''
        self.ld_flags = ''
        self.run_test = True
        self.edm_plugin = False
        self.needed_libs = set()
        self.include_dirs = set()
        self.finds = set()
        self.add_subdir = False
        self.dir = ''
        self.module = None
        self.forwards = set()
        self.was_linked = False
        self.root_dict = None
        
    def link_dependencies(self):
        for forward in self.forwards:
            forward=forward.lstrip().rstrip()
            try:
                dep_target = self.project.get_target(forward)
                self.dependencies.add(dep_target)
            except IOError as e:
                print('Warning: Dependency "' + forward + '" not found!')
        for dependency in self.dependencies_by_name:
            dependency=dependency.lstrip().rstrip()
            try:
                dep_target = self.project.get_target(dependency)
                self.dependencies.add(dep_target)
            except IOError as e:
                print('Warning: Dependency "' + dependency + '" not found!')
                
    def link(self):
        if self.was_linked:
            return
        self.was_linked = True
        for dependency in self.dependencies:
            dependency.link()
            self.include_dirs |= dependency.include_dirs
            self.needed_libs |= dependency.libs
            self.finds |= dependency.finds
            self.cxx_flags |= dependency.cxx_flags
            if self.is_virtual():
                self.libs |= dependency.libs




    def built_by_cmake(self):
        return not self.is_virtual() and self.has_dictionary() and not self.external


    def has_source(self):
        return len(self.source_files) !=0
    
    def is_virtual(self):
        return len(self.source_files) == 0

    def has_dictionary(self):
        return not len(self.classes_h) == 0


def get_lib_or_name_attr(node):
    if 'name' in node.attrib:
        return node.attrib['name']
    return node.attrib['lib']


class ScramModuleLibrary(ScramTargetBase):
    def __init__(self, node, base_dir):
        super(ScramModuleLibrary,self).__init__()
        self.dir = base_dir
        self.name = remove_prefix(base_dir)
        self.symbol = self.name.replace('/', '').replace('-', '')

        for child in node:
            if child.tag == 'use' or child.tag == 'lib':
                self.dependencies_by_name.add(get_lib_or_name_attr(child))
            if child.tag == 'flags':
                for key, value in child.attrib.items():
                    if 'CXXFLAGS' == key or 'cppflags' == key or 'CPPFLAGS' == key:
                        self.cxx_flags |= set(value.split())
                    elif 'CPPDEFINES' == key:
                        self.defines += ' -D' + value
                        self.defines = self.defines.strip()
                    elif 'LDFLAGS' == key:
                        self.ld_flags += ' ' + value
                        self.ld_flags = self.ld_flags.strip()
                    elif 'EDM_PLUGIN' == key:
                        if child.attrib['EDM_PLUGIN'] == '1':
                            self.edm_plugin = True
                    elif 'BIGOBJ_CXXFLAGS' == key:
                        pass
                    elif 'DROP_DEP' == key:
                        pass
                    elif 'RIVET_PLUGIN' == key:
                        pass
                    elif 'GENREFLEX_ARGS' == key:
                        pass
                    elif 'NO_LIB_CHECKING' == key:
                        pass
                    elif 'LCG_DICT_XML' == key:
                        pass
                    elif 'LCG_DICT_HEADER' == key:
                        pass
                    elif key.startswith('REM_'):
                      pass
                    elif 'ADD_SUBDIR' == key:
                        if value == '1':
                            self.add_subdir = True
                    else:
                        print('Unknown flag type: ' + str(child.attrib))
                    
        
        cwd_bak = os.path.realpath(os.getcwd())
        os.chdir(base_dir)
        base_glob = 'src/*'
        
        self.source_files = glob.glob(base_glob+'.cc')
        self.source_files += glob.glob(base_glob+'.cpp')
        self.source_files += glob.glob(base_glob+'.cxx')
        self.source_files += glob.glob(base_glob+'.c')
        self.source_files += glob.glob(base_glob+'.C')

        if self.add_subdir:
            base_glob = 'src/**/*'
            self.source_files += glob.glob(base_glob+'.cc')
            self.source_files += glob.glob(base_glob+'.cpp')
            self.source_files += glob.glob(base_glob+'.cxx')
            self.source_files += glob.glob(base_glob+'.c')
            self.source_files += glob.glob(base_glob+'.C')
        self.classes_h = glob.glob('src/classes*.h')

        os.chdir(cwd_bak)

        if self.has_source():
            self.libs.add(self.symbol)
         
        if self.has_dictionary():
            self.libs.add(self.symbol)

class ScramTarget(ScramTargetBase):
    def __init__(self, node, base_dir):
        super(ScramTarget,self).__init__()
        self.dir = base_dir
        self.source_files = get_files(node, base_dir)

        try:
            self.name = node.attrib['name']
        except KeyError as e:
            assert len(self.source_files) == 1
            self.name = self.source_files[0].split('.')[0]

        self.symbol = self.name.replace('/', '').replace('-', '')

        if node.tag == 'bin':
            self.is_executable = True
        else:
            self.libs.add(self.symbol)

        for child in node:
            if child.tag == 'use' or child.tag == 'lib':
                self.dependencies_by_name.add(get_lib_or_name_attr(child))
            if child.tag == 'flags':
                for key, value in child.attrib.items():
                    if 'TEST_RUNNER_ARGS' == key:
                        self.test_runner_args = value.split()
                    if 'PRE_TEST' == key:
                        self.pretest = value
                    if 'EDM_PLUGIN' == key:
                        self.edm_plugin=True
                    if 'NO_TESTRUN' == key:
                        self.run_test=False



class ScramModule():

    def get_targets_from(self, base_dir, node):
        result = []
        for child in node:
            if child.tag == 'bin' or child.tag == 'library':
                bin = ScramTarget(child, base_dir)
                result.append(bin)
            if child.tag == 'environment':
                result += self.get_targets_from(base_dir, child)
        return result

    def parse_directory(self, base_dir):
        result = []
        file_path = base_dir + 'BuildFile.xml'
        if os.path.isfile(file_path):
            xml = parse_BuildFileXml(file_path)
            result += self.get_targets_from(base_dir, xml)
        return result

    def __init__(self, name, base_dir, node):
        self.base_dir = base_dir
        assert(len(base_dir.split('/')) == 2)
        self.subsystem = base_dir.split('/')[0]
        self.package = base_dir.split('/')[1]
        self.name = self.base_dir.replace('/', '_')
        self.targets = []

        self.main_lib = ScramModuleLibrary(node, base_dir)
        self.targets = [self.main_lib]

        self.binaries = self.parse_directory(base_dir + os.sep + 'bin' + os.sep)
        self.tests = self.parse_directory(base_dir + os.sep + 'test' + os.sep)
        self.plugins = self.parse_directory(base_dir + os.sep + 'plugins' + os.sep)

        self.targets += self.binaries
        self.targets += self.tests
        self.targets += self.plugins

        for target in self.targets:
            target.module = self
            if target is not self.main_lib:
                target.dependencies.add(self.main_lib)

    def has_buildable_targets(self):
        for target in self.targets:
            if target.built_by_cmake():
                return True
        return False


class ScramProject:
    # Creates all external targets that SCRAM can depend on (e.g. root, geant4...)
    # They only serve the purpose that we can resolve dependencies...
    def init_builtin(self):
        builtin_json = open(os.path.dirname(os.path.realpath(__file__)) + os.sep + 'builtin.json')
        builtins = json.load(builtin_json)

        for key, value in builtins.items():
            m = ScramTargetBase()
            m.name = key
            m.target = key
            m.external = True
            if 'includes' in value:
                m.include_dirs |= set(value['includes'])

            if 'depends' in value:
                m.forwards |= set(value['depends'])

            if 'links' in value:
                m.libs |= set(value['links'])

            if 'find' in value:
                m.finds |= set(value['find'])

            if 'cxx_flags' in value:
                m.cxx_flags |= set(value['cxx_flags'])

            self.add_target(m)

    def __init__(self):
        # List of all modules in this project
        self.modules = []
        # All targets that can be built within this project (contains also externals targets
        # that aren't directly built such as root, geant4)
        self.targets = {}
        # Dictionary with the format "subsystem-name" -> [module1, module2]
        self.subsystems = {}
        # Create builtin targets
        self.init_builtin()

    def get_target(self, name):
        key = name.lstrip().rstrip()
        if key in self.targets:
            return self.targets[key]
        print('Could\'t find target: "' + name + '"')
        raise IOError()

    def add_module(self, module):
        module.project = self
        self.modules.append(module)

        if module.subsystem in self.subsystems:
            self.subsystems[module.subsystem].append(module)
        else:
            self.subsystems[module.subsystem] = [module]

        for target in module.targets:
            self.add_target(target)

    def add_target(self, target):
        assert isinstance(target, ScramTargetBase)
        target.project = self
        key = target.name.lstrip().rstrip()
        self.targets[key] = target

    # During parsing each target only knows other targets (e.g. dependencies)
    # by name. This will resolve all those names to actual references to those
    # targets.
    def resolve_dependencies(self):
        for target in self.targets.values():
            target.link_dependencies()
        for target in self.targets.values():
            target.link()

# Takes a path to a BuildFile.xml and transforms it into an XML node.
# Also does some preprocessing like handling global <use> tags...
def parse_BuildFileXml(path):
    f = open(path)
    try:
        data = ''
        for line in f:
            if not re.match('^#',line):
                data += line
        data = '<scram>' + data.strip() + '</scram>'
        root = ET.fromstring(data)
        # Manually copy all global <use> not inside a <bin>/<library> tag
        # to each of those tags in the current BuildFile.xml
        for topElement in root:
            if topElement.tag == 'use' or topElement.tag == 'flags' :
                for element in root:
                    if element.tag == 'library' or element.tag == 'bin':
                        element.append(topElement)
        # Deal with the case where <use> is wrapped in <environment> at the top level
            if topElement.tag == 'environment':
                used=topElement.find('use')
                if used is not None:
                    for element in root:
                        if element.tag == 'library' or element.tag == 'bin':
                            element.append(used)
        f.close()
        return root
        

    except Exception as e:
        print('error in  ' + path + ': ' + str(e))
        f.close()
        return None


# Given the specific module root directory (e.g. '~/CERN/cmssw/FWCore/Version') and the full path
# to a BuildFile.xml inside this directory (e.g. '~/CERN/cmssw/FWCore/Version/BuildFile.xml'),
# this function will configure a ScramModule.
def handle_BuildFileXml(root, path):
    rel_path = remove_prefix(root)
    node = parse_BuildFileXml(path)
    return ScramModule(rel_path, root, node)

# Generates CMake files that represent the given ScramProject.
class CMakeGenerator:

    def __init__(self, project):
        self.project = project

    # Writes the necessary CMake commands to generate the given target
    # to the given out stream (which needs to support a 'write' call).
    def generate_target(self, target, out):
        if re.search('buildfile2cmake',target.dir):
            return
        if target.has_source():
            if target.has_dictionary():
                out.write('include_directories(${CMAKE_SOURCE_DIR}\n')
                out.write('\n'.join(sorted(target.include_dirs)))
                out.write('\n)\n\n')
                for clh in sorted(target.classes_h) :
                    cl = clh.replace('.h','')
                    part = cl.replace('src/classes','')
                    cldef = 'src/classes_def'+part+'.xml'
                    name=target.symbol+part
                    out.write('cms_rootdict('+target.symbol+' '+clh+' '+cldef+')\n\n')
                out.write('add_rootdict_rules('+target.symbol+')\n\n')
            if target.is_executable:
                if len(target.test_runner_args) >= 0:
                    out.write('add_executable(')
                    out.write(target.symbol)
                    for source in sorted(target.source_files):
                        out.write('\n  ' + source)
                    out.write('\n)\n\n')
            else:
                out.write('add_library(')
                out.write(target.symbol)
                out.write(' SHARED ${'+target.symbol+'_EXTRA_SOURCES}')
                for source in sorted(target.source_files):
                    out.write('\n  ' + source)
                out.write('\n)\n\n')

            if re.search('test', target.dir):
                if target.is_executable:
                    if len(target.test_runner_args) == 3:
                        out.write('add_test(NAME %s_CTest COMMAND %s -x ${CMAKE_CURRENT_SOURCE_DIR}/%s)\n\n' % (target.symbol, target.test_runner_args[0], target.test_runner_args[2] ) )
                        out.write('add_test_properties(%s_CTest)\n\n' % target.symbol)
                        if not target.pretest == '':
                            out.write('set_tests_properties(%s_CTest PROPERTIES DEPENDS %s)\n\n' % (target.symbol, target.pretest))
                    if len(target.test_runner_args) > 3:
                        for i in range(2,len(target.test_runner_args)):
                            out.write('add_test(NAME %s_%s_CTest COMMAND %s -x ${CMAKE_CURRENT_SOURCE_DIR}/%s)\n\n'%(target.symbol,i,target.test_runner_args[0], target.test_runner_args[i] ))
                            out.write('add_test_properties(%s_%s_CTest)\n\n' % (target.symbol,i))


                    if len(target.test_runner_args) == 0:
                        if target.run_test:
                            out.write('add_test(NAME '+target.symbol+'_CTest COMMAND '+target.symbol+')\n\n')
                            out.write('add_test_properties('+target.symbol+'_CTest)\n\n')
                    out.write('set_target_properties(%s PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/test)\n\n'%target.symbol)
                    out.write('add_dependencies( ALL_TESTBINS '+target.symbol+')\n\n')
                    out.write('add_dependencies( '+target.symbol+' ALL_PLUGINS)\n\n')
                    if target.symbol == 'standAloneWithMessageLogger':
                        out.write('target_compile_definitions( %s PUBLIC EDM_ML_DEBUG)\n\n' % target.symbol)
                    if target.symbol == 'testPyDevParameterSet' or target.symbol == 'testPyBind11ParameterSet':
                        out.write('add_dependencies( %s file_in_path )\n\n' % target.symbol)
                else:
                    out.write('set_target_properties(')
                    out.write(target.symbol)
                    out.write(' PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\n\n')
                    out.write('add_dependencies( ALL_TESTLIBS '+target.symbol+')\n\n')
            else:
                if target.is_executable:
                    out.write('set_target_properties( ')
                    out.write(target.symbol)
                    out.write(' PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)\n\n')
                    out.write('add_dependencies( ALL_BINS '+target.symbol+')\n\n')
                else:
                    out.write('set_target_properties(')
                    out.write(target.symbol)
                    out.write(' PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\n\n')
                    out.write('add_dependencies( ALL_LIBS '+target.symbol+')\n\n')
            if re.search('plugin', target.dir) or target.edm_plugin:
                if not target.is_executable:
                    out.write('set_target_properties(')
                    out.write(target.symbol)
                    out.write(' PROPERTIES PREFIX "plugin")\n\n')
                    out.write('add_dependencies( ALL_PLUGINS '+target.symbol+')\n\n')
                    tdir=str.join('/', target.dir.split('/')[:2])
                    out.write('edm_write_config( '+target.symbol+' '+tdir+')\n\n')
            if len(target.test_runner_args) >= 0:
                out.write('target_include_directories('+target.symbol+' PRIVATE ${CMAKE_SOURCE_DIR})\n\n')
                if len(target.include_dirs) > 0:
                    out.write('target_include_directories(' + target.symbol + ' PUBLIC\n')
                    out.write('\n'.join(sorted(target.include_dirs)))
                    out.write('\n)\n\n')
                
                if target.cxx_flags:
                    out.write('add_compile_options('+'"'+' '.join(sorted(target.cxx_flags))+'"' + ')\n\n')

                if len(target.ld_flags.strip()) != 0:
                    out.write('# Manually defined LD_FLAGS\n')
                    out.write('# target_xcompile_options(' + target.symbol + 
                              ' ' + target.ld_flags + ')\n\n')

                if len(target.needed_libs) != 0:
                    out.write('target_link_libraries(' + target.symbol + ' PUBLIC\n')
                    out.write('\n'.join(sorted(target.needed_libs)))
                    out.write('\n)\n\n')
                out.write('\n')

                if re.search('test', target.dir) :
                    if not target.is_executable:
                        if target.edm_plugin:
                            out.write('add_python_rules('+target.dir+')\n\n')
                            out.write('add_script_rules('+target.dir+')\n\n')
                        out.write('install( TARGETS ') 
                        out.write(target.symbol)
                        out.write(' DESTINATION ${CMAKE_INSTALL_PREFIX}/')
                        out.write('${CMAKE_INSTALL_LIBDIR}')
                        out.write(' EXPORT stitched::')
                        out.write(')\n\n')
                else:
                    out.write('add_python_rules('+target.dir+')\n\n')
                    out.write('add_script_rules('+target.dir+')\n\n')
                    out.write('install( TARGETS ') 
                    out.write(target.symbol)
                    out.write(' DESTINATION ${CMAKE_INSTALL_PREFIX}/')
                    if target.is_executable:
                        out.write('${CMAKE_INSTALL_BINDIR} ')
                    else:
                        out.write('${CMAKE_INSTALL_LIBDIR}')
                    out.write(' EXPORT stitched::')
                    out.write(')\n\n')
        else:
            if target.has_dictionary():
                out.write('include_directories(${CMAKE_SOURCE_DIR}\n')
                out.write('\n'.join(sorted(target.include_dirs)))
                out.write('\n)\n\n')
                for clh in sorted(target.classes_h) :
                    cl = clh.replace('.h','')
                    part = cl.replace('src/classes','')
                    cldef = 'src/classes_def'+part+'.xml'
                    name=target.symbol+part
                    out.write('cms_rootdict('+target.symbol+' '+clh+' '+cldef+')\n\n')
                out.write('add_rootdict_rules('+target.symbol+')\n\n')
                out.write('add_library('+target.symbol+' SHARED ${'+target.symbol+'_EXTRA_SOURCES})\n\n')
                out.write('set_target_properties(')
                out.write(target.symbol)
                out.write(' PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\n\n')
                out.write(' add_dependencies( ALL_LIBS '+target.symbol+')\n\n')
                out.write('install( TARGETS ') 
                out.write(target.symbol)
                out.write(' DESTINATION ${CMAKE_INSTALL_PREFIX}/')
                out.write('${CMAKE_INSTALL_LIBDIR}')
                out.write(' EXPORT stitched::')
                out.write(')\n\n')
 
    # Generates the CMakeLists.txt for a given target. Note: This function APPENDS to an
    # existing CMakeLists.txt, because multiple targets are each written by their own
    # `handle_target` call to the same CMakeLists.txt.
    def handle_target(self, target):
        output_path = target.dir + os.sep + 'CMakeLists.txt'
        output_file = open(output_path, 'a')
        self.generate_target(target, output_file)
        output_file.close()


    # Genereates the CMakeLists.txt for a given module (e.g. `FWCore/Version/CMakeLists.txt`
    def handle_module(self, module):
        output_path = module.base_dir + os.sep + 'CMakeLists.txt'

        if os.path.isfile(output_path):
            return False

        output_file = open(output_path, 'w')

        if len(module.binaries) != 0:
            output_file.write('add_subdirectory(bin)\n')
        if len(module.tests) != 0:
            output_file.write('if(BUILDTEST)\n  add_subdirectory(test)\nendif()\n\n')
        if len(module.plugins) != 0:
            output_file.write('add_subdirectory(plugins)\n\n')

        output_file.close()
        return True

    def handle_subsystem(self, subsystem, subsystem_modules):
        subsystem_cmake = open(subsystem + os.sep + 'CMakeLists.txt', 'w')
        for module in sorted(subsystem_modules):
            # FIXME: Another StaticAnalyzer check that is just an ugly hack...
            if module.package == 'StaticAnalyzers':
                continue
            subsystem_cmake.write('add_subdirectory(' + module.package + ')\n\n')

        subsystem_cmake.write('\n\n')
        subsystem_cmake.write('set(pythondir ${CMAKE_BINARY_DIR}/python/%s)\n\n' % subsystem)
        subsystem_cmake.write('file(MAKE_DIRECTORY ${pythondir})\n\n')
        #subsystem_cmake.write('file(WRITE  '${pythondir}/__init__.py' '#autogenerated\n__path__.append(\'${pythondir}\')\n')\n')
        subsystem_cmake.write('file(WRITE  "${pythondir}/__init__.py" "#autogenerated\nimport os\n__path__.append(os.path.dirname(os.path.realpath(__file__)))")\n')
 
        has_buildable_targets = False
        for module in subsystem_modules:
            for target in module.targets:
                if target.built_by_cmake():
                    has_buildable_targets = True
                    break

        if has_buildable_targets:
            subsystem_cmake.write('# Meta-target that builds everything in this subsystem\n')
            subsystem_cmake.write('add_custom_target(' + subsystem + '_all)\n\n')
            subsystem_cmake.write('add_dependencies(' + subsystem + '_all')
            for module in sorted(subsystem_modules):
                for target in sorted(module.targets):
                    # FIXME: The StaticAnalyzer check is just an ugly hack...
                    if target.built_by_cmake() and target.symbol != 'UtilitiesStaticAnalyzers':
                        subsystem_cmake.write('\n  ' + target.symbol)

            subsystem_cmake.write('\n)\n')

        subsystem_cmake.close()

    # Generates the top-level CMakeLists.txt
    def gen_top_level(self):

        output_path = 'CMakeLists.txt'
        output_file = open(output_path, 'w')
        preamble="""
cmake_minimum_required(VERSION 3.12)
project(Framework VERSION 0.0.0 LANGUAGES C CXX)

if(BUILDTEST)
  include(CTest)
  enable_testing()
else()
  message('set BUILDTEST=True to run tests')
endif()
cmake_policy( SET CMP0082 NEW)
find_package(ROOT)
include(GNUInstallDirs)
set(CMAKE_INSTALL_LIBDIR "lib")
include(${ROOT_USE_FILE})
set(Boost_NO_BOOST_CMAKE ON)
set(Boost_NO_SYSTEM_PATHS ON)
find_package(Boost COMPONENTS filesystem thread iostreams regex serialization system program_options)
add_compile_definitions(BOOST_SPIRIT_THREADSAFE;PHOENIX_THREADSAFE;BOOST_MATH_DISABLE_STD_FPCLASSIFY;BOOST_UUID_RANDOM_PROVIDER_FORCE_POSIX;TBB_USE_GLIBCXX_VERSION=80400;TBB_SUPPRESS_DEPRECATED_MESSAGES)
find_package(Python COMPONENTS Development)
set(Python_INCLUDE_DIR ${Python_INCLUDE_DIRS})
set(Python_LIBRARY Python::Python)
find_package(Threads)
find_package(CMakeTools)
UseCMakeTools()
include(GoogleTest)
add_custom_target(ALL_LIBS ALL)
add_custom_target(ALL_DICTS ALL)
add_dependencies(ALL_LIBS ALL_DICTS)
add_custom_target(ALL_PLUGINS ALL)
add_dependencies(ALL_LIBS ALL_PLUGINS)
add_custom_target(ALL_TESTLIBS ALL)
add_dependencies(ALL_LIBS ALL_TESTLIBS)
add_custom_target(ALL_BINS ALL)
add_custom_target(ALL_TESTBINS ALL)
add_dependencies(ALL_BINS ALL_LIBS)
add_dependencies(ALL_TESTBINS ALL_LIBS)
"""
        output_file.write(preamble)

        preinstall="""
set(testdir ${CMAKE_BINARY_DIR}/test)
set(pythondir ${CMAKE_BINARY_DIR}/python)
set(sourcedir ${CMAKE_BINARY_DIR}/src)
set(pcmfiles "")
set(rootmapfiles "")
file(MAKE_DIRECTORY ${testdir} )
file(MAKE_DIRECTORY ${sourcedir})
file(GLOB sources "${CMAKE_SOURCE_DIR}/*")
file(COPY ${sources} DESTINATION ${sourcedir} PATTERN ".git" EXCLUDE)
add_custom_target(file_in_path
COMMAND mkdir -p "${CMAKE_BINARY_DIR}/test/Geometry/TrackerSimData/data"
COMMAND touch  "${CMAKE_BINARY_DIR}/test/Geometry/TrackerSimData/data/trackersens.xml"
COMMAND touch "${CMAKE_BINARY_DIR}/test/tmp.py")
file(MAKE_DIRECTORY ${pythondir})
file(WRITE  "${pythondir}/__init__.py" "#autogenerated")
"""
        output_file.write(preinstall)

        include_paths = set()
        finds = set()

        for module in self.project.modules:
            for target in module.targets:
                include_paths |= target.include_dirs
                finds |= target.finds

        for f in sorted(finds):
            output_file.write('find_package('+f+')\n\n')
        output_file.write('set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")\n\n')
 

        macros="""
 
macro(cms_rootdict name)
  if("${ARGN}" STREQUAL "")
    set(classes_h classes.h)
    set(classes_xml classes_def.xml)
    set(${name}_ROOTDICT ${name}_x)
  else()
    set(classes_h ${ARGV1})
    set(classes_xml ${ARGV2})
    if(${name}_ROOTDICT)
      MATH(EXPR ${name}_DICT_COUNT "${${name}_DICT_COUNT}+1")
      set(${name}_ROOTDICT ${name}_x${${name}_DICT_COUNT})
    else()
      set(${name}_DICT_COUNT 0)
      set(${name}_ROOTDICT ${name}_x)
    endif()
  endif()
  set(${name}_EXTRA_SOURCES ${${name}_EXTRA_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/${${name}_ROOTDICT}.cc)
  set(${name}_ROOTDICTS ${${name}_ROOTDICTS} ${${name}_ROOTDICT})
  set(ROOTDICT_${${name}_ROOTDICT} ${classes_h} ${classes_xml} ${CMAKE_CURRENT_BINARY_DIR}/${${name}_ROOTDICT}.cc)
  unset(clases_h)
  unset(classes_xml)
endmacro()

macro(add_rootdict_rules name)
  set(flags "")
  get_directory_property(defs COMPILE_DEFINITIONS)
  if(defs)
    foreach(d ${defs})
      set(flags ${flags} -D${d})
    endforeach()
  endif()
  get_directory_property(dirs INCLUDE_DIRECTORIES)
  if(dirs)
    foreach(inc ${dirs})
      set(flags ${flags} -I${inc})
    endforeach()
  endif()
  foreach(dict ${${name}_ROOTDICTS})
    list(GET ROOTDICT_${dict} 0 classes_h)
    list(GET ROOTDICT_${dict} 1 classes_xml)
    list(GET ROOTDICT_${dict} 2 classes_cc)
    get_filename_component(classname ${classes_h} NAME_WE)
    set_source_files_properties(${classes_cc} PROPERTIES COMPILE_FLAGS "-Os -Wno-unused-variable")
    add_custom_command(OUTPUT ${classes_cc}
                    COMMAND ${ROOT_genreflex_CMD} ${CMAKE_CURRENT_SOURCE_DIR}/${classes_h}
                    -s ${CMAKE_CURRENT_SOURCE_DIR}/${classes_xml}
                    -o ${classes_cc}
                    --deep
                    --rootmap=${CMAKE_BINARY_DIR}/lib/${dict}.rootmap
                    --rootmap-lib=${CMAKE_BINARY_DIR}/lib/lib${name}.so
                    --library ${CMAKE_BINARY_DIR}/lib/lib${name}.so --multiDict
                    -DCMS_DICT_IMPL -D_REENTRANT -DGNUSOURCE -D__STRICT_ANSI__ ${FLAGS} -DCMSSW_REFLEX_DICT 
                    -I${CMAKE_SOURCE_DIR} ${flags}
                    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${classes_h} ${CMAKE_CURRENT_SOURCE_DIR}/${classes_xml}
                    )
    list(APPEND rootmapfiles ${CMAKE_BINARY_DIR}/lib/${dict}.rootmap)
    list(APPEND pcmfiles ${CMAKE_BINARY_DIR}/lib/lib${dict}_${dict}_x_rdict.pcm)
  endforeach()
  unset(clases_h)
  unset(classes_xml)
  unset(classname)
  unset(FLAGS)
endmacro()

add_custom_command(TARGET ALL_LIBS POST_BUILD
  COMMAND $<TARGET_FILE:edmPluginRefresh> "plugin*.so" 
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/lib
  DEPENDS edmPluginRefresh
  BYPRODUCTS ${CMAKE_BINARY_DIR}/lib/.edmplugincache
)

macro(add_script_rules target)
  file(GLOB scripts ${CMAKE_CURRENT_SOURCE_DIR}/scripts/*)
  if(scripts)
      foreach(script ${scripts})
          file(COPY ${script} DESTINATION ${CMAKE_BINARY_DIR}/bin)
          install(FILES ${script}  DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
      endforeach()
  endif()
  unset(scripts)
endmacro()


macro(edm_write_config target dir)
  set(cfidir ${CMAKE_BINARY_DIR}/cfipython/${dir})
  file(MAKE_DIRECTORY ${cfidir})
  add_custom_command( TARGET ${target} POST_BUILD
                    COMMAND $<TARGET_FILE:edmWriteConfigs> -p $<TARGET_FILE:${target}>
                    WORKING_DIRECTORY ${cfidir}
                    DEPENDS edmWriteConfigs ${target}
                    )
  unset(cfidir)
endmacro()

macro(edm_register_plugin target dir)
endmacro()

macro(add_python_rules target)
  set(pythonsrcdir ${sourcedir}/${target}/python)
  file(GLOB pyfiles ${pythonsrcdir}/*.py)
  file(GLOB tpyfiles ${pythonsrcdir}/test/*.py)
  set(pythondir ${CMAKE_BINARY_DIR}/python/${target})
  get_filename_component(pdir ${pythondir} DIRECTORY)
  if(pyfiles OR tpyfiles)
      file(MAKE_DIRECTORY ${pdir})
      file(RELATIVE_PATH reldir ${pdir} ${pythonsrcdir} )
      file(CREATE_LINK ${reldir}  ${pythondir} SYMBOLIC)
      set(dnames "")
      foreach(file ${tpyfiles})
          get_filename_component(dname ${file} DIRECTORY)
          list(APPEND dnames ${dname})
      endforeach()
      if(dnames)
          list(REMOVE_DUPLICATES dnames)
          foreach(dir ${dnames})
              if(NOT EXISTS "${dir}/__init__.py")
                  file(WRITE "${dir}/__init__.py" "#autogenerated by add_python_rules")
              endif()
          endforeach()
      endif()
      if(NOT EXISTS "${pythondir}/__init__.py")
          file(WRITE "${pythondir}/__init__.py" "#autogenerated by add_python_rules\nimport os\n__path__.append(os.path.dirname(os.path.realpath(__file__)).rsplit(\'/src/${target}\',1)[0]+\'/cfipython/${target}\')\n")
      endif()
  endif()
  unset(pythonsrcdir)
  unset(pyfiles)
  unset(tpyfiles)
  unset(pythondir)
endmacro()

macro(add_test_properties target)
set_tests_properties(${target} PROPERTIES TIMEOUT 600 )
set_tests_properties(${target} PROPERTIES WORKING_DICRECTORY ${CMAKE_BINARY_DIR}/test/${target})
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/test/${target})
set_tests_properties(${target}
PROPERTIES ENVIRONMENT "LOCALRT=${CMAKE_BINARY_DIR};ROOT_INCLUDE_PATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}:$ENV{ROOTSYS}/include:$ENV{SPACK_INCLUDE_DIRS}:$ENV{ROOT_INCLUDE_PATH};CMSSW_BASE=${CMAKE_BINARY_DIR};CMSSW_RELEASE_BASE=${CMAKE_BINARY_DIR};CMSSW_SEARCH_PATH=${CMAKE_BINARY_DIR}/lib:${CMAKE_BINARY_DIR}/test:${CMAKE_BINARY_DIR}/src;PYTHONPATH=${CMAKE_BINARY_DIR}/python:${CMAKE_BINARY_DIR}/cfipython:${CMAKE_BINARY_DIR}/lib:${CMAKE_BINARY_DIR}/test:$ENV{PYTHONPATH};PATH=${CMAKE_BINARY_DIR}/test:${CMAKE_BINARY_DIR}/bin:$ENV{PATH};LD_LIBRARY_PATH=${CMAKE_CURRENT_BINARY_DIR}:${CMAKE_BINARY_DIR}/lib:${CMAKE_BINARY_DIR}/test:$ENV{SPACK_LINK_DIRS}:$ENV{LD_LIBRARY_PATH};LOCAL_TMP_DIR=${CMAKE_BINARY_DIR}/test/${target};LOCAL_TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
endmacro()

"""
        output_file.write(macros)

        subsystem_list = []
        for subsystem in self.project.subsystems:
            subsystem_list.append(subsystem)
        subsystem_list.sort()


        for subsystem in sorted(subsystem_list):
            output_file.write('add_subdirectory(' + subsystem + ')\n')
            subsystem_modules = self.project.subsystems[subsystem]
            self.handle_subsystem(subsystem, subsystem_modules)


        module_groups = {}

        for module in self.project.modules:
            if not module.has_buildable_targets():
                continue

            group_name = module.name.split('_')[0]

            if module.name in module_groups:
                module_groups[group_name].append(module)
            else:
                module_groups[group_name] = [module]
 

        
        postamble="""
install(DIRECTORY ${CMAKE_BINARY_DIR}/src DESTINATION ${CMAKE_INSTALL_PREFIX})
install(DIRECTORY ${CMAKE_BINARY_DIR}/python DESTINATION ${CMAKE_INSTALL_PREFIX})
install(DIRECTORY ${CMAKE_BINARY_DIR}/cfipython DESTINATION ${CMAKE_INSTALL_PREFIX})
install(FILES ${pcmfiles} ${rootmapfiles} ${CMAKE_BINARY_DIR}/lib/.edmplugincache DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
"""
        output_file.write(postamble)

        output_file.close()


    def get_headers(self, path):
        result = set()
        result |= set(glob.glob(path + '**/*.h'))
        result |= set(glob.glob(path + '**/*.hh'))
        result |= set(glob.glob(path + '**/*.hpp'))
        result |= set(glob.glob(path + '**/*.icc'))
        result |= set(glob.glob(path + '**/*.inc'))

        for s in list(result):
            if s.endswith('/classes.h'):
                result.remove(s)
                continue
            if s.endswith('/headers.h'):
                result.remove(s)
                continue

            for ignored in ignored_headers:
                if s.endswith(ignored):
                    result.remove(s)
                    break

        result = list(result)
        result.sort()
        
        return result

    def is_obsolete(self, path):
        try:
            content = open(path).read()
            if '#error' in content and not '#if' in content:
                print('Obsolete header ignored: ' + path)
                return True
        except UnicodeDecodeError:
            print('Failed to read header: ' + path)
            return False
        return False


    def gen(self):
        for module in sorted(self.project.modules):
            if self.handle_module(module):
                for target in module.targets:
                    self.handle_target(target)
        self.gen_top_level()


def main():
    #make_dicts()

    # Create an empty ScramProject
    project = ScramProject()

    for root, dirs, files in os.walk('.'):
        r = remove_str_refix(root, './')
        if len(r.split('/')) == 2 and not r.startswith('.git') and not r.startswith('cmaketools') and not r.startswith('buildfile2cmake'):
            if not 'BuildFile.xml' in os.listdir(r):
                with open('%s/BuildFile.xml'%r, 'w') as f:
                    data = '<export><lib name="1"/></export>\n'

                    f.write(data)
        if r == 'IOPool/TFileAdaptor/plugins':
            data=''
            with open('%s/BuildFile.xml' % root , 'r') as g:
                data=g.read()
            with open('%s/BuildFile.xml' % root , 'w') as f:
                data += """
<library   file="module.cc" name="IOPoolTFileAdaptorPlugins">
  <flags   EDM_PLUGIN="1"/>
</library>
"""
                f.write(data)
        if r == 'Utilities/General/test':
            data=''
            with open('%s/BuildFile.xml' % r , 'r') as g:
                data=g.read()
            with open('%s/BuildFile.xml' % r , 'w') as f:
                data = re.sub('\&\&','&amp;&amp;', data)
                f.write(data)


    # traverse root directory, and list directories as dirs and files as files
    for root, dirs, files in os.walk('.'):
        for file in files:
            root = remove_str_refix(root, './')
            if len(root.split('/')) != 2:
              continue
            if root == 'Utilities/StaticAnalyzers':
              continue
            if root == 'buildfile2cmake/.git':
              continue
            if file == 'BuildFile.xml':
                path = os.path.join(root, file)
                m = handle_BuildFileXml(root, path)
                if m:
                    project.add_module(m)


    project.resolve_dependencies()
    

    generator = CMakeGenerator(project)
    generator.gen()

if __name__ == '__main__':
    main()



