#
# Copyright 2011 Free Software Foundation, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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/>.
#

########################################################################
# Parse the arches xml file:
#  Test each arch to see if the compiler supports the flag.
#  If the test passes append the arch to the available list.
########################################################################
#extract the arch lines from the xml file using crazy python
execute_process(
    COMMAND ${PYTHON_EXECUTABLE} -c
    "from xml.dom import minidom; print ';'.join(map(lambda a: '%s %s'%(a.attributes['name'].value,a.getElementsByTagName('flag')[0].firstChild.data),minidom.parse('${CMAKE_SOURCE_DIR}/gen/archs.xml').getElementsByTagName('arch')))"
    OUTPUT_VARIABLE arch_lines OUTPUT_STRIP_TRAILING_WHITESPACE
)

#get any mutually exclusive archs so we can exclude them
#this is really for compilers which can do both 32- and 64-bit compilations.
execute_process(
    COMMAND ${PYTHON_EXECUTABLE} -c
    "from xml.dom import minidom; print ';'.join(map(lambda a: '%s %s'%(a.parentNode.attributes['name'].value,a.firstChild.data),minidom.parse('${CMAKE_SOURCE_DIR}/gen/archs.xml').getElementsByTagName('mutex')))"
    OUTPUT_VARIABLE mutex_lines OUTPUT_STRIP_TRAILING_WHITESPACE
)

#This macro sets the ${arch}_flag variable,
#and handles special cases for MSVC arch flags.
macro(set_arch_flag name flag)
    if(MSVC AND ${name} STREQUAL "mmx")
        set(${name}_flag "/arch:SSE") #no /arch:MMX
    elseif(MSVC AND ${name} STREQUAL "sse")
        set(${name}_flag "/arch:SSE")
    elseif(MSVC AND ${name} STREQUAL "sse2")
        set(${name}_flag "/arch:SSE2")
    else()
        set(${name}_flag -${flag})
    endif()
endmacro(set_arch_flag)

macro(handle_arch name flag)

    #handle special case for none flag
    if(${flag} STREQUAL "none")
        set(have_${name} TRUE)

    #otherwise test the flag against the compiler
    else()
        include(CheckCXXCompilerFlag)
        set_arch_flag(${name} ${flag})
        CHECK_CXX_COMPILER_FLAG(${${name}_flag} have_${name})
    endif()

    if(have_${name})
        list(APPEND available_arches ${name})
    endif()
endmacro(handle_arch)

macro(remove_mutex name mutex)
    if(have_${name})
        unset(have_${mutex})
    endif()
    list(REMOVE_ITEM available_arches ${mutex})
endmacro(remove_mutex)

#create a list of available arches
foreach(arch_line ${arch_lines})
    separate_arguments(args UNIX_COMMAND "${arch_line}")
    handle_arch(${args})
endforeach(arch_line)

#strip out mutex archs
foreach(mutex_line ${mutex_lines})
    separate_arguments(args UNIX_COMMAND "${mutex_line}")
    remove_mutex(${args})
endforeach(mutex_line)

message(STATUS "Available arches: ${available_arches}")

########################################################################
# Parse the machines xml file:
#  Test each machine to see if its arch dependencies are supported.
#  Build a list of supported machines and the machine definitions.
########################################################################
#extract the machine lines from the xml file using crazy python
execute_process(
    COMMAND ${PYTHON_EXECUTABLE} -c
    "from xml.dom import minidom; print ';'.join(map(lambda a: '%s %s'%(a.attributes['name'].value,a.getElementsByTagName('archs')[0].firstChild.data),minidom.parse('${CMAKE_SOURCE_DIR}/gen/machines.xml').getElementsByTagName('machine')))"
    OUTPUT_VARIABLE machine_lines OUTPUT_STRIP_TRAILING_WHITESPACE
)

