#! /usr/bin/env ruby

require 'optparse'

class BipDependencyResolver
    def initialize(includeDirs, sourceDirectory)
        @includeDirs = includeDirs
        @sourceDirectory = sourceDirectory
    end

    def analyse(fileName)
        @packageDependencies = []
        @modelDependencies = []

        open(fileName, "r") do |theFile|
            theFile.each_line do |theLine|
                if theLine =~ /^[ \t]*use[ \t]+([a-zA-Z][a-zA-Z0-9_]*)[ \t]*$/
                    modelName = $1
                    result = resolvePackage(modelName)
                    if result.nil?
                        if File.file?("#{@sourceDirectory}/#{modelName}.bip")
                            @modelDependencies << "#{@sourceDirectory}/#{modelName}.bip"
                        else
                            raise RuntimeError, "Model #{modelName} is not found in the list of include directories"
                        end
                    else
                        @packageDependencies << "\"#{result}\""
                    end
                end
            end
        end

        return nil
    end

    def getPackageDependencies()
        @packageDependencies
    end

    def getModelDependencies()
        @modelDependencies
    end

    private

    def resolvePackage(packageName)
        @includeDirs.each do |includeDir|
            fileName = "#{includeDir}"
            if !includeDir.end_with? "/"
                fileName += "/"
            end
            fileName += "#{packageName}.model"
            if File.file?(fileName)
                return packageName
            end
        end

        return nil
    end
end

module CMakeGenerator
    def CMakeGenerator.generateModelCompilationCommands(fileName, includeDirs)
        res = []

        theLocalParser = BipDependencyResolver.new(includeDirs, File.dirname(fileName))
        theLocalParser.analyse(fileName)
        theLocalParser.getModelDependencies.each do |dependency|
            res += generateModelCompilationCommands(dependency, includeDirs)
            res.uniq!
        end

        allDependencies = theLocalParser.getPackageDependencies
        theLocalParser.getModelDependencies.each do |neededBipModel|
            allDependencies << "\"#{File.basename(neededBipModel, ".bip")}\""
        end
        if allDependencies.empty?
            res << "add_bip2_model(\"#{File.expand_path(fileName)}\")"
        else
            res << "add_bip2_model(\"#{File.expand_path(fileName)}\"#{" " + allDependencies.join(" ")})"
        end
        res.uniq!

        return res
    end

    def CMakeGenerator.collectAllSources(fileName)
        res = []

        theLocalParser = BipDependencyResolver.new([], File.dirname(fileName))
        theLocalParser.analyse(fileName)
        theLocalParser.getModelDependencies.each do |dependency|
            res += collectAllSources(dependency)
            res.uniq!
        end

        res << "    ${#{File.basename(fileName, ".bip")}_GENERATED_SOURCE_FILE}"
        res.uniq!

        return res
    end

    def CMakeGenerator.generateCmakeInputFile(options, target)
        inputFileName = options[:inputFileName]
        includeDirs = options[:includeDirs] || []

        overwrite = options[:overwrite]

        if File.file?("CMakeLists.txt") && !overwrite
            raise RuntimeError, "There already exists a file named CMakeLists.txt and you did not ask to overwrite it."
        end

        baseName = File.basename(inputFileName, ".bip")

        open("CMakeLists.txt", "w") do |theFile|
            theFile<< <<fileContent
project(#{baseName}_bip CXX)

cmake_minimum_required(VERSION 2.6)

set(BIPMODE #{target.to_s})
if (${CMAKE_MODULE_PATH})
    set(CMAKE_MODULE_PATH "$ENV{BIP2_HOME}/share/cmake-2.6/Modules:${CMAKE_MODULE_PATH}")
else (${CMAKE_MODULE_PATH})
    set(CMAKE_MODULE_PATH "$ENV{BIP2_HOME}/share/cmake-2.6/Modules")
endif (${CMAKE_MODULE_PATH})

find_package(BIP2 REQUIRED)

include_directories(${BIP2_INCLUDE_DIR})
link_directories(${BIP2_LIBRARY_DIR})

#{
    res = []
    includeDirs.each do |includeDir|
        res << "add_bip2_include_directory(\"#{baseName}\" \"#{includeDir}\")"
    end
    theLocalParser = BipDependencyResolver.new(includeDirs, File.dirname(inputFileName))
    theLocalParser.analyse(inputFileName)
    if !theLocalParser.getModelDependencies.empty?
        res << "add_bip2_include_directory(\"#{baseName}\" ${CMAKE_CURRENT_BINARY_DIR})"
    end
    if res.empty?
        ""
    else
        res.join("\n") + "\n"
    end
}
#{
    generateModelCompilationCommands(options[:inputFileName], includeDirs).join("\n") + "\n"
}
add_executable(#{baseName}.bip.x
#{
    collectAllSources(options[:inputFileName]).join("\n")
}
)

set_target_properties(#{baseName}.bip.x
    PROPERTIES
        INSTALL_RPATH_USE_LINK_PATH TRUE
)

target_link_libraries(#{baseName}.bip.x
    ${BIP2_LIBRARIES}
)

fileContent
        end
    end
end

options = {}

opts = OptionParser.new do |opts|
    opts.banner = "Usage: #$0 [options] revision"

    opts.on("-f", "--file filename", String, "Set the input file") do |filename|
        options[:inputFileName] = filename.strip
    end

    opts.on("-I filename", "Add include dir for packages") do |v|
        options[:includeDirs] = [] if options[:includeDirs].nil?
        options[:includeDirs] << v.strip
    end

    opts.on("--genC-execute", "Generate C code for single threaded execution.") do |v|
        options[:genCExecute] = v
    end

    opts.on("--genC-multi", "Generate C code for multi threaded execution.") do |v|
        options[:genCMulti] = v
    end

    opts.on("--genC-explore", "Generate C code for exploration. Implemented for compatibility with bipc.") do |v|
    end

    opts.on("--genC-top", "Defines the top component that will be used when generating code. Implemented for compatibility with bipc.") do |v|
    end

    opts.on("-m", "--model filename", "Defines the input model to be taken in input by the BIP compiler. Implemented for compatibility with bipc.") do |v|
    end

    opts.on("-r", "--reverse filename", "Defines the filename of the bip model that will be produced from the reverse compiler. Implemented for compatibility with bipc.") do |v|
    end

    opts.on("--overwrite", "Will overwrite any existing CMakeLists.txt.") do |v|
        options[:overwrite] = v
    end

    opts.on_tail("-h", "--help", "Show this message") do
        puts opts
        exit
    end
end

opts.parse!

theParser = BipDependencyResolver.new(options[:includeDirs] || [], File.dirname(options[:inputFileName]))
theParser.analyse(options[:inputFileName])

target = :mono if options[:genCExecute]
target = :multi if options[:genCMulti]

CMakeGenerator.generateCmakeInputFile(options, target)
