// Copyright 2024 The Android Open Source Project
//
// 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.

//! Device tree source (dts) for comparing device tree contents
//! i.e. sorted dts decompiled by `dtc -s -O dts`.

use anyhow::{anyhow, Result};
use libfdt::Fdt;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};

/// Device tree source (dts)
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Dts {
    dts: String,
}

impl Dts {
    /// Creates a device tree source from /proc/device-tree style directory
    pub fn from_fs(path: &Path) -> Result<Self> {
        let path = path.to_str().unwrap();
        let res = Command::new("./dtc_static")
            .args(["-f", "-s", "-I", "fs", "-O", "dts", path])
            .output()?;
        if !res.status.success() {
            return Err(anyhow!("Failed to run dtc_static, res={res:?}"));
        }
        Ok(Self { dts: String::from_utf8(res.stdout)? })
    }

    /// Creates a device tree source from dtb
    pub fn from_dtb(path: &Path) -> Result<Self> {
        let path = path.to_str().unwrap();
        let res = Command::new("./dtc_static")
            .args(["-f", "-s", "-I", "dtb", "-O", "dts", path])
            .output()?;
        if !res.status.success() {
            return Err(anyhow!("Failed to run dtc_static, res={res:?}"));
        }
        Ok(Self { dts: String::from_utf8(res.stdout)? })
    }

    /// Creates a device tree source from Fdt
    pub fn from_fdt(fdt: &Fdt) -> Result<Self> {
        let mut dtc = Command::new("./dtc_static")
            .args(["-f", "-s", "-I", "dtb", "-O", "dts"])
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .spawn()?;

        {
            let mut stdin = dtc.stdin.take().unwrap();
            stdin.write_all(fdt.as_slice())?;
            // Explicitly drop stdin to avoid indefinite blocking
        }

        let res = dtc.wait_with_output()?;
        if !res.status.success() {
            return Err(anyhow!("Failed to run dtc_static, res={res:?}"));
        }
        Ok(Self { dts: String::from_utf8(res.stdout)? })
    }
}