macro(handle_machine1 name)
    unset(machine_flags)
    string(TOUPPER LV_MACHINE_${name} machine_def)

    #check if all the arches are supported
    foreach(arch ${ARGN})
        set(is_match ${have_${arch}})
        if(NOT is_match)
            set(is_match FALSE)
            break()
        endif(NOT is_match)
        set(machine_flags "${machine_flags} ${${arch}_flag}")
    endforeach(arch)

    if(is_match)
        #this is a match, append the source and set its flags
        set(machine_source ${CMAKE_CURRENT_BINARY_DIR}/volk_machine_${name}.c)
        set_source_files_properties(${machine_source} PROPERTIES COMPILE_FLAGS ${machine_flags})
        list(APPEND machine_sources ${machine_source})
        list(APPEND machine_defs ${machine_def})
        list(APPEND available_machines ${name})
    endif()
endmacro(handle_machine1)

macro(handle_machine name)
    set(arches ${ARGN})
    list(FIND arches "32|64" index)
    if(${index} EQUAL -1)
        handle_machine1(${name} ${arches})
    else()
        list(REMOVE_ITEM arches "32|64")
        handle_machine1(${name}_32 32 ${arches})
        handle_machine1(${name}_64 64 ${arches})
    endif()
endmacro(handle_machine)

#setup the available machines
foreach(machine_line ${machine_lines})
    separate_arguments(args UNIX_COMMAND "${machine_line}")
    handle_machine(${args})
endforeach(machine_line)

message(STATUS "Available machines: ${available_machines}")

########################################################################
# Create rules to run the volk generator
########################################################################
#list of the generated sources
set(volk_gen_sources
    ${CMAKE_BINARY_DIR}/include/volk/volk.h
    ${CMAKE_BINARY_DIR}/lib/volk.c
    ${CMAKE_BINARY_DIR}/lib/volk_init.h
    ${CMAKE_BINARY_DIR}/include/volk/volk_typedefs.h
    ${CMAKE_BINARY_DIR}/include/volk/volk_cpu.h
    ${CMAKE_BINARY_DIR}/lib/volk_cpu.c
    ${CMAKE_BINARY_DIR}/include/volk/volk_config_fixed.h
    ${CMAKE_BINARY_DIR}/lib/volk_environment_init.c
    ${CMAKE_BINARY_DIR}/lib/volk_environment_init.h
    ${CMAKE_BINARY_DIR}/lib/volk_machines.h
    ${CMAKE_BINARY_DIR}/lib/volk_machines.c
    ${machine_sources}
)

