#!/usr/bin/env python

import os
import platform
import sys

cmakelists_str = \
"""# This file is automatically generated by running
#
# generate-cmakelists
#
# Require CMake 2.8
cmake_minimum_required(VERSION 2.8)
set(PROJECT_NAME %(project_name)s)
project(${PROJECT_NAME})
# Set verbose output while testing CMake
#set(CMAKE_VERBOSE_MAKEFILE 1)
#
# Get 3rdparty configuration data (<3rdParty>Config.cmake must be in PROJECT_CMAKE_CONFIG_PATH)
# edit line below to give cmake a hint where to find a <3rdParty>Config.cmake in case of manually built
# and not properly installed 3rdParty Package
#set(<3rdParty>_DIR "/path/to/<3rdParty>Config.cmake")
#example:
#find_package(PCL 1.3 REQUIRED COMPONENTS <component_1> <component_2>)
#include_directories(${<3rdParty>_INCLUDE_DIRS})
#
# Default build type (can be overridden by user)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
        "Choose the type of build, options are: Debug MinSizeRel Release RelWithDebInfo." FORCE)
endif()
#
# Compiler definitions from main project
#
add_definitions(${PROJECT_CXX_DEFINITIONS})
#
# Add special compiler flags
#
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PROJECT_CXX_FLAGS}")

#
#list all dependent files
#
#set(SRCS src_files)
%(source_files)s
#
#
# uncomment strings bellow if you want to generate subgroups\projects
#SOURCE_GROUP(${PROJECT_NAME} ${SRCS})
#
# you may need to build plain c files with g++, example - xerces
# if it is not the common case - comment this string
#set_source_files_properties(${SRCS} PROPERTIES LANGUAGE CXX )
#
# Include directories
#
include_directories(${PROJECT_INCLUDE_DIRS})
%(includes)s
#
# Executable
#
%(executables)s
#
# shared lib
#
%(library)s
#
# static libs
#
%(library_static)s
#
# Target libraries
#
%(target_libraries)s
set(TARGET_LOCATION ${EXECUTABLE_OUTPUT_PATH})
%(precompiled_headers)s
"""

#
#		Some global defs, yeah, yeah, I also not a big fan of it
#

start_label = 'module_list_start'
end_label = 'module_list_end'

# add more suffixes if necessary
src_suffix = ('.c', '.cpp', '.cxx')
inc_suffix = ('.h', '.hpp')

# if we find main.* file treat this subproject's target as executable
# otherwise - create static\shared libraries
main_file_names = ('main.')
exclude_dirs = ['build', 'CMakeFiles']

add_exec = "add_executable(%s %s)"
add_lib = "add_library(%s SHARED %s)"
add_lib_static = "add_library(%s STATIC %s)"
add_includes = "include_directories(SYSTEM %s)"
target_link_libraries_str = "target_link_libraries(%s ${PROJECT_LIBRARIES} ${PROJECT_3RD_PARTY_LIBRARIES})"

if platform.system() == "Windows":  # FIXME - re-factor it!!!
    add_precompiled_header_support = "SET_SOURCE_FILES_PROPERTIES(${PCH_SRC_CPP} PROPERTIES COMPILE_FLAGS \"${PCH_SRC_CMPL_FLAG} /Yc\\\"${PCH_SRC_H}\\\"\")"
else:
    add_precompiled_header_support = " ADD_PRECOMPILED_HEADER ( ${PROJECT_NAME} ${PCH_SRC} )"

should_generate_precompiled_headers = True


# TODO: describe in\out params
def get_all_subfolders_by_suffix(some_suffix, exclude_dirs):
    cur_dir = os.getcwd()

#    out_subprj_lst = set ( os.path.relpath(folder) for folder, subfolders, files in os.walk(cur_dir,topdown = True ) for file_ in files if os.path.splitext(file_)[1].endswith ( some_suffix ) )
#   return out_subprj_lst

    res_folders = []
    for root, dirs, files in os.walk(cur_dir, topdown=True):
        dirs[:] = [d for d in dirs if d not in exclude_dirs and not d.startswith('.')]
        for d in dirs:
            for name in os.listdir(os.path.join(root, d)):
                if name.endswith(some_suffix):
                    new_entry = os.path.normpath(os.path.join(root, d))
                    if platform.system() == "Windows":  # FIXME - re-factor it!!!
                        new_entry = new_entry.replace("\\", "/")
                    res_folders.append(new_entry)
                    break       # proceed to next subfolder

    return res_folders


