#! /usr/bin/env python
# -*- coding: UTF-8 -*-
#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*

import sys, os, subprocess, shutil, json

#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*

import makefile
import common_definitions
import download_and_install_gccarm
import teensy_cli_loader_builder
import dev_platform

#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*
#   Run process and wait for termination                                                                               *
#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*

def runProcess (command) :
  returncode = subprocess.call (command)
  if returncode != 0 :
    print (makefile.BOLD_RED () + "Error " + str (returncode) + makefile.ENDC ())
    sys.exit (returncode)

#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*
#   Run process, get output and wait for termination                                                                   *
#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*

def runProcessAndGetOutput (command) :
  result = ""
  childProcess = subprocess.Popen (command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  while True:
    out = childProcess.stdout.read(1)
    if out == '' and childProcess.poll() != None:
      break
    if out != '':
      result += out
#--- Wait for subprocess termination
  if childProcess.poll () == None :
    childProcess.wait ()
  if childProcess.returncode != 0 :
    print (makefile.BOLD_RED () + "Error " + str (childProcess.returncode) + makefile.ENDC ())
    sys.exit (childProcess.returncode)
  return result

#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*
#   dictionaryFromJsonFile                                                                                             *
#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*

def dictionaryFromJsonFile (file) :
  result = {}
  if not os.path.exists (os.path.abspath (file)):
    print (makefile.BOLD_RED () + "The '" + file + "' file does not exist" + makefile.ENDC ())
    sys.exit (1)
  try:
    f = open (file, "r")
    result = json.loads (f.read ())
    f.close ()
  except:
    print (makefile.BOLD_RED () + "Syntax error in " + file + makefile.ENDC ())
    sys.exit (1)
  return result


#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*
#   buildCode                                                                                                          *
#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*

def buildCode (GOAL, projectDir, maxConcurrentJobs, showCommand):
#--------------------------------------------------------------------------- Prepare
  os.chdir (projectDir)
  make = makefile.Make (GOAL)
  make.mMacTextEditor = "TextWrangler" # "Atom"
  allGoal = []
#--------------------------------------------------------------------------- Install Linux UDEV rules ?
  platform = dev_platform.getPlatform ()
  if (platform == "linux") or (platform == "linux32") :
    import udev_on_linux
    udev_on_linux.installUDEVrulesOnLinux ()
#--------------------------------------------------------------------------- Install compiler ?
  BASE_NAME = "arm-none-eabi"
  TOOL_DIR = download_and_install_gccarm.installGCCARMandGetToolDirectory ()
  AS_TOOL_WITH_OPTIONS = [TOOL_DIR + "/bin/" + BASE_NAME + "-as", "-mthumb", "-mcpu=cortex-m4"]
  COMPILER_TOOL_WITH_OPTIONS = [TOOL_DIR + "/bin/" + BASE_NAME + "-gcc", "-mthumb", "-mcpu=cortex-m4"]
  LD_TOOL_WITH_OPTIONS = [TOOL_DIR + "/bin/" + BASE_NAME + "-ld"]
  LD_TOOL_WITH_OPTIONS = COMPILER_TOOL_WITH_OPTIONS
  OBJCOPY_TOOL_WITH_OPTIONS = [TOOL_DIR + "/bin/" + BASE_NAME + "-objcopy"]
  DISPLAY_OBJ_SIZE_TOOL = [TOOL_DIR + "/bin/" + BASE_NAME + "-size"]
  OBJDUMP_TOOL = TOOL_DIR + "/bin/" + BASE_NAME + "-objdump"
#--------------------------------------------------------------------------- Install Teensy loader CLI ?
  TEENSY_CLI_LOADER_PATH = teensy_cli_loader_builder.buildAndGetPath (TOOL_DIR + "/bin")
#--------------------------------------------------------------------------- Analyze JSON file
  print (makefile.BOLD_GREEN () + "--- Making " + projectDir + makefile.ENDC ())
  dictionaire = dictionaryFromJsonFile (projectDir + "/makefile.json")
#--- TEENSY
  linkerScript = ""
  teensyName = ""
  if dictionaire.has_key ("TEENSY") :
    TeensyKey = dictionaire ["TEENSY"]
    if (TeensyKey == "3.1") or (TeensyKey == "3.2") :
      teensyName = "TEENSY31"
      linkerScript = "teensy-3-1.ld"
    elif (TeensyKey == "3.5") :
      teensyName = "TEENSY35"
      linkerScript = "teensy-3-5.ld"
    elif (TeensyKey == "3.6") :
      teensyName = "TEENSY36"
      linkerScript = "teensy-3-6.ld"
    else:
      make.enterError ('In the makefile.json file, the "TEENSY" key value is invalid (possible values: "3.1", "3.2", "3.5" or "3.6").')
  else:
    make.enterError ('The makefile.json file does not have a "TEENSY" key (possible values: "3.1", "3.2", "3.5" or "3.6").')
#--- ASSERTION_GENERATION
  ASSERTION_GENERATION = False
  if dictionaire.has_key ("ASSERTION-GENERATION") and dictionaire ["ASSERTION-GENERATION"] :
    ASSERTION_GENERATION = True
#--- CPU_MHZ
  CPU_MHZ = 0
  if dictionaire.has_key ("CPU-MHZ") :
    CPU_MHZ = dictionaire ["CPU-MHZ"]
#--- SOURCE_FILE_DIRECTORIES
  SOURCE_FILE_DIRECTORIES = []
  if dictionaire.has_key ("SOURCE-DIR") :
    SOURCE_FILE_DIRECTORIES = dictionaire ["SOURCE-DIR"]
#--- GROUP_SOURCES
  GROUP_SOURCES = False
  if dictionaire.has_key ("GROUP-SOURCES") :
    GROUP_SOURCES = dictionaire ["GROUP-SOURCES"]
#--- TASK_COUNT
  TASK_COUNT = "0" # Means TASK_COUNT is not defined by JSON file
  if dictionaire.has_key ("TASK-COUNT") :
    TASK_COUNT = str (dictionaire ["TASK-COUNT"])
#--- LTO
  usesLTO = False
  if dictionaire.has_key ("LTO") and dictionaire ["LTO"] :
    usesLTO = True
#--- SERVICE
  serviceScheme = ""
  if dictionaire.has_key ("SERVICE-SCHEME") :
    serviceScheme = dictionaire ["SERVICE-SCHEME"]
#--- SECTION
  sectionScheme = ""
  if dictionaire.has_key ("SECTION-SCHEME") :
    sectionScheme = dictionaire ["SECTION-SCHEME"]
#--------------------------------------------------------------------------- Directories
  BUILD_DIR = common_definitions.buildDirectory ()
  GENERATED_SOURCE_DIR = common_definitions.generatedSourceDirectory ()
  PRODUCT_DIR = common_definitions.productDirectory ()
  ASBUILD_DIR = common_definitions.asDirectory ()
#--------------------------------------------------------------------------- Build source lists
  includeDirsInCompilerCommand = ["-I", GENERATED_SOURCE_DIR, "-I", projectDir]
  H_SOURCE_LIST = []
  CPP_SOURCE_LIST = []
  S_SOURCE_LIST = []
  for f in SOURCE_FILE_DIRECTORIES :
    for root, dirs, files in os.walk (f) :
      includeDirsInCompilerCommand += ["-I", root]
      for name in files:
        sourcePath = os.path.join (root, name)
        (b, extension) = os.path.splitext (sourcePath)
        if extension == ".cpp" :
          CPP_SOURCE_LIST.append (sourcePath)
        elif extension == ".h" :
          H_SOURCE_LIST.append (sourcePath)
        elif extension == ".s" :
          S_SOURCE_LIST.append (sourcePath)
        elif extension != "" : # Ceci permet d'ignorer les fichés cachés (dont les noms commencent par un point)
          print (makefile.MAGENTA () + makefile.BOLD () + "Note: unhandled file " + sourcePath + makefile.ENDC ())
#--------------------------------------------------------------------------- Build base header file
  baseHeader_file = GENERATED_SOURCE_DIR + "/base.h"
  H_SOURCE_LIST.insert (0, baseHeader_file)
  rule = makefile.Rule ([baseHeader_file], "Build base header file")
  rule.mOpenSourceOnError = False
  rule.mDependences.append ("makefile.json")
  rule.mCommand += ["../dev-files/build_base_header_file.py", baseHeader_file, str (CPU_MHZ), TASK_COUNT, teensyName, "1" if ASSERTION_GENERATION else "0"]
  rule.mPriority = -1
  make.addRule (rule)
#--------------------------------------------------------------------------- Build all header file
  allHeadersSecondaryDependenceFile = BUILD_DIR + "/all-headers.dep"
  allHeaders_file = GENERATED_SOURCE_DIR + "/all-headers.h"
  rule = makefile.Rule ([allHeaders_file, allHeadersSecondaryDependenceFile], "Build all headers file")
  rule.mOpenSourceOnError = False
  rule.mDependences.append ("makefile.json")
  rule.mDependences += H_SOURCE_LIST
  rule.mCommand += ["../dev-files/build_all_header_file.py", allHeaders_file, allHeadersSecondaryDependenceFile]
  rule.mCommand += H_SOURCE_LIST
  rule.enterSecondaryDependanceFile (allHeadersSecondaryDependenceFile, make)
  rule.mPriority = -1
  make.addRule (rule)
#--------------------------------------------------------------------------- Build interrupt handler files
  interruptHandlerSFile = GENERATED_SOURCE_DIR + "/interrupt-handlers.s"
  interruptHandlerCppFile = GENERATED_SOURCE_DIR + "/interrupt-handler-helper.cpp"
  S_SOURCE_LIST.append (interruptHandlerSFile)
  CPP_SOURCE_LIST.append (interruptHandlerCppFile)
  rule = makefile.Rule ([interruptHandlerSFile, interruptHandlerCppFile], "Build interrupt files")
  rule.mOpenSourceOnError = False
  rule.mDependences += H_SOURCE_LIST
  rule.mDependences.append ("makefile.json")
  rule.mDependences.append ("../dev-files/build_interrupt_handlers.py")
  rule.mCommand += ["../dev-files/build_interrupt_handlers.py"]
  rule.mCommand += [interruptHandlerCppFile]
  rule.mCommand += [interruptHandlerSFile]
  rule.mCommand += [serviceScheme]
  rule.mCommand += [sectionScheme]
  rule.mCommand += H_SOURCE_LIST
  rule.mPriority = -1
  make.addRule (rule)
#--------------------------------------------------------------------------- Group sources ?
  if GROUP_SOURCES :
    allSourceFile = GENERATED_SOURCE_DIR + "/all-sources.cpp"
    rule = makefile.Rule ([allSourceFile], "Group all sources")
    rule.mOpenSourceOnError = False
    rule.mDependences += CPP_SOURCE_LIST
    rule.mDependences.append ("makefile.json")
    rule.mCommand += ["../dev-files/build_grouped_sources.py", allSourceFile]
    rule.mCommand += CPP_SOURCE_LIST
    rule.mPriority = -1
    make.addRule (rule)
    CPP_SOURCE_LIST = [allSourceFile]
#--------------------------------------------------------------------------- Build makefile rules
  objectFileList = []
  asObjectFileList = []
#--- CPP source files
  for sourcePath in CPP_SOURCE_LIST :
    source = os.path.basename (sourcePath)
    objectFile = BUILD_DIR + "/" + source + ".o"
    objectFileForChecking = BUILD_DIR + "/" + source + ".check.o"
    asObjectFile = BUILD_DIR + "/" + source + ".s"
  #--- Checking source
    rule = makefile.Rule ([objectFileForChecking], "Checking " + source)
    rule.mOpenSourceOnError = False
    rule.mDependences.append (allHeaders_file)
    rule.mDependences.append (sourcePath)
    rule.mDependences.append ("makefile.json")
    rule.enterSecondaryDependanceFile (objectFileForChecking + ".dep", make)
    rule.mCommand += COMPILER_TOOL_WITH_OPTIONS
    rule.mCommand += common_definitions.checkModeOptions ()
    rule.mCommand += common_definitions.C_Cpp_optimizationOptions ()
    rule.mCommand += common_definitions.Cpp_actualOptions (False)
    rule.mCommand += ["-c", sourcePath]
    rule.mCommand += ["-o", objectFileForChecking]
    rule.mCommand += ["-DSTATIC="]
    rule.mCommand += includeDirsInCompilerCommand
    rule.mCommand += ["-MD", "-MP", "-MF", objectFileForChecking + ".dep"]
    make.addRule (rule)
    rule.mPriority = -1
    allGoal.append (objectFileForChecking)
 #--- Compile source
    rule = makefile.Rule ([objectFile], "Compiling " + source)
    rule.mOpenSourceOnError = False
    rule.mCommand += COMPILER_TOOL_WITH_OPTIONS
    rule.mCommand += common_definitions.C_Cpp_optimizationOptions ()
    rule.mCommand += common_definitions.Cpp_actualOptions (usesLTO)
    rule.mCommand += ["-g"]
    rule.mCommand += ["-c", sourcePath]
    rule.mCommand += ["-o", objectFile]
    rule.mCommand += ["-DSTATIC=static __attribute__((unused))"] if GROUP_SOURCES else ["-DSTATIC="]
    rule.mCommand += includeDirsInCompilerCommand
    rule.mCommand += ["-MD", "-MP", "-MF", objectFile + ".dep"]
    rule.mDependences.append (allHeaders_file)
    rule.mDependences.append (sourcePath)
    rule.mDependences.append ("makefile.json")
    rule.enterSecondaryDependanceFile (objectFile + ".dep", make)
    make.addRule (rule)
    objectFileList.append (objectFile)
  #--- objdump python source
    objdumpPythonFile = BUILD_DIR + "/" + source + ".objdump.py"
    rule = makefile.Rule ([objdumpPythonFile], "Building " + source + ".objdump.py")
    rule.mDependences.append (objectFile)
    rule.mDependences.append ("makefile.json")
    rule.mCommand += ["../dev-files/build_objdump.py", OBJDUMP_TOOL, source, objdumpPythonFile]
    rule.mPriority = -1
    make.addRule (rule)
    allGoal.append (objdumpPythonFile)
  #--- AS rule
    rule = makefile.Rule ([asObjectFile], "Compiling -> s " + source)
    rule.mOpenSourceOnError = False
    rule.mCommand += COMPILER_TOOL_WITH_OPTIONS
    rule.mCommand += common_definitions.C_Cpp_optimizationOptions ()
    rule.mCommand += common_definitions.Cpp_actualOptions (usesLTO)
    rule.mCommand += ["-S", sourcePath]
    rule.mCommand += ["-o", asObjectFile]
    rule.mCommand += ["-DSTATIC="]
    rule.mCommand += includeDirsInCompilerCommand
    rule.mCommand += ["-MD", "-MP", "-MF", asObjectFile + ".dep"]
    rule.mDependences.append (sourcePath)
    rule.mDependences.append (allHeaders_file)
    rule.mDependences.append ("makefile.json")
    rule.enterSecondaryDependanceFile (asObjectFile + ".dep", make)
    make.addRule (rule)
  #--- AS rule, getting output assembler file
    listingFile = ASBUILD_DIR + "/" + source + ".s.list"
    rule = makefile.Rule ([listingFile], "Assembling -> listing " + source)
    rule.mOpenSourceOnError = False
    rule.mCommand += AS_TOOL_WITH_OPTIONS
    rule.mCommand += [asObjectFile]
    rule.mCommand += ["-o", "/dev/null"]
    rule.mCommand += ["-aln=" + listingFile]
    rule.mDependences.append (asObjectFile)
    rule.mDependences.append (allHeaders_file)
    rule.mDependences.append ("makefile.json")
    make.addRule (rule)
    asObjectFileList.append (listingFile)
#-- Add ARM S files
  for sourcePath in S_SOURCE_LIST :
    source = os.path.basename (sourcePath)
    objectFile = BUILD_DIR + "/" + source + ".o"
    objectFileForChecking = BUILD_DIR + "/" + source + ".check.o"
    asObjectFile = ASBUILD_DIR + "/" + source + ".s"
    if sourcePath != "" :
      rule = makefile.Rule ([objectFile], "Assembling " + source)
      rule.mOpenSourceOnError = False
      rule.mCommand += AS_TOOL_WITH_OPTIONS
      rule.mCommand += [sourcePath]
      rule.mCommand += ["-o", objectFile]
      rule.mCommand += includeDirsInCompilerCommand
      rule.mCommand += ["--MD", objectFile + ".dep"]
      rule.mDependences.append (sourcePath)
      rule.mDependences.append ("makefile.json")
      rule.enterSecondaryDependanceFile (objectFile + ".dep", make)
      make.addRule (rule)
      objectFileList.append (objectFile)
    #--- Add listing file
      listingFile = ASBUILD_DIR + "/" + source + ".list"
      rule = makefile.Rule ([listingFile], "Assembling -> listing " + source)
      rule.mOpenSourceOnError = False
      rule.mCommand += AS_TOOL_WITH_OPTIONS
      rule.mCommand += [sourcePath]
      rule.mCommand += ["-o", "/dev/null"]
      rule.mCommand += ["-aln=" + listingFile]
      rule.mDependences.append (sourcePath)
      rule.mDependences.append ("makefile.json")
      make.addRule (rule)
      asObjectFileList.append (listingFile)
#--------------------------------------------------------------------------- Link for internal flash
  PRODUCT_INTERNAL_FLASH = PRODUCT_DIR + "/product"
  LINKER_SCRIPT_INTERNAL_FLASH = "../dev-files/" + linkerScript
  allGoal.append (PRODUCT_INTERNAL_FLASH + ".elf")
#--- Add link rule
  rule = makefile.Rule ([PRODUCT_INTERNAL_FLASH + ".elf"], "Linking " + PRODUCT_INTERNAL_FLASH + ".elf")
  rule.mDependences += objectFileList
  rule.mDependences.append (LINKER_SCRIPT_INTERNAL_FLASH)
  rule.mDependences.append ("makefile.json")
  rule.mCommand += LD_TOOL_WITH_OPTIONS
  rule.mCommand += objectFileList
  rule.mCommand += ["-T" + LINKER_SCRIPT_INTERNAL_FLASH]
  rule.mCommand.append ("-Wl,-Map=" + PRODUCT_INTERNAL_FLASH + ".map")
  rule.mCommand += common_definitions.commonLinkerFlags (usesLTO)
  rule.mCommand += ["-o", PRODUCT_INTERNAL_FLASH + ".elf"]
  make.addRule (rule)
#--- Add hex rule
  allGoal.append (PRODUCT_INTERNAL_FLASH + ".hex")
  rule = makefile.Rule ([PRODUCT_INTERNAL_FLASH + ".hex"], "Hexing " + PRODUCT_INTERNAL_FLASH + ".hex")
  rule.mDependences.append (PRODUCT_INTERNAL_FLASH + ".elf")
  rule.mDependences.append ("makefile.json")
  rule.mCommand += OBJCOPY_TOOL_WITH_OPTIONS
  rule.mCommand.append ("-O")
  rule.mCommand.append ("ihex")
  rule.mCommand.append (PRODUCT_INTERNAL_FLASH + ".elf")
  rule.mCommand.append (PRODUCT_INTERNAL_FLASH + ".hex")
  make.addRule (rule)
#--------------------------------------------------------------------------- Goals
  make.addGoal ("all", allGoal, "Build all")
  make.addGoal ("run", allGoal, "Building all and run")
  make.addGoal ("view-hex", allGoal, "Building all and show hex")
  make.addGoal ("display-obj-size", allGoal, "Build binaries and display object sizes")
  make.addGoal ("as", asObjectFileList, "Compile C and C++ to assembly")
#--------------------------------------------------------------------------- Run jobs
  #make.printRules ()
  #make.checkRules ()
#   make.writeRuleDependancesInDotFile ("dependances.dot")
  make.runGoal (maxConcurrentJobs, showCommand)
#--------------------------------------------------------------------------- Ok ?
  make.printErrorCountAndExitOnError ()
#---------------------------------------------------------------------------- "display-obj-size"
  if GOAL == "display-obj-size" :
    makefile.runCommand (DISPLAY_OBJ_SIZE_TOOL + objectFileList + ["-t"], "Display Object Size", False, showCommand)
#---------------------------------------------------------------------------- "All" or "run"
  if (GOAL == "all") or (GOAL == "run") or (GOAL == "view-hex") :
    s = runProcessAndGetOutput (DISPLAY_OBJ_SIZE_TOOL + ["-t"] + [PRODUCT_INTERNAL_FLASH + ".elf"])
    secondLine = s.split('\n')[1]
    numbers = [int(s) for s in secondLine.split() if s.isdigit()]
    print ("  ROM code:    " + str (numbers [0]) + " bytes")
    print ("  ROM data:    " + str (numbers [1]) + " bytes")
    print ("  RAM + STACK: " + str (numbers [2]) + " bytes")
#----------------------------------------------- Run ?
  if GOAL == "run":
    FLASH_TEENSY = [TEENSY_CLI_LOADER_PATH, "-w", "-v", "-mmcu=TEENSY36"]
    print (makefile.BOLD_BLUE () + "Loading Teensy..." + makefile.ENDC ())
    runProcess (FLASH_TEENSY + [PRODUCT_INTERNAL_FLASH + ".hex"])
    print (makefile.BOLD_GREEN () + "Success" + makefile.ENDC ())
  elif GOAL == "view-hex":
    print (makefile.BOLD_GREEN () + "View hex..." + makefile.ENDC ())
    scriptDir = os.path.dirname (os.path.abspath (__file__))
    runProcess (["python", scriptDir+ "/view-hex.py", PRODUCT_INTERNAL_FLASH + ".hex"])

#——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*