# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
#

from .config import Configuration
from .target import TargetConditional
from .path import Path
import os

class BuildAction:
    input = None
    output = None
    _product = None
    dependencies = None
    skipRule = False

    def __init__(self, input=None, output=None):
        self.input = input
        self.output = output
    
    @property
    def product(self):
        return self._product

    def set_product(self, product):
        self._product = product

    def generate_dependencies(self, extra = None):
        if self.dependencies is not None and len(self.dependencies) > 0:
            rule = " |"
            for dep in self.dependencies:
                rule += " " + dep.name
            if extra is not None:
                rule += " " + extra
            return rule
        else:
            if extra is not None:
                return " | " + extra
            return ""

    def add_dependency(self, phase):
        if self.dependencies is None:
            self.dependencies = [phase]
        else:
            self.dependencies.append(phase)


class Cp(BuildAction):
    def __init__(self, source, destination):
        BuildAction.__init__(self, input=source, output=destination)

    def generate(self):
        return """
build """ + self.output.relative() + """: Cp """ + self.input.relative() + self.generate_dependencies() + """
"""

class CompileSource(BuildAction):
    path = None
    def __init__(self, path, product):
        BuildAction.__init__(self, input=path, output=Configuration.current.build_directory.path_by_appending(product.name).path_by_appending(path.relative() + ".o"))
        self.path = path

    @staticmethod
    def compile(source, phase):
        ext = source.extension()
        if ext == ".c" or ext == ".m":
            return CompileC(source, phase.product)
        elif ext == ".mm" or ext == ".cpp" or ext == ".CC":
            return CompileCxx(source, phase.product)
        elif ext == ".S" or ext == ".s":
            return Assemble(source, phase.product)
        elif ext == ".swift":
            return CompileSwift(source, phase.product, phase)
        else:
            return None


class CompileC(CompileSource):
    def __init__(self, path, product):
        CompileSource.__init__(self, path, product)

    def generate(self):
        generated = """
build """ + self.output.relative() + """: CompileC """ + self.path.relative() + self.generate_dependencies() + """
    flags = """
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative()
        generated += " -I" + Configuration.current.build_directory.relative()
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.ROOT_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PUBLIC_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PRIVATE_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PROJECT_HEADERS_FOLDER_PATH
        cflags = TargetConditional.value(self.product.CFLAGS)
        if cflags is not None:
            generated += " " + cflags
        prefix = TargetConditional.value(self.product.GCC_PREFIX_HEADER)
        if prefix is not None:
            generated += " -include " + prefix
        generated += "\n"
        if self.path.extension() == ".m" or "-x objective-c" in generated:
            self.product.needs_objc = True
        return generated


class CompileCxx(CompileSource):
    def __init__(self, path, product):
        CompileSource.__init__(self, path, product)

    def generate(self):
        generated = """
build """ + self.output.relative() + """: CompileCxx """ + self.path.relative() + self.generate_dependencies() + """
    flags = """
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative()
        generated += " -I" + Configuration.current.build_directory.relative()
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.ROOT_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PUBLIC_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PRIVATE_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PROJECT_HEADERS_FOLDER_PATH
        cflags = TargetConditional.value(self.product.CFLAGS)
        if cflags is not None:
            generated += " " + cflags
        cxxflags = TargetConditional.value(self.product.CXXFLAGS)
        if cxxflags is not None:
            generated += " " + cxxflags
        prefix = TargetConditional.value(self.product.GCC_PREFIX_HEADER)
        if prefix is not None:
            generated += " -include " + prefix
        generated += "\n"
        if self.path.extension() == ".mm" or "-x objective-c" in generated:
            self.product.needs_objc = True
        self.product.needs_stdcxx = True
        return generated


class Assemble(CompileSource):
    def __init__(self, path, product):
        CompileSource.__init__(self, path, product)

    def generate(self):
        generated = """
build """ + self.output.relative() + """: Assemble """ + self.path.relative() + self.generate_dependencies() + """
    flags = """
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative()
        generated += " -I" + Configuration.current.build_directory.relative()
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.ROOT_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PUBLIC_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PRIVATE_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.PROJECT_HEADERS_FOLDER_PATH
        asflags = TargetConditional.value(self.product.ASFLAGS)
        if asflags is not None:
            generated += " " + asflags
        return generated


