import os
import subprocess

PREFIX = "arm-none-eabi-"
BUILDER = "DEV"

common_flags = []
build_projects = {}

build_projects["body"] = {
  "MAIN": "main.c",
  "STARTUP_FILE": "startup_stm32f413xx.s",
  "LINKER_SCRIPT": "stm32fx_flash.ld",
  "APP_START_ADDRESS": "0x08004000",
  "PROJECT_FLAGS": [
    "-mcpu=cortex-m4",
    "-mhard-float",
    "-DSTM32F4",
    "-DSTM32F413xx",
    "-mfpu=fpv4-sp-d16",
    "-fsingle-precision-constant",
    "-Os",
    "-g",
  ],
}

if os.getenv("RELEASE"):
  BUILD_TYPE = "RELEASE"
  cert_fn = os.getenv("CERT")
  assert cert_fn is not None, 'No certificate file specified. Please set CERT env variable'
  assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path'
else:
  BUILD_TYPE = "DEBUG"
  cert_fn = File("../certs/debug").srcnode().relpath
  common_flags += ["-DALLOW_DEBUG"]

if os.getenv("DEBUG"):
  common_flags += ["-DDEBUG"]

includes = [
  "inc/STM32F4xx_HAL_Driver/Inc",
  "inc",
  "..",
  ".",
]

c_sources = [
  ["hal_flash", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c"],
  ["hal_pwr", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c"],
  ["hal_rcc", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c"],
  ["hal_i2c", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c"],
  ["hal_i2c_ex", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c"],
  ["hal_tim", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c"],
  ["hal_tim_ex", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c"],
  ["hal_adc_ex", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc_ex.c"],
  ["hal_cortex", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c"],
  ["hal_flash_ex", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c"],
  ["hal_gpio", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c"],
  ["hal_rcc_ex", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c"],
  ["hal", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c"],
  ["hal_adc", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc.c"],
  ["hal_dma", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c"],
  ["system", "inc/system_stm32f4xx.c"],
  ["it", "inc/stm32f4xx_it.c"],
  ["bldc", "bldc/bldc.c"],
  ["bldc_data", "bldc/BLDC_controller_data.c"],
  ["bldc_con", "bldc/BLDC_controller.c"],
  ["util", "util.c"],
]

c_bstub_sources = [
  ["hal_pwr", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c"],
  ["hal_rcc", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c"],
  ["hal_cortex", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c"],
  ["hal_gpio", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c"],
  ["hal_rcc_ex", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c"],
  ["hal", "inc/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c"],
  ["system", "inc/system_stm32f4xx.c"],
  ["it", "inc/stm32f4xx_it.c"],
  ["util", "util.c"],
]

def get_version(builder, build_type):
  try:
    git = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], encoding='utf8').strip()
  except subprocess.CalledProcessError:
    git = "00000000"
  return f"{git}"


def to_c_uint32(x):
  nums = []
  for _ in range(0x20):
    nums.append(x % (2**32))
    x //= (2**32)
  return "{" + 'U,'.join(map(str, nums)) + "U}"


def get_key_header(name):
  from Crypto.PublicKey import RSA

  public_fn = f'../certs/{name}.pub'
  rsa = RSA.importKey(open(public_fn).read())
  assert(rsa.size_in_bits() == 1024)

  rr = pow(2**1024, 2, rsa.n)
  n0inv = 2**32 - pow(rsa.n, -1, 2**32)

  r = [
    f"RSAPublicKey {name}_rsa_key = {{",
    f"  .len = 0x20,",
    f"  .n0inv = {n0inv}U,",
    f"  .n = {to_c_uint32(rsa.n)},",
    f"  .rr = {to_c_uint32(rr)},",
    f"  .exponent = {rsa.e},",
    f"}};",
  ]
  return r

def objcopy(source, target, env, for_signature):
    return '$OBJCOPY -O binary %s %s' % (source[0], target[0])

# Common autogenerated includes
os.makedirs("obj", exist_ok=True)
git_ver = get_version(BUILDER, BUILD_TYPE)
with open("obj/gitversion.h", "w") as f:
  f.write(f'const uint8_t gitversion[8] = "{git_ver}";\n')

certs = [get_key_header(n) for n in ["debug", "release"]]
with open("obj/cert.h", "w") as f:
  for cert in certs:
    f.write("\n".join(cert) + "\n")

for project_name in build_projects:
  project = build_projects[project_name]
  linkerscript_fn = File(project["LINKER_SCRIPT"]).srcnode().relpath

  flags = [
    "-Wall",
    "-Wextra",
    "-Wstrict-prototypes",
    "-Werror",
    "-mlittle-endian",
    "-mthumb",
    "-nostdlib",
    "-fno-builtin",
    f"-T{linkerscript_fn}",
    "-std=gnu11",
    "-fdata-sections",
    "-ffunction-sections",
    "-Wl,--gc-sections",
  ] + project["PROJECT_FLAGS"] + common_flags

  project_env = Environment(
    ENV=os.environ,
    CC=PREFIX + 'gcc',
    AS=PREFIX + 'gcc',
    OBJCOPY=PREFIX + 'objcopy',
    OBJDUMP=PREFIX + 'objdump',
    ASCOM="$AS $ASFLAGS -o $TARGET -c $SOURCES",
    CFLAGS=flags,
    ASFLAGS=flags,
    LINKFLAGS=flags,
    LIBS = ['gcc',],
    CPPPATH=includes,
    BUILDERS={
      'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf')
    }
  )
  startup = project_env.Object(project["STARTUP_FILE"])

  c_bstub_obj_list = []
  for c in c_bstub_sources:
    c_bstub_obj_list.append(project_env.Object(f"{c[0]}-{project_name}", c[1]))

  # Bootstub
  crypto_obj = [
    project_env.Object(f"rsa-{project_name}", "../crypto/rsa.c"),
    project_env.Object(f"sha-{project_name}", "../crypto/sha.c")
  ]
  bootstub_obj = project_env.Object(f"bootstub-{project_name}", "bootstub.c")
  bootstub_elf = project_env.Program(f"obj/bootstub.{project_name}.elf", [startup] + crypto_obj + c_bstub_obj_list + [bootstub_obj])
  bootstub_bin = project_env.Objcopy(f"obj/bootstub.{project_name}.bin", bootstub_elf)

  c_obj_list = []
  for c in c_sources:
    c_obj_list.append(project_env.Object(f"{c[0]}-{project_name}", c[1]))

  # Build main
  main_obj = project_env.Object(f"main-{project_name}", project["MAIN"])
  obj_list = [startup, main_obj] + c_obj_list
  main_elf = project_env.Program(f"obj/{project_name}.elf", obj_list,
    LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags)
  main_bin = project_env.Objcopy(f"obj/{project_name}.bin", main_elf)

  # Sign main
  sign_py = File("../crypto/sign.py").srcnode().relpath
  panda_bin_signed = project_env.Command(f"obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}")
