if ! $(ModuleRules_INCLUDED) {
ModuleRules_INCLUDED = TRUE ;

include [ FDirName $(SUBDIR) CommonRules ] ;
include [ FDirName $(SUBDIR) VariantRules ] ;

# TRACE_MODULE ?= TRUE ;

# LIBRARIES can be:
#   STATIC
#   SHARED
#   NONE

LIBRARIES ?= SHARED ;

# Module <name> : <modules this module depends on> ;
rule Module {
  local name = $(1) ;
  local modules = $(2) ;

  if $(TRACE_MODULE) = TRUE {
    Echo Module $(name) . $(modules) ;
  }

  local module = $(name:G=module) ;
  NotFile $(module) ;

  LINK_LIBRARIES on $(module) = $(NEEDSHARES) ;
  LINK_MODULES on $(module) = $(modules:G=module) ;
  LOCATE_OBJECTS on $(module) = [ FDirName $(SUBDIR) [ ObjectsSubdirName $(VARIANTS) ] ] ;
  LOCATE_SHARED on $(module) = [ FDirName $($(_top)) lib ] ;
  INCLUDES on $(module) += $(SUBDIR) ;
  INCLUDES on $(module) += [ FDirName $(SUBDIR) core ] ;
  INCLUDES on $(module) += [ FDirName $(SUBDIR) component ] ;
}

# ModuleFiles <name> : <files for this module> ;
rule ModuleFiles {
  local name = $(1) ;
  local files = $(2) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleFiles $(name) . $(files) ;
  }

  local module = $(name:G=module) ;
  INCLUDES on $(module) += $(SUBDIR) ;
  ModulePart $(module) : $(files) ;
}

# ModuleCore <name> : <files for this module > ;
rule ModuleCore {
  ModuleFiles $(1) : $(2) ;
}

# ModuleComponent <name> : <files for this module > ;
rule ModuleComponent {
  ModuleFiles $(1) : $(2) ;
}

rule ModuleDocumentation {
  local name = $(1) ;
  local files = $(2) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleDocumentation $(name) . $(files) ;
  }
}

# ModuleObjects <source files> : <modules that these files depend on> ;
rule ModuleObjects {
  local files = $(1) ;
  local modules = $(2) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleObjects $(files) . $(modules) ;
  }

  local links = [ FLinkModules $(modules) ] ;

  local link ;
  local headers ;
  for link in $(links) {
    local module = $(link:G=module) ;
    headers += [ on $(module) return $(INCLUDES) ] ;
  }

  local variant ;
  for variant in $(ALL_VARIANTS) {
    local C++FLAGS = $(C++FLAGS) [ FVariantC++Flags $(variant) ] ;
    local CFLAGS = $(CFLAGS) [ FVariantCFlags $(variant) ] ;

    local use_pic_flags ;
    if SHARED in $(LIBRARIES) {
      use_pic_flags += YES ;
    }
    if SHARED != $(LIBRARIES) {
      use_pic_flags += NO ;
    }
    for use_pic_flag in $(use_pic_flags) {
      local C++FLAGS = $(C++FLAGS) ;
      local CFLAGS = $(CFLAGS) ;
      if $(use_pic_flag) = YES {
        C++FLAGS += $(POSITION_INDEPENDENT_CODE_FLAG) ;
        CFLAGS += $(POSITION_INDEPENDENT_CODE_FLAG) ;
      }

      local file ;
      for file in $(files) {
        if $(file:S) != $(SUFOBJ) {
          file = [ FGristFiles $(file) ] ;
          SEARCH on $(file) = $(SEARCH_SOURCE) ;

          local output = [ FVariantName $(file:S=) : $(variant) ] ;
          if $(use_pic_flag) = YES {
            output = $(output)_pic ;
          }
          output = $(output)$(SUFOBJ) ;
          local HDRS = $(headers) ;
          ObjectHdrs $(output) : $(headers) ;
          Object $(output) : $(file) ;
        }
      }
    }
  }
}

