// Copyright (c) 2026, Infineon Technologies AG or an affiliate of Infineon Technologies AG.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing permissions and
// limitations under the License.

use std::env;
use std::fs;
use std::path::PathBuf;

macro_rules! error {
    ($($args:expr),*) => {{
        println!("cargo::error={}", format!($($args),*));
        std::process::exit(0);
    }};
}

fn main() {
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

    let parts = psoc_devices::DICE
        .iter()
        .flat_map(|die| die.parts().iter().map(move |p| (die, p)));

    let selected_part = parts
        .filter(|(_, (name, _))| {
            env::var(format!(
                "CARGO_FEATURE_DEVICE_{}",
                name.to_uppercase().replace('-', "_")
            ))
            .is_ok()
        })
        .collect::<Vec<_>>();

    let (die, (mpn, part)) = match selected_part.as_slice() {
        [part] => part,
        [] => error!("no device selected; you must enable exactly one device feature"),
        parts => error!(
            "multiple devices enabed: {}; \
                you must enable exactly one device feature",
            parts
                .iter()
                .map(|(_, (name, _))| name.to_lowercase())
                .collect::<Vec<_>>()
                .join(", ")
        ),
    };

    let selected_core = std::env::vars()
        .flat_map(|(k, _)| k.strip_prefix("CARGO_FEATURE_CORE_").map(str::to_lowercase))
        .next()
        .unwrap_or_else(|| die.cores().first().unwrap().to_string());

    println!("cargo:rustc-check-cfg=cfg(boot_image)");
    if std::env::var("CARGO_FEATURE_BOOT_IMAGE").is_ok() {
        let boot_image = match (die.name, selected_core.as_str()) {
            ("psoc6_01", "cm4") => "boot/psoc6_01_cm0p_sleep.bin",
            ("psoc6_02", "cm4") => "boot/psoc6_02_cm0p_sleep.bin",
            ("psoc6_03", "cm4") => "boot/psoc6_03_cm0p_sleep.bin",
            ("psoc6_04", "cm4") => "boot/psoc6_04_cm0p_sleep.bin",
            _ => {
                println!(
                    "cargo::error=No boot image available for device '{}' and core '{}'",
                    die.name, selected_core
                );
                std::process::exit(0);
            }
        };
        println!("cargo:rustc-cfg=boot_image");
        println!(
            "cargo:rustc-env=BOOT_IMAGE={}/{boot_image}",
            std::env::var("CARGO_MANIFEST_DIR").unwrap()
        );
    }

    // Generate Rust code for the selected part.
    let device_rs = part.generate_device_file(die);
    std::fs::write(out_dir.join("device.rs"), device_rs).expect("failed to write part.rs");

    let selected_memory_x = PathBuf::from("ld")
        .join(die.name)
        .join(format!("{}.x", mpn.to_lowercase()));
    println!("cargo:rerun-if-changed=ld/psoc.x");
    println!("cargo:rerun-if-changed={}", selected_memory_x.display());

    fs::copy("ld/psoc.x", out_dir.join("psoc.x")).expect("failed to copy linker script to OUT_DIR");
    fs::copy(&selected_memory_x, out_dir.join("memory.x"))
        .expect("failed to copy linker script to OUT_DIR");

    println!("cargo:rustc-link-search={}", out_dir.display());
}