#dependencies are all python, xml, and header implementation files
file(GLOB xml_files ${CMAKE_SOURCE_DIR}/gen/*.xml)
file(GLOB py_files ${CMAKE_SOURCE_DIR}/gen/*.py)
file(GLOB h_files ${CMAKE_SOURCE_DIR}/include/volk/*.h)

#make sure we can use -B with python (introduced in 2.6)
execute_process(
    COMMAND ${PYTHON_EXECUTABLE} -B -c ""
    OUTPUT_QUIET ERROR_QUIET
    RESULT_VARIABLE PYTHON_HAS_DASH_B_RESULT
)
if(PYTHON_HAS_DASH_B_RESULT EQUAL 0)
    set(PYTHON_DASH_B "-B")
endif()

add_custom_command(
    OUTPUT ${volk_gen_sources}
    DEPENDS ${xml_files} ${py_files} ${h_files}
    COMMAND ${PYTHON_EXECUTABLE} ${PYTHON_DASH_B}
        ${CMAKE_SOURCE_DIR}/gen/volk_register.py
        ${CMAKE_BINARY_DIR}
)

########################################################################
# Handle orc support
########################################################################
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
PKG_CHECK_MODULES(ORC "orc-0.4 > 0.4.11")
endif(PKG_CONFIG_FOUND)

find_program(ORCC_EXECUTABLE orcc)

if(ORC_FOUND AND ORCC_EXECUTABLE)
    #setup orc library usage
    include_directories(${ORC_INCLUDE_DIRS})
    link_directories(${ORC_LIBRARY_DIRS})
    add_definitions(-DLV_HAVE_ORC)

    #setup orc functions
    file(GLOB orc_files ${CMAKE_SOURCE_DIR}/orc/*.orc)
    foreach(orc_file ${orc_files})

        #extract the name for the generated c source from the orc file
        get_filename_component(orc_file_name_we ${orc_file} NAME_WE)
        set(orcc_gen ${CMAKE_CURRENT_BINARY_DIR}/${orc_file_name_we}.c)

        #create a rule to generate the source and add to the list of sources
        add_custom_command(
            COMMAND ${ORCC_EXECUTABLE} --implementation -o ${orcc_gen} ${orc_file}
            DEPENDS ${orc_file} OUTPUT ${orcc_gen}
        )
        list(APPEND volk_sources ${orcc_gen})

    endforeach(orc_file)
else()
    message(STATUS "Did not find liborc and orcc, disabling orc support...")
endif()

########################################################################
# Setup the volk sources list and library
########################################################################
if(CMAKE_COMPILER_IS_GNUCXX AND NOT WIN32)
    #http://gcc.gnu.org/wiki/Visibility
    add_definitions(-fvisibility=hidden)
endif()

include_directories(
    ${CMAKE_BINARY_DIR}/include
    ${CMAKE_SOURCE_DIR}/include
    ${CMAKE_CURRENT_BINARY_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}
)

list(APPEND volk_sources
    ${CMAKE_CURRENT_SOURCE_DIR}/volk_prefs.c
    ${CMAKE_CURRENT_SOURCE_DIR}/volk_rank_archs.c
    ${volk_gen_sources}
)

#set the machine definitions where applicable
set_source_files_properties(
    ${CMAKE_CURRENT_BINARY_DIR}/volk.c
    ${CMAKE_CURRENT_BINARY_DIR}/volk_machines.c
PROPERTIES COMPILE_DEFINITIONS "${machine_defs}")

if(MSVC)
    #add compatibility includes for stdint types
    include_directories(${CMAKE_SOURCE_DIR}/msvc)
    #compile the sources as C++ due to the lack of complex.h under MSVC
    set_source_files_properties(${volk_sources} PROPERTIES LANGUAGE CXX)
endif(MSVC)

#create the volk runtime library
add_library(volk SHARED ${volk_sources})
target_link_libraries(volk ${ORC_LIBRARIES})
set_target_properties(volk PROPERTIES SOVERSION ${LIBVER})
set_target_properties(volk PROPERTIES DEFINE_SYMBOL "volk_EXPORTS")

install(TARGETS volk
    LIBRARY DESTINATION lib${LIB_SUFFIX} COMPONENT "volk_runtime" # .so file
    ARCHIVE DESTINATION lib${LIB_SUFFIX} COMPONENT "volk_devel"   # .lib file
    RUNTIME DESTINATION bin              COMPONENT "volk_runtime" # .dll file
)

########################################################################
# Build the QA test application
########################################################################
find_package(Boost COMPONENTS unit_test_framework)

if(Boost_FOUND)

set_source_files_properties(
    ${CMAKE_CURRENT_SOURCE_DIR}/testqa.cc PROPERTIES
    COMPILE_DEFINITIONS "BOOST_TEST_DYN_LINK;BOOST_TEST_MAIN"
)

include_directories(${Boost_INCLUDE_DIRS})
link_directories(${Boost_LIBRARY_DIRS})

add_executable(test_all
    ${CMAKE_CURRENT_SOURCE_DIR}/testqa.cc
    ${CMAKE_CURRENT_SOURCE_DIR}/qa_utils.cc
)
target_link_libraries(test_all volk ${Boost_LIBRARIES})
#ADD_TEST(qa_volk_test_all test_all)

endif()