# ModuleNamedObjects <object name> : <source files> : <modules that these files depend on> ;
rule ModuleNamedObjects {
  local output = $(1) ;
  local files = $(2) ;
  local modules = $(3) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleNamedObjects $(output) . $(files) . $(modules) ;
  }

  local links = [ FLinkModules $(modules) ] ;

  local link ;
  local headers ;
  for link in $(links) {
    local module = $(link:G=module) ;
    headers += [ on $(module) return $(INCLUDES) ] ;
  }

  local variant ;
  for variant in $(ALL_VARIANTS) {
    local C++FLAGS = $(C++FLAGS) [ FVariantC++Flags $(variant) ] ;
    local CFLAGS = $(CFLAGS) [ FVariantCFlags $(variant) ] ;

    local use_pic_flags ;
    if SHARED in $(LIBRARIES) {
      use_pic_flags += YES ;
    }
    if SHARED != $(LIBRARIES) {
      use_pic_flags += NO ;
    }
    for use_pic_flag in $(use_pic_flags) {
      local C++FLAGS = $(C++FLAGS) ;
      local CFLAGS = $(CFLAGS) ;
      if $(use_pic_flag) = YES {
        C++FLAGS += $(POSITION_INDEPENDENT_CODE_FLAG) ;
        CFLAGS += $(POSITION_INDEPENDENT_CODE_FLAG) ;
      }

      local file ;
      for file in $(files) {
        if $(file:S) != $(SUFOBJ) {
          file = [ FGristFiles $(file) ] ;
          SEARCH on $(file) = $(SEARCH_SOURCE) ;

          local localoutput = [ FVariantName $(output) : $(variant) ] ;
          #local output = [ FVariantName $(file:S=) : $(variant) ] ;
          if $(use_pic_flag) = YES {
            localoutput = $(localoutput)_pic ;
          }
          localoutput = $(localoutput)$(SUFOBJ) ;
          localoutput = [ FGristFiles $(localoutput) ] ; 
          local HDRS = $(headers) ;
          ObjectHdrs $(localoutput) : $(headers) ;
          Object $(localoutput) : $(file) ;
        }
      }
    }
  }
}

# ModuleMain <name> : <files for this test> : <modules that this test depends on> : <target to build the exe> ;
rule ModuleMain {
  local name = $(1) ;
  local files = $(2) ;
  local modules = $(3) ;
  local build_target = $(4) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleMain $(name) . $(files) . $(modules) . $(build_target) ;
  }

  ModuleObjects $(files) : $(modules) ;

  local links = [ FLinkModules $(modules) ] ;

  local library ;
  for library in $(LIBRARIES) {
    if $(library) = SHARED && $(ENV_VARS_OK) = NO {
      Echo Fatal build error: ;
      Echo Env variable $(ENV_VARS_MSG) must be set to build shared libraries. ;
      Exit ;
    }
    local variant ;
    for variant in $(VARIANTS) {
      if $(library) != SHARED || $(variant) != PROFILE {
        local target = [ FVariantName $(name) : $(variant) ] ;
        if $(library) = SHARED {
          target = $(target)_rt ;
        } else if $(library) = NONE {
          target = $(target)_no ;
        } else if $(library) != STATIC {
          Exit ModuleMain unknown library type: $(library) ;
        }
        LINKFLAGS on $(target) = $(LINKFLAGS) [ FVariantLinkFlags $(variant) ] ;
		LINKLIBS on $(target) = $(LINKLIBS) ;
        NEEDSHARES on $(target) = ;
		LinkSharedLibrariesNoDeps $(target) : [ on $(name:G=module) return $(LINK_LIBRARIES) ] ;
        local shared_dirs ;
        local link ;
        for link in $(links) {
          if $(library) = SHARED {
            local variant_name = [ FVariantName $(link) : $(variant) ] ;
            LinkSharedLibraries $(target) : $(variant_name) ;
          } else if $(library) = STATIC {
            LinkLibraries $(target) : [ FModuleStaticLibraryName $(link) : $(variant) ] ;
          } else if $(library) = NONE {
            local _o = [ on $(link:G=module) return $($(variant)_OBJECTS) ] ;
            MainFromObjects $(target) : $(_o) : $(build_target) : true ;
          }
          local link_libraries = [ on $(link:G=module) return $(LINK_LIBRARIES) ] ;
          LinkSharedLibrariesNoDeps $(target) : $(link_libraries) ;
          if $(library) = SHARED {
            local locate_shared = [ on $(link:G=module) return $(LOCATE_SHARED) ] ;
            shared_dirs = [ FMergeList $(shared_dirs) : $(locate_shared) ] ;
          }
        }
        if $(library) = SHARED {
          for shared_dir in $(shared_dirs) {
            LINKFLAGS on $(target) += $(LINKER_PASSTHROUGH_FLAG)$(LIBRARY_PATH_SEARCH_FLAG)$(shared_dir) ;
          }
        }

        local outputs ;
        local file ;
        for file in $(files) {
          if ! $(file:G) {
            file = [ FGristFiles $(file) ] ;
          }
          local output = [ FVariantName $(file:S=) : $(variant) ] ;
          if $(library) = SHARED {
            output = $(output)_pic ;
          }
          output = $(output)$(SUFOBJ) ;
          outputs += $(output) ;
        }
        MainFromObjects $(target) : $(outputs) : $(build_target) : nogrist ;
      }
    }
  }
}