def get_all_files_by_suffix(current_target_dir, exclude_dirs, src_suffix):
    src_group = dict()

    dir_list = set(os.path.normpath(folder) for folder, subfolders, files in os.walk(current_target_dir, topdown=True) for file_ in files if os.path.splitext(file_)[1].endswith(src_suffix))

    for dir in dir_list:
        should_add_group = False
        current_target_src = []
        for name in os.listdir(dir):
            if name.endswith(src_suffix):
                new_entry_folder = os.path.relpath(dir, current_target_dir)		# NOTE - order is important
                new_entry_file = os.path.normpath(os.path.join(new_entry_folder, name))
                if platform.system() == "Windows":  # FIXME - re-factor it!!!
                    new_entry_file = new_entry_file.replace("\\", "/")
                current_target_src.append(new_entry_file)
                should_add_group = True
            if should_add_group:
                src_group[os.path.relpath(dir, current_target_dir)] = current_target_src

    return src_group


def get_index_of_substr(list_for_search, substr):
    for i, s in enumerate(list_for_search):
        if substr in s:
            return i
    return -1


def clean_previous_subdir_entry():
    # erase all previous entries for add_subdirectory in order to generate them explicitly from the scratch
    writing = True
    with open('CMakeLists.txt') as f:
        with open('CMakeLists_new.txt', 'a+') as out:
            for line in f:
                if writing:
                    if start_label in line:
                        writing = False
                        out.write(line)
                        buffer = [line]
                    else:
                        out.write(line)
                elif end_label in line:
                    out.write(line)
                    writing = True
                else:
                    buffer.append(line)
            else:
                if not writing:
                    # There wasn't a closing "I'm in hell", so write buffer contents
                    out.writelines(buffer)

    os.remove('CMakeLists.txt')
    os.rename('CMakeLists_new.txt', 'CMakeLists.txt')

# idea from:
# https://github.com/alogg/dolfin/blob/master/cmake/scripts/generate-cmakefiles
# TODO: describe in\out params
def generate_cmake_files(in_sub_dir_list, in_main_file_names, in_src_suffix, in_inc_dir):

    clean_previous_subdir_entry()

    sub_projects = []

    root = os.getcwd()

    for cur_sub_dir in in_sub_dir_list:

        src_files = []
        full_path = cur_sub_dir

        root_name = os.path.split(root)[-1]
        start_pos = cur_sub_dir.find(root_name)
        subproject_name = cur_sub_dir[start_pos:]
        project_name = subproject_name.replace('/', '-')

        name_forms = dict(
                project_name=project_name,
                source_files="#SRCS - NOT SET",
                executables="#executables - NOT_SET",
                target_libraries="#target_libraries - NOT_SET",
                library="#library - NOT_SET",
                library_static="#library_static - NOT_SET",
                includes="#include - NOT_SET",
                precompiled_headers="#NOT_SET")

        # FIXME - code duplication >_M
        for f in os.listdir(full_path):
            filename, extension = os.path.splitext(f)
            if extension in in_src_suffix:
                full_file_name = os.path.join(full_path, f)
                if platform.system() == "Windows":
                    full_file_name = full_file_name.replace("\\", "/")
                src_files.append(full_file_name)
            # If no src files - continue
            if not src_files:
                continue

        # set(SRCS %(src_files)s)
        if should_generate_precompiled_headers:
            name_forms["source_files"] = "set(SRCS ${PCH_SRC} " + "\n".join(src_files)+" )"
        else:
            name_forms["source_files"] = "set(SRCS " + "\n".join(src_files)+" )"

        if in_main_file_names in src_files:
            # If directory contains a main file we assume that only one
            # executable should be generated for this directory and all other
            # src files should be linked to this
            name_forms["executables"] = add_exec % ("${PROJECT_NAME}", "${SRCS}")
            name_forms["target_libraries"] = target_link_libraries_str % "${PROJECT_NAME}"

            if should_generate_precompiled_headers:
                name_forms["executables"] += os.linesep+"ADD_NATIVE_PRECOMPILED_HEADER(${PROJECT_NAME} ${PCH_SRC_H})"
        else:
            # If no main file in source files, we assume that we build library
            # be compiled as an executable
            name_forms["library"] = add_lib % ("${PROJECT_NAME}", "${SRCS}")
            name_forms["library_static"] = add_lib_static % ("${PROJECT_NAME}-static", "${SRCS}")
            if should_generate_precompiled_headers:
                name_forms["library"] += os.linesep+"ADD_NATIVE_PRECOMPILED_HEADER(${PROJECT_NAME} ${PCH_SRC_H})"
                name_forms["library_static"] += os.linesep+"ADD_NATIVE_PRECOMPILED_HEADER(${PROJECT_NAME}-static ${PCH_SRC_H})"

            name_forms["target_libraries"] = target_link_libraries_str % "${PROJECT_NAME}"

        name_forms["includes"] = add_includes % (' '.join(in_inc_dir))

        if should_generate_precompiled_headers:
            name_forms["precompiled_headers"] = add_precompiled_header_support

        with open(os.path.join(cur_sub_dir, "CMakeLists.txt"), 'w') as f:
            f.write(cmakelists_str % name_forms)
        sub_projects.append(subproject_name)

    # explicitly add aditional sub-directory to main CMakeLists.txt file
    f = open("CMakeLists.txt", "r")
    lines = f.readlines()
    i = get_index_of_substr(lines, start_label)
    if i > -1:
        for item in sub_projects:
            if platform.system() == "Windows":
                start_pos = item.find(root)+len(root)-1
                item = item[start_pos:]
                item = item.replace("\\", "/")
            else:
                start_pos = item.find("/")+1
                item = item[start_pos:]
            new_entry = "add_subdirectory("+item+")"+os.linesep
            lines.insert(i+1, new_entry)
    f.close()

    out = open('CMakeLists_new.txt', 'a+')
    out.writelines(lines)
    out.close()

    os.remove('CMakeLists.txt')
    os.rename('CMakeLists_new.txt', 'CMakeLists.txt')