class CompileSwift(CompileSource):
    phase = None
    def __init__(self, path, product, phase):
        CompileSource.__init__(self, path, product)
        self.phase = phase

    def generate(self):
        generated = """
build """ + self.output.relative() + """: CompileSwift """ + self.path.relative() + self.generate_dependencies() + """
    module_sources = """ + self.module_sources + """
    module_name = """ + self.product.name + """
    flags = """
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative()
        generated += " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.ROOT_HEADERS_FOLDER_PATH
        generated += " -I" + Configuration.current.build_directory.relative()
        swiftflags = TargetConditional.value(self.product.SWIFTCFLAGS)
        # Force building in Swift 4 compatibility mode.
        swiftflags += " -swift-version 4"
        if swiftflags is not None:
            generated += " " + swiftflags
        return generated

    @property
    def module_sources(self):
        sources = self.phase.module_sources(primary=self.path.absolute())
        return " ".join(sources)
    

class BuildPhase(BuildAction):
    previous = None
    name = None
    dependencies = None
    actions = None
    def __init__(self, name):
        BuildAction.__init__(self)
        self.dependencies = []
        self.actions = []
        self.name = name

    def generate(self):
        generated = ""
        for action in self.actions:
            action.dependencies = self.dependencies
            generated += action.generate() + "\n"

        rule = "build " + self.name + ": phony"

        if self.previous is not None or len(self.actions) > 0:
            rule += " |"
            if self.previous is not None:
                rule += " " + self.previous.name
            for action in self.actions:
                rule += " " + action.output.relative()
        rule += "\n"
        return generated + "\n" + rule

    @property
    def objects(self):
        return []


class CopyHeaders(BuildPhase):
    _public = []
    _private = []
    _project = []
    _module = None
    def __init__(self, public, private, project, module=None):
        BuildPhase.__init__(self, "CopyHeaders")
        if public is not None:
            self._public = public
        if private is not None:
            self._private = private
        if project is not None:
            self._project = project
        self._module = module

    @property
    def product(self):
        return self._product
    
    def set_product(self, product):
        BuildAction.set_product(self, product)
        self.actions = []

        module = Path.path(TargetConditional.value(self._module))
        if module is not None:
            action = Cp(module, self.product.public_module_path.path_by_appending("module.modulemap"))
            self.actions.append(action)
            action.set_product(product)

        for value in self._public:
            header = Path.path(TargetConditional.value(value))
            if header is None:
                continue
            action = Cp(header, self.product.public_headers_path.path_by_appending(header.basename()))
            self.actions.append(action)
            action.set_product(product)

        for value in self._private:
            header = Path.path(TargetConditional.value(value))
            if header is None:
                continue
            action = Cp(header, self.product.private_headers_path.path_by_appending(header.basename()))
            self.actions.append(action)
            action.set_product(product)

        for value in self._project:
            header = Path.path(TargetConditional.value(value))
            if header is None:
                continue
            action = Cp(header, self.product.project_headers_path.path_by_appending(header.basename()))
            self.actions.append(action)
            action.set_product(product)

class CopyResources(BuildPhase):
    _resources = []
    _resourcesDir = None
    
    def __init__(self, outputDir, resources):
        BuildPhase.__init__(self, "CopyResources")
        if resources is not None:
            self._resources = resources
            self._resourcesDir = outputDir

    @property
    def product(self):
        return self._product
    
    def set_product(self, product):
        BuildAction.set_product(self, product)
        self.actions = []

        for value in self._resources:
            resource = Path.path(TargetConditional.value(value))
            if resource is None:
                continue
        
            action = Cp(resource, Configuration.current.build_directory.path_by_appending(self._resourcesDir).path_by_appending(resource.basename()))
            self.actions.append(action)
            action.set_product(product)