rule ModuleSharedLibrary {
  local name = $(1) ;
  local files = $(2) ;
  local modules = $(3) ;
  local build_target = $(4) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleSharedLibrary $(name) . $(files) . $(modules) . $(build_target) ;
  }

  ModuleObjects $(files) : $(modules) ;

  local links = [ FLinkModules $(modules) ] ;

  if SHARED in $(LIBRARIES) {
    local variant ;
    for variant in $(VARIANTS) {
      if $(variant) != PROFILE {
        local outputs ;
        local file ;
        for file in $(files) {
          file = [ FGristFiles $(file) ] ;
          local output = [ FVariantName $(file:S=) : $(variant) ] ;
          output = $(output)_pic ;
          output = $(output)$(SUFOBJ) ;
          outputs += $(output) ;
        }

        local target = [ FModuleSharedLibraryName $(name) : $(variant) ] ;
        LINKFLAGS on $(target) = $(LINKFLAGS) [ FVariantLinkFlags $(variant) ] ;

        local shared_dirs ;
        local link ;
        for link in $(links) {
          local variant_name = [ FVariantName $(link) : $(variant) ] ;
          LinkSharedLibraries $(target) : $(variant_name) ;
          local link_libraries = [ on $(link:G=module) return $(LINK_LIBRARIES) ] ;
          LinkSharedLibrariesNoDeps $(target) : $(link_libraries) ;
          local locate_shared = [ on $(link:G=module) return $(LOCATE_SHARED) ] ;
          shared_dirs = [ FMergeList $(shared_dirs) : $(locate_shared) ] ;
        }
        for shared_dir in $(shared_dirs) {
          LINKFLAGS on $(target) += $(LINKER_PASSTHROUGH_FLAG)$(LIBRARY_PATH_SEARCH_FLAG)$(shared_dir) ;
        }
        if $(build_target) {
          SharedLibraryFromObjects $(target) : $(outputs) : true : true ;
          LocalDepends $(build_target) : $(target) ;
        } else {
          SharedLibraryFromObjects $(target) : $(outputs) : : true ;
        }
      }
    }
  }
}

