#
#  Shared definitions for building with JAM.
#

TOOLS_DIR ?= ../../tools ;
CONV_DIR ?= $(TOOLS_DIR)/conv ;
GBA_DIR ?= .. ;

if ( $(VERBOSITY) == "" ) VERBOSITY = ;

# The tools we are going to use ----------------------------

CC       ?= gcc.exe ;
CC32     ?= arm-elf-gcc.exe ;
LINKER   ?= ar.exe ;
AS       ?= as.exe ;
OBJCOPY  ?= objcopy.exe ;
CONV     ?= $(CONV_DIR)/bin/conv.exe ;
BRSEDIT  ?= $(TOOLS_DIR)/brs/brs_edit.exe "-v $(VERBOSITY)" ;
PERL     ?= perl ;

OBJ_DIR  ?= obj/ ;

# macros to control output ---------------------------------

# SILENCE = " | sed -n -e 's/warning/[1;31mwarning[0m/p' -e 's/error/[1;31merror[0m/p'" ;

# the flags -----------------------------

INCLUDE_DIRS = $(INCLUDE_DIRS:A) ;

REL_INCLUDE_DIRS = $(INCLUDE_DIRS:X="[[:alpha:]]:.*") ;
ABS_INCLUDE_DIRS = $(INCLUDE_DIRS:I="[[:alpha:]]:.*") ;

COMMON_CCOPTS = -c -mthumb-interwork -W -Wall -Wno-unused --short-enums $(SAVE_TEMPS_FLAGS) ;

if ( $(CURFLAV) )  CURFLAV = $(CURFLAV:L) ;
else               CURFLAV = debug ;

if ( $(SAVE_TEMPS:L) != false )
  SAVE_TEMPS_FLAGS = -save-temps --verbose-asm ;

if ( !VERSION )
{
   #FIXME: get version id somehow
   VERID = "12" ; # $(shell svn info | grep Revision | sed "s/Revision: //g")
   FULLVERID = $(VERID).$(USERNAME).$(REGION).$(BUILD_CODE) ;
   VERSION = -DVERSION=\"$(FULLVERID)\" -DREGION=REGION_$(REGION) -DREGION$(REGION) ;
}

if ( $(CURFLAV) == release ) 
{
   # shipping builds: highest optimization level, assertion removed in all code.
   # minimal debugging support. local variables are not visible in the deset bugger
   # because we optimized out the frame pointer register. Special release
   # only definition turned on
   # -O3 is fastest
   # -Os is optimized for size.
   
   BUILD_CODE = r ;
   CCOPTS   =    -Os $(COMMON_CCOPTS) -DNDEBUG -DRELEASE $(VERSION) --omit-frame-pointer ;
   CC32OPTS =    -O3 $(COMMON_CCOPTS) -DNDEBUG -DRELEASE --omit-frame-pointer ;
   
} else if ( $(CURFLAV) == optimized ) {
   
   # mostly optimized, but we put the frame pointer back
   BUILD_CODE = o ;
   CCOPTS   = -g -Os $(COMMON_CCOPTS) $(VERSION) -DNDEBUG ;
   CC32OPTS = -g -O3 $(COMMON_CCOPTS) -DNDEBUG ;

} else if ( $(CURFLAV) == debug ) {

   # full debugging support in 16 bit code, and limited debugging support in 32 bit code.
   BUILD_CODE = d ;
   CCOPTS   = -g     $(COMMON_CCOPTS) $(VERSION) -DDEBUG ;
   CC32OPTS = -g -O3 $(COMMON_CCOPTS) -DNDEBUG ;
   
} else if ( $(CURFLAV) == extra_debug ) {

   # full debugging support in all code. Can be pretty slow. Also making 32-bit code
   # debug may cause cache memory to overflow.
   BUILD_CODE = x ;
   CCOPTS   = -g     $(COMMON_CCOPTS) $(VERSION) -DDEBUG ;
   CC32OPTS = -g     $(COMMON_CCOPTS) -DDEBUG ;
   
} else {

   exit "CURFLAV must be set to one of extra_debug, debug, optimized, or release." ;
}

# Note: some systems can be built with paranoid checking or in debugging mode
# that makes them super slow but will catch some hard problems. Look in the code.