def generate_cmake_files_deep_nested(target_list, main_file_names, src_suffix, includes):
    cur_dir = os.getcwd()

    for dir in target_list:
        current_target_dir = os.path.normpath(os.path.join(cur_dir, dir))

        current_target = os.path.basename(os.path.normpath(dir))
        current_target_src = []
        group_src = get_all_files_by_suffix(current_target_dir, exclude_dirs, src_suffix)

        name_forms = dict(
                project_name=current_target,
                source_files="#SRCS - NOT SET",
                executables="#executables - NOT_SET",
                target_libraries="#target_libraries - NOT_SET",
                library="#library - NOT_SET",
                library_static="#library_static - NOT_SET",
                includes="#include - NOT_SET",
                precompiled_headers="#NOT_SET"
                )

        # generate groups for every nested sub-directory
        # set(SRCS %(src_files)s)
        # SOURCE_GROUP(${PROJECT_NAME} ${SRCS})
        final_src_substituion = ""
        for group in group_src:
            if platform.system() == "Windows":
                cur_group = os.path.normpath(group).replace('\\', '_')
            else:
                cur_group = os.path.normpath(group).replace('/', '_')
            cur_group_src_files = group_src[group]

            final_src_substituion += "set(" + cur_group + "_SRC " + "\n".join(cur_group_src_files) + " )" + os.linesep
            final_src_substituion += "SOURCE_GROUP(" + cur_group + " FILES ${" + cur_group + "_SRC} )" + os.linesep

            current_target_src = current_target_src + cur_group_src_files

        if should_generate_precompiled_headers:
            final_src_substituion += "set(SRCS ${PCH_SRC} " + "\n".join(current_target_src)+" )" + os.linesep
        else:
            final_src_substituion += "set(SRCS " + "\n".join(current_target_src) + " )" + os.linesep

        name_forms["source_files"] = final_src_substituion

        if main_file_names in current_target_src:
            # If directory contains a main file we assume that only one
            # executable should be generated for this directory and all other
            # src files should be linked to this
            name_forms["executables"] = add_exec % ("${PROJECT_NAME}", "${SRCS}")
            if should_generate_precompiled_headers:
                name_forms["executables"] += os.linesep+"ADD_NATIVE_PRECOMPILED_HEADER(${PROJECT_NAME} ${PCH_SRC_H})"
            name_forms["target_libraries"] = target_link_libraries_str % "${PROJECT_NAME}"
        else:
            # If no main file in source files, we assume that we build library
            # be compiled as an executable
            name_forms["library"] = add_lib % ("${PROJECT_NAME}", "${SRCS}")
            name_forms["library_static"] = add_lib_static % ("${PROJECT_NAME}-static", "${SRCS}")
            if should_generate_precompiled_headers:
                name_forms["library"] += os.linesep+"ADD_NATIVE_PRECOMPILED_HEADER(${PROJECT_NAME} ${PCH_SRC_H})"
                name_forms["library_static"] += os.linesep+"ADD_NATIVE_PRECOMPILED_HEADER(${PROJECT_NAME}-static ${PCH_SRC_H})"
            name_forms["target_libraries"] = target_link_libraries_str % "${PROJECT_NAME}"

        name_forms["includes"] = add_includes % (' '.join(includes))
        if should_generate_precompiled_headers:
            name_forms["precompiled_headers"] = add_precompiled_header_support

        with open(os.path.join(current_target_dir, "CMakeLists.txt"), 'w') as f:
            f.write(cmakelists_str % name_forms)


#
#				MAIN
#

if os.path.isfile("generated.flag"):
    quit()

should_use_second_algo = False
target_list = sys.argv[1:]

cur_dir = os.getcwd()
if platform.system() == "Windows":
    cur_dir = cur_dir.replace("\\", "/")
includes = get_all_subfolders_by_suffix(inc_suffix, exclude_dirs)
includes.append(cur_dir)

if len(target_list) > 0:
    exclude_dirs += [os.path.basename(os.path.normpath(x)) for x in target_list]
    should_use_second_algo = True

res_folder = get_all_subfolders_by_suffix(src_suffix, exclude_dirs)

generate_cmake_files(res_folder, main_file_names, src_suffix, includes)

if should_use_second_algo:
    generate_cmake_files_deep_nested(target_list, main_file_names, src_suffix, includes)