# ModuleLibraries <name> : <libraries> ;
rule ModuleLibraries {
  local name = $(1) ;
  local libraries = $(2) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleLibraries $(name) . $(libraries) . [ on $(module) return $(LINK_LIBRARIES) ] ;
  }

  local module = $(name:G=module) ;
  LINK_LIBRARIES on $(module) = [ FMergeList [ on $(module) return $(LINK_LIBRARIES) ] : $(libraries) ] ;
}

# RunModuleMain <target that when built runs this> : <executable> : <arguments> : <prefix> : <outputFile> ;
rule RunModuleMain {
  local tests = $(1) ;
  local executable = $(2) ;
  local arguments = $(3) ;
  local prefix = $(4) ; # E.g., "/usr/bin/time" to run the command under the UNIX/Linux time command.
  local outputFN = $(5) ;

  if $(TRACE_MODULE) = TRUE {
    Echo RunModuleMain $(tests) . $(executable) . $(arguments) . $(prefix) . $(outputFN) ;
  }

  local variant ;
  for variant in $(VARIANTS) {
    local library ;
    for library in $(LIBRARIES) {
      if $(library) != SHARED || $(variant) != PROFILE {
        local exe = [ FVariantName $(executable) : $(variant) ] ;
        if $(library) = SHARED {
          exe = $(exe)_rt ;
        } else if $(library) = NONE {
          exe = $(exe)_no ;
        } else if $(library) != STATIC {
          Exit RunModuleMain unknown library type: $(library) ;
        }
        Execute $(tests) : $(exe) : $(arguments) : $(prefix) : $(outputFN) ;
     }
    }
  }
}


################
# internal rules

rule FLinkModules {
  local module = $(1) ;

  if $(TRACE_MODULE) = TRUE {
    Echo FLinkModules $(module) ;
  }

  local pushed ;
  local modules = $(module:G=) ;
  local found ;
  while $(modules) {
    local mod = $(modules[1]) ;
    if $(TRACE_MODULE) = TRUE {
      Echo - $(mod) ;
    }
    if $(mod) in $(found) && $(LIBRARIES) = NONE {
      modules = $(modules[2-]) ;
    } else {
      if $(mod) in $(found) {
        found = [ FRemoveItem $(found) : $(mod) ] ;
        if ! $(mod) in $(pushed) {
          pushed += $(mod) ;
        }
      }
      found += $(mod) ;
      modules = [ FMergeList $(modules[2-]) : [ on $(mod:G=module) return $(LINK_MODULES:G=) ] ] ;
      if $(found) in $(pushed) {
        Exit Cycle Detected in FLinkModules. ;
      }
    }
  }

  if $(TRACE_MODULE) = TRUE {
    Echo FLinkModules . $(found) ;
  }

  return $(found) ;
}