ASFLAGS = -mthumb-interwork -gstabs ;

CONV_FLAGS = -p $(CONV_DIR)/converters -p $(CONV_DIR)/template "-v $(VERBOSITY)" ;

C_HDRPATTERN      = "#[\t ]*include[\t ]*[\"]([^\"]*)[\"]" ;
S_HDRPATTERN      = ".include[\t ]*[\"]([^\"]*)[\"]" ;
DL_HDRPATTERN     = "file[\t ]*=[\t ]*[\"]([^\"]*)[\"]" ;

HDRSEARCH         = $(INCLUDE_DIRS) ;

SEARCH            = $(SRC_DIRS) $(INCLUDE_DIRS) $(LIB_DIRS) ;
SEARCH on "clean" = . ;

notfile all clean clean_data ;
always clean clean_data ;

# if you are using a Dos shell, these temporary files get created and must be deleted
# CLEAN_LIST = .output $(OBJ_DIR)static/.output $(OBJ_DIR)shared/.output ;
CLEAN_LIST = ;

depends all : $(ALL_TARGETS) ;

# rules --------------------------------------------

rule Library TARGET : SOURCES : LIBS : TARGET_DIR
{
   ObjectSet $(TARGET) : $(SOURCES) : $(LIBS) : LinkLibrary ;
}

rule Executable TARGET : SOURCES : LIBS : TARGET_DIR
{
   ObjectSet $(TARGET) : $(SOURCES) : $(LIBS) : LinkExecutable ;
}