class CompileSources(BuildPhase):
    _sources = []
    def __init__(self, sources):
        BuildPhase.__init__(self, "CompileSources")
        if sources is not None:
            self._sources = sources

    @property
    def product(self):
        return self._product
    
    def set_product(self, product):
        BuildAction.set_product(self, product)
        self.actions = []

        for value in self._sources:
            source = Path.path(TargetConditional.value(value))
            if source is None:
                continue
            action = CompileSource.compile(source, self)
            if action is None:
                print("Unable to compile source " + source.absolute())
                assert action is not None
            self.actions.append(action)
            action.set_product(product)

    @property
    def objects(self):
        objects = []
        for action in self.actions:
            objects.append(action.output.relative())
        return objects


class MergeSwiftModule:
    name = None
    def __init__(self, module):
        self.name = module\

    @property
    def output(self):
        return Path.path(self.name)


class CompileSwiftSources(BuildPhase):
    _sources = []
    _module = None
    def __init__(self, sources):
        BuildPhase.__init__(self, "CompileSwiftSources")
        if sources is not None:
            self._sources = sources
        self.enable_testable_import = False

    @property
    def product(self):
        return self._product
    
    def set_product(self, product):
        BuildAction.set_product(self, product)
        self.actions = []

        for value in self._sources:
            source = Path.path(TargetConditional.value(value))
            if source is None:
                continue
            action = CompileSource.compile(source, self)
            if action is None:
                print("Unable to compile source " + source.absolute())
                assert action is not None
            self.actions.append(action)
            action.set_product(product)
        self._module = Configuration.current.build_directory.path_by_appending(self.product.name).path_by_appending(self.product.name + ".swiftmodule")
        product.add_dependency(MergeSwiftModule(self._module.relative()))

    def module_sources(self, primary):
        modules = []
        for value in self._sources:
            source = Path.path(TargetConditional.value(value))
            if source is None:
                continue
            if source.absolute() != primary:
                modules.append(source.relative())
            else:
                modules.append("-primary-file")
                modules.append(source.relative())
        return modules

    def generate(self):
        generated = BuildPhase.generate(self)
        generated += "\n\n"
        objects = ""
        partial_modules = ""
        partial_docs = ""
        for value in self._sources:
            path = Path.path(value)
            compiled = Configuration.current.build_directory.path_by_appending(self.product.name).path_by_appending(path.relative() + ".o")
            objects += compiled.relative() + " "
            partial_modules += compiled.relative() + ".~partial.swiftmodule "
            partial_docs += compiled.relative() + ".~partial.swiftdoc "

        testable_import_flags = ""
        if self.enable_testable_import:
            testable_import_flags = "-enable-testing"

        generated += """
build """ + self._module.relative() + ": MergeSwiftModule " + objects + """
    partials = """ + partial_modules + """
    module_name = """ + self.product.name + """
    flags = """ + testable_import_flags + " -I" + self.product.public_module_path.relative() + """ """ + TargetConditional.value(self.product.SWIFTCFLAGS) + """ -emit-module-doc-path """ + self._module.parent().path_by_appending(self.product.name).relative() + """.swiftdoc 
"""
        return generated

    @property
    def objects(self):
        objects = []
        for action in self.actions:
            objects.append(action.output.relative())
        return objects

# This builds a Swift executable using one invocation of swiftc (no partial compilation)
class SwiftExecutable(BuildPhase):
    executableName = None
    outputDirectory = None
    sources = []
    
    def __init__(self, executableName, sources):
        BuildAction.__init__(self, output=executableName)
        self.executableName = executableName
        self.name = executableName
        self.sources = sources
        self.outputDirectory = executableName
    
    def generate(self):
        appName = Configuration.current.build_directory.relative() + """/""" + self.outputDirectory + """/""" + self.executableName
        libDependencyName = self.product.product_name
        swiftSources = ""
        for value in self.sources:
            resource = Path.path(TargetConditional.value(value))
            if resource is None:
                continue
            swiftSources += " " + resource.relative()
        # Note: Fix -swift-version 4 for now.
        return """
build """ + appName + """: SwiftExecutable """ + swiftSources + self.generate_dependencies(libDependencyName) + """
    flags = -swift-version 4 -I""" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + self.product.ROOT_HEADERS_FOLDER_PATH + " -I" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + " -L" + Configuration.current.build_directory.path_by_appending(self.product.name).relative() + " " + TargetConditional.value(self.product.SWIFTCFLAGS) + """
build """ + self.executableName + """: phony | """ + appName + """
"""