rule ModulePart {
  local module = $(1) ;
  local files = $(2) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModulePart $(module) . $(files) ;
  }

  local LOCATE_TARGET = [ on $(module) return $(LOCATE_OBJECTS) ] ;
  ModuleObjects $(files) : $(module) ;

  local library ;
  for library in $(LIBRARIES) {
    local variant ;
    for variant in $(ALL_VARIANTS) {
      if $(library) != SHARED || $(variant) != PROFILE {
        local outputs ;
        local file ;
        for file in $(files) {
          file = [ FGristFiles $(file) ] ;
          local output = [ FVariantName $(file:S=) : $(variant) ] ;
          if $(library) = SHARED {
            output = $(output)_pic ;
          }
          output = $(output)$(SUFOBJ) ;
          outputs += $(output) ;
        }

        local LINKFLAGS = $(LINKFLAGS) [ FVariantLinkFlags $(variant) ] ;

        local libname ;
        if $(library) = SHARED {
          libname = [ FModuleSharedLibraryName $(module) : $(variant) ] ;
        } else if $(library) = STATIC {
          libname = [ FModuleStaticLibraryName $(module) : $(variant) ] ;
        } else if $(library) = NONE {
          $(variant)_OBJECTS on $(module) += $(outputs) ;
        } else {
          Exit ModulePart unknown library type: $(library) ;
        }
        if $(library) = SHARED {
          local LOCATE_TARGET = [ FDirName $($(_top)) lib ] ;
          LINKFLAGS on $(libname) = $(LINKFLAGS)
                                    $(LINKER_PASSTHROUGH_FLAG)$(LIBRARY_PATH_SEARCH_FLAG)$(LOCATE_TARGET) ;
          LINKLIBS on $(libname) = $(LINKLIBS) ;
          local need_shares = [ on $(module) return $(LINK_LIBRARIES) ] ;
          local shared_dirs ;
          local link ;
          for link in [ FLinkModules [ on $(module) return $(LINK_MODULES) ] ] {
            local variant_name = [ FModuleSharedLibraryLinkName $(link) : $(variant) ] ;
            Depends $(libname) : lib$(variant_name)$(SUFSHARE) ;
            need_shares = [ FMergeList $(need_shares) : $(variant_name) ] ;
            need_shares = [ FMergeList $(need_shares) : [ on $(link:G=module) return $(LINK_LIBRARIES) ] ] ;
            local locate_shared = [ on $(link:G=module) return $(LOCATE_SHARED) ] ;
            shared_dirs = [ FMergeList $(shared_dirs) : $(locate_shared) ] ;
          }
          for shared_dir in $(shared_dirs) {
            LINKFLAGS on $(libname) += $(LINKER_PASSTHROUGH_FLAG)$(LIBRARY_PATH_SEARCH_FLAG)$(shared_dir) ;
          }
          NEEDSHARES on $(libname) = $(need_shares) ;
          if $(variant) in $(VARIANTS) {
            ModuleSharedLibraryFromObjects $(libname) : $(outputs) ;
          } else {
            ModuleSharedLibraryFromObjects $(libname) : $(outputs) : false ;
          }
        }
        if $(library) = STATIC {
          if $(variant) in $(VARIANTS) {
            ModuleStaticLibraryFromObjects $(libname) : $(outputs) ;
          } else {
            ModuleStaticLibraryFromObjects $(libname) : $(outputs) : false ;
          }
        }

        if $(variant) in $(VARIANTS) {
          if $(library) = NONE {
            Depends $(module) : $(files) ;
          } else {
            Depends $(module) : $(libname) ;
          }
        }
      }
    }
  }
}

rule FModuleStaticLibraryName {
  local module = $(1) ;
  local variant = $(2) ;

  local name = $(module:G=) ;
  local libname = [ FVariantName $(name) : $(variant) ] ;
  return lib$(libname)$(SUFLIB) ;
}

rule ModuleStaticLibraryFromObjects {
  local name = $(1) ;
  local files = $(2) ;
  local suppress_global_dependencies = $(3) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleStaticLibraryFromObjects $(name) . $(files) ;
  }

  if $(files) {
    LibraryFromObjects $(name) : $(files) : $(suppress_global_dependencies) ;
  }
}

rule FModuleSharedLibraryLinkName {
  local module = $(1) ;
  local variant = $(2) ;

  local name = $(module:G=) ;
  local libname = [ FVariantName $(name) : $(variant) ] ;
  return $(libname) ;
}

rule FModuleSharedLibraryName {
  local module = $(1) ;
  local variant = $(2) ;

  local libname = [ FModuleSharedLibraryLinkName $(module) : $(variant) ] ;
  return lib$(libname)$(SUFSHARE) ;
}

rule ModuleSharedLibraryFromObjects {
  local name = $(1) ;
  local files = $(2) ;
  local suppress_global_dependencies = $(3) ;

  if $(TRACE_MODULE) = TRUE {
    Echo ModuleSharedLibraryFromObjects $(name) . $(files) ;
    Echo LINKFLAGS . $(LINKFLAGS) ;
  }

  if $(files) {
    SharedLibraryFromObjects $(name) : $(files) : $(suppress_global_dependencies)  ;
  }
}

} # ModuleRules_INCLUDED