rule ObjectSet TARGET : SOURCES : LIBS : LINK_RULE : TARGET_DIR
{
   LOCATE on $(TARGET) = $(TARGET_DIR) ;
   
   if ( $(TARGET_DIR) ) TARGET_DIR = $(TARGET_DIR)/ ;
   else                 TARGET_DIR = "" ;
   
   # divide up the sources and objects
   
   local OBJECTS      = $(SOURCES:I="\.o$") ;
   local C_SOURCES    = $(SOURCES:I="\.c$") ;
   local S_SOURCES    = $(SOURCES:I="\.s$") ;
   local DL_SOURCES   = $(SOURCES:I="\.dl$") ; 
   local PROJ_SOURCES = $(SOURCES:I="\.proj$") ; 
   local C16_SOURCES  = $(C_SOURCES:X="32\.c$") ;
   local C32_SOURCES  = $(C_SOURCES:I="32\.c$") ;
   local S16_SOURCES  = $(S_SOURCES:X="32\.s$") ;
   local S32_SOURCES  = $(S_SOURCES:I="32\.s$") ;
   
   HDRSCAN on $(C_SOURCES)  = $(C_HDRPATTERN)  ;
   HDRRULE on $(C_SOURCES)  = C_HeaderRule ;
   HDRSIZE on $(C_SOURCES)  = "2048" ;                  # some generated .c files are very large, so we scan only the first two k.

   HDRSCAN on $(S_SOURCES)  = $(S_HDRPATTERN)  ;
   HDRRULE on $(S_SOURCES)  = S_HeaderRule ;
   
   HDRSCAN on $(DL_SOURCES) = $(DL_HDRPATTERN) ;
   HDRRULE on $(DL_SOURCES) = DL_HeaderRule ;
   
   # for sources with a grist, we search a subdir of that name:
   for ( I in $(SOURCES) )
   {
      if ( $(I:G) )
      {
         SEARCH           on $(I) += $(SEARCH)/$(I:G) ;
         HDRSEARCH        on $(I) += $(SEARCH) ;

//         on $(I)
//         {
//            print "set additional paths for $(I)" ;
//            echo $(SEARCH) ;
//            echo $(HDRSEARCH) ;
//         }
      }
   }   
   
   # compute dependencies
   for ( SRC_FILE in $(DL_SOURCES) )
   {
      C16_SOURCES += [ DlRule $(SRC_FILE:S=.c) : $(SRC_FILE) ] ;
      BRS_SPEC = $(DL_SOURCES:FD)/*.brs ;
      CLEAN_DATA_LIST += $(BRS_SPEC:M) ;
   }
   
   for ( SRC_FILE in $(PROJ_SOURCES) )
      OBJECTS += [ ProjRule $(SRC_FILE:S=.o) : $(SRC_FILE) ] ;
   
   for ( SRC_FILE in $(C16_SOURCES) )
      OBJECTS += [ Compile16Rule $(SRC_FILE:S=.o) : $(SRC_FILE) ] ;
   
   for ( SRC_FILE in $(C32_SOURCES) )
      OBJECTS += [ Compile32Rule $(SRC_FILE:N="32\.c$"0=".o32") : $(SRC_FILE) ] ;
   
   for ( SRC_FILE in $(S16_SOURCES) )
      OBJECTS += [ AssembleRule $(SRC_FILE:S=_asm.o) : $(SRC_FILE) ] ;
   
   for ( SRC_FILE in $(S32_SOURCES) )
      OBJECTS += [ AssembleRule $(SRC_FILE:S=_asm.o) : $(SRC_FILE) ] ;
      
   # bind localized objects explicitly
   for ( I in $(OBJECTS) )
   {
      if ( $(I:G) )
      {
         TMP = $(I:F="$(OBJ_DIR)$(BUILD_CODE)/$(I:G)_$(I:H)") ;
         
         INCLUDE_DIRS     on $(I) += $(INCLUDE_DIRS)/$(I:G) ;
         REL_INCLUDE_DIRS on $(I) += $(REL_INCLUDE_DIRS)/$(I:G) ;
         ABS_INCLUDE_DIRS on $(I) += $(ABS_INCLUDE_DIRS)/$(I:G) ;

//         on $(I)
//         {
//            print "set include paths for $(I)" ;
//            echo $(INCLUDE_DIRS) ;
//            echo $(REL_INCLUDE_DIRS) ;
//            echo $(ABS_INCLUDE_DIRS) ;
//         }
         
         # print $(I) explictly bound to $(I:F) ;
      }
   }   
   
   LOCATE on $(OBJECTS) = $(OBJ_DIR)$(BUILD_CODE) ;
   depends $(TARGET) : $(OBJECTS) $(LIBS) ;
   temporary $(OBJECTS) ;
   
   CLEAN_LIST += $(TARGET) ;
   
   $(LINK_RULE) $(TARGET) : $(OBJECTS) $(LIBS) ;
}

rule DlRule C_SOURCE : DL_SOURCES
{
   # This is rather complex. Making this work in normal cases is easy. But getting this all 
   # to work when a subset of the files is missing ( like just the .h file or just the .c file etc. )
   # can be quite tricky
   
   # The .dl file generates a .h and .c file. The file must be 
   # generated in the same directory as the dl file. So 'locate' the .c and .h file in
   # the bound directory of the .dl file. This causes the .dl file to be bound earlier than
   # normal. Which in this case is ok.
   H_SOURCE = $(C_SOURCE:S=.h) ;
   LOCATE on $(C_SOURCE) $(H_SOURCE) = $(DL_SOURCES:FD) ;
   
   # both the .c and .h file depent on the .dl source. So if the .dl source changes both files will 
   # need updating. It is not normal for the .c and .h files to be dependent on anything. But its true 
   # in this case
   depends $(C_SOURCE) $(H_SOURCE) : $(DL_SOURCES) ;
   
   # Both files are temporary intermediate files, but we only mark the .c file as such,
   # Marking a file which is only included as a temporary file makes the time stamp calculation code
   # defer to the timestamp of the .dl file. If the dl file is current, the .h file will not be 
   # regenerated, even though it might be needed for some .c file which needs to be compiled.
   # temporary $(C_SOURCE) ;
   
   # normally the auto header scanning code would catch that the .c file includes the .h file, but not
   # in this case, because it is possible that the .c file has not yet been generated. 
   # But this is in fact a good thing. Without this dependency they both get built at the same time - as they
   # should. Adding this dependency will cause the build action to run twice.
   # includes $(C_SOURCE) : $(H_SOURCE) ;
   
   # The .o file generated by the .c file depends on the .h as well. Again this is not normal. The normal
   # case is that the .0 file depends on just the .c and the .c includes the .h. We need the .o to depend
   # on the .h because of the strange case where the .h is missing, but the .c exists. Without this 
   # hilarity would ensue.
   depends $(C_SOURCE:S=.o) : $(H_SOURCE) ;
   
   # now we call the .dl action. This action has two targets which is also not normal.
   ProcessDl $(C_SOURCE) $(H_SOURCE) : $(DL_SOURCES) ;
   
   CLEAN_DATA_LIST += $(C_SOURCE) $(H_SOURCE) ;
   return $(C_SOURCE) ; 
}

rule ProjRule OBJECT : PROJ_SOURCE
{
   depends $(OBJECT) : $(PROJ_SOURCE) ;
   ProcessProj $(OBJECT) : $(PROJ_SOURCE) ;
   CLEAN_LIST += $(OBJECT) ;
   
   PROJ_NAMES_C = $(PROJ_SOURCE:B)_names.c ; 
   PROJ_NAMES_H = $(PROJ_SOURCE:B)_names.h ;
   PROJ_NAMES = $(PROJ_NAMES_C) $(PROJ_NAMES_H) ;
   LOCATE on $(PROJ_NAMES) = $(PROJ_SOURCE:FD) ;
   depends $(PROJ_NAMES) : $(PROJ_SOURCE:S=.h) ;
   depends $(PROJ_NAMES:S=.o) : $(PROJ_NAMES_H) ;
   depends $(PROJ_NAMES_C) : $(PROJ_NAMES_H) ;
   
   CLEAN_DATA_LIST += $(PROJ_NAMES) ;
   
   ProcessProjNames $(PROJ_NAMES) : $(PROJ_SOURCE:S=.h) ;
   
   return $(OBJECT) ;
}

rule Compile16Rule OBJECT : C_SOURCE
{
   depends $(OBJECT) : $(C_SOURCE) ;
   CompileObject16 $(OBJECT) : $(C_SOURCE) ;
   CLEAN_LIST += $(OBJECT) ;
   return $(OBJECT) ;
}

rule Compile32Rule OBJECT : C_SOURCE
{
   depends $(OBJECT) : $(C_SOURCE) ;
   CompileObject32 $(OBJECT) : $(C_SOURCE) ;
   CLEAN_LIST += $(OBJECT) ;
   return $(OBJECT) ;
}

rule AssembleRule OBJECT : S_SOURCE
{
   depends $(OBJECT) : $(S_SOURCE) ;
   AssembleObject $(OBJECT) : $(S_SOURCE) ;
   CLEAN_LIST += $(OBJECT) ;
   return $(OBJECT) ;
}

rule C_HeaderRule INCLUDER : INCLUDEDS
{
   includes $(INCLUDER) : $(INCLUDEDS) ;        
   HDRSCAN on $(INCLUDEDS) = $(C_HDRPATTERN) ; # recursively search headers
   HDRRULE on $(INCLUDEDS) = C_HeaderRule ;
   nocare $(INCLUDEDS) ;
}

rule S_HeaderRule INCLUDER : INCLUDEDS
{
   includes $(INCLUDER) : $(INCLUDEDS) ;        
   HDRSCAN on $(INCLUDEDS) = $(S_HDRPATTERN) ; # recursively search headers
   HDRRULE on $(INCLUDEDS) = S_HeaderRule ;
   nocare $(INCLUDEDS) ;
}

rule BrushRule BRUSH_FILES : DL_FILE
{
   BRUSH_DIR = $(DL_FILE:FD) ;
   LOCATE on $(BRUSH_FILES) = $(BRUSH_DIR) ;
   
   BRUSH_IMAGES = $(BRUSH_DIR)/brush/*/*.bmp $(BRUSH_DIR)/brush/*/*.png ;
   BRUSH_IMAGES = $(BRUSH_IMAGES:M) ;
   depends $(BRUSH_FILES) : $(BRUSH_IMAGES) ;
      
#   CLEAN_DATA_LIST += $(BRUSH_FILES) ;

   {
      SCRIPT = make_brushes.txt ;
      local UNMATCHED_BRUSHES ;
      
      while ( $(BRUSH_FILES) )
      {
         local FILE = $(BRUSH_FILES[0]) ;
         
         LSCRIPT = $(FILE:S=.txt) ;
         if ( $(LSCRIPT:M) )
         {
            ConvertBrush $(FILE) : $(LSCRIPT) ;
         } else {
            UNMATCHED_BRUSHES += $(FILE) ;
         }
         
         BRUSH_FILES = $(BRUSH_FILES[1-]) ;
      }
      
      if ( $(UNMATCHED_BRUSHES) )
         ConvertBrush $(UNMATCHED_BRUSHES) : $(SCRIPT) ;
   }
}

rule DL_HeaderRule INCLUDER : INCLUDEDS
{
   BRUSH_FILES = $(INCLUDEDS:I="\.brs$") ;
   IMAGE_FILES = $(INCLUDEDS:X="\.brs$") ;

   includes $(INCLUDER) : $(INCLUDEDS) ;
   
   BrushRule $(BRUSH_FILES) : $(INCLUDER) ; 
}

rule CleanRule TARGET : GENERATED_FILES : TARGET_DIR
{
   local LIST = $(TARGET) ;        # all the targets
   
   if ( $(TARGET_DIR) ) CLEAN_LIST += $(TARGET_DIR)$(TARGETS) ;
   else                 CLEAN_LIST += $(TARGETS) ;
   
   CLEAN_LIST += $(GENERATED_FILES) ;
}

# actions --------------------------------------------

action quietly CompileObject16 TARGET : SOURCE
{%
   echo Compiling  [$(BUILD_CODE)] $(TARGET:H) : $(SOURCE:H)
   $(CC) $(CCOPTS) -I$(REL_INCLUDE_DIRS) -I$(ABS_INCLUDE_DIRS) $(SOURCE) -o $(TARGET) $(SILENCE)
%}

action quietly CompileObject32 TARGET : SOURCE
{%
   echo Compiling  [$(BUILD_CODE)] $(TARGET:H) : $(SOURCE:H)
   $(CC) $(CC32OPTS) -I$(REL_INCLUDE_DIRS) -I$(ABS_INCLUDE_DIRS) $(SOURCE) -o $(TARGET) $(SILENCE)
%}

action quietly AssembleObject TARGET : SOURCE
{%
   echo Assembling [$(BUILD_CODE)] $(TARGET:H) : $(SOURCE:H)
   $(AS) $(ASFLAGS) -I$(REL_INCLUDE_DIRS) -I$(ABS_INCLUDE_DIRS) -o $(TARGET) $(SOURCE) $(SILENCE)
   $(PROCESS_SILENCE)
%}

action quietly LinkLibrary TARGET : SOURCES
{%
   echo Linking        $(TARGET:H) : $(SOURCES:H)
   $(LINKER) -rs $(TARGET) $(SOURCES) $(SILENCE)
%}

action quietly LinkExecutable TARGET : SOURCES
{%
   echo Linking        $(TARGET:H) : $(SOURCES:H) 
   $(LINKER) -rs $(TARGET) $(SOURCES) $(SILENCE)
%}

action piecemeal existing quietly Clean TARGET : SOURCES
{%
   echo Cleaning       $(SOURCES)
   rm -f $(SOURCES:C)
%}

action quietly ProcessDl TARGET : SOURCES
{%
   echo Convert        $(TARGET:H) : $(SOURCES:H)
   $(CONV) $(CONV_FLAGS) -d$(SOURCES) -w $(TARGET[1]:D) -o $(TARGET[1]:B)
%}

action quietly ProcessProj TARGET : SOURCE
{%
   echo Convert        $(TARGET:H) : $(SOURCE:H)
   $(OBJCOPY) -v -I binary --add-section .music=$(SOURCE) --remove-section=.data -O elf32-little $(SOURCE) $(TARGET) | echo
%}

action quietly ProcessProjNames TARGET : SOURCE
{%
   echo Convert        $(TARGET:H) : $(SOURCE:H)
   $(PERL) $(GBA_DIR)/musyx/extract_names.pl   < $(SOURCE) > $(TARGET[0])
   $(PERL) $(GBA_DIR)/musyx/extract_names_h.pl < $(SOURCE) > $(TARGET[1])
%}

action quietly ConvertBrush TARGETS : SCRIPT
{%
   echo ConvertBrush   $(TARGETS:H) : $(SCRIPT)
   $(BRSEDIT) -s -p brush -w $(TARGETS[0]:D) -i $(SCRIPT:H)
%}

