import os
import copy
import subprocess

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

common_flags = []
build_projects = {}

build_projects["pedal"] = {
  "MAIN": "pedal/main.c",
  "STARTUP_FILE": "stm32fx/startup_stm32f205xx.s",
  "LINKER_SCRIPT": "stm32fx/stm32f2_flash.ld",
  "APP_START_ADDRESS": "0x8004000",
  "PROJECT_FLAGS": [
    "-mcpu=cortex-m3",
    "-msoft-float",
    "-DSTM32F2",
    "-DSTM32F205xx",
    "-O2",
    "-DPEDAL",
  ],
}

build_projects["pedal_usb"] = copy.deepcopy(build_projects["pedal"])
build_projects["pedal_usb"]["PROJECT_FLAGS"].append("-DPEDAL_USB")

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

build_projects["panda_h7"] = {
  "MAIN": "main.c",
  "STARTUP_FILE": "stm32h7/startup_stm32h7x5xx.s",
  "LINKER_SCRIPT": "stm32h7/stm32h7x5_flash.ld",
  "APP_START_ADDRESS": "0x8020000",
  "PROJECT_FLAGS": [
    "-mcpu=cortex-m7",
    "-mhard-float",
    "-DSTM32H7",
    "-DSTM32H725xx",
    "-mfpu=fpv5-d16",
    "-fsingle-precision-constant",
    "-Os",
    "-g",
    "-DPANDA",
  ],
}

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().abspath
  common_flags += ["-DALLOW_DEBUG"]

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

includes = [
  "stm32fx/inc",
  "stm32h7/inc",
  "..",
  ".",
]

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


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 = File(f'../certs/{name}.pub').srcnode().abspath
  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
with open("obj/gitversion.h", "w") as f:
  f.write(f'const uint8_t gitversion[] = "{get_version(BUILDER, BUILD_TYPE)}";\n')

with open("obj/version", "w") as f:
  f.write(f'{get_version(BUILDER, BUILD_TYPE)}')

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().abspath

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

  if ("ENABLE_SPI" in os.environ or "h7" in project_name) and not project_name.startswith('pedal'):
    flags.append('-DENABLE_SPI')

  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,
    CPPPATH=includes,
    BUILDERS={
      'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf')
    }
  )

  startup = project_env.Object(f"obj/startup_{project_name}", project["STARTUP_FILE"])

  # 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 + [bootstub_obj])
  bootstub_bin = project_env.Objcopy(f"obj/bootstub.{project_name}.bin", bootstub_elf)

  # Build main
  main_obj = project_env.Object(f"main-{project_name}", project["MAIN"])
  main_elf = project_env.Program(f"obj/{project_name}.elf", [startup, main_obj],
    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().abspath
  panda_bin_signed = project_env.Command(f"obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}")
