/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::fs::File;
use std::path::{Path, PathBuf};

use api::{ExternalImageData, ImageDescriptor};
#[cfg(feature = "png")]
use api::ImageFormat;
use api::units::TexelRect;
#[cfg(feature = "png")]
use api::units::DeviceIntSize;
#[cfg(feature = "capture")]
use crate::print_tree::{PrintableTree, PrintTree};
use crate::render_api::CaptureBits;


#[derive(Clone)]
pub struct CaptureConfig {
    pub root: PathBuf,
    pub bits: CaptureBits,
    /// Scene sequence ID when capturing multiple frames. Zero for a single frame capture.
    pub scene_id: u32,
    /// Frame sequence ID when capturing multiple frames. Zero for a single frame capture.
    pub frame_id: u32,
    /// Resource sequence ID when capturing multiple frames. Zero for a single frame capture.
    pub resource_id: u32,
    #[cfg(feature = "capture")]
    pretty: ron::ser::PrettyConfig,
}

impl CaptureConfig {
    #[cfg(any(feature = "capture", feature = "replay"))]
    pub fn new(root: PathBuf, bits: CaptureBits) -> Self {
        CaptureConfig {
            root,
            bits,
            scene_id: 0,
            frame_id: 0,
            resource_id: 0,
            #[cfg(feature = "capture")]
            pretty: ron::ser::PrettyConfig::new()
                .enumerate_arrays(true)
                .indentor(" ".to_string()),
        }
    }

    #[cfg(feature = "capture")]
    pub fn prepare_scene(&mut self) {
        use std::fs::create_dir_all;
        self.scene_id += 1;
        let _ = create_dir_all(&self.scene_root());
    }

    #[cfg(feature = "capture")]
    pub fn prepare_frame(&mut self) {
        use std::fs::create_dir_all;
        self.frame_id += 1;
        let _ = create_dir_all(&self.frame_root());
    }

    #[cfg(feature = "capture")]
    pub fn prepare_resource(&mut self) {
        use std::fs::create_dir_all;
        self.resource_id += 1;
        let _ = create_dir_all(&self.resource_root());
    }

    #[cfg(any(feature = "capture", feature = "replay"))]
    pub fn scene_root(&self) -> PathBuf {
        if self.scene_id > 0 {
            let path = format!("scenes/{:05}", self.scene_id);
            self.root.join(path)
        } else {
            self.root.clone()
        }
    }

    #[cfg(any(feature = "capture", feature = "replay"))]
    pub fn frame_root(&self) -> PathBuf {
        if self.frame_id > 0 {
            let path = format!("frames/{:05}", self.frame_id);
            self.scene_root().join(path)
        } else {
            self.root.clone()
        }
    }

    #[cfg(any(feature = "capture", feature = "replay"))]
    pub fn resource_root(&self) -> PathBuf {
        if self.resource_id > 0 {
            let path = format!("resources/{:05}", self.resource_id);
            self.root.join(path)
        } else {
            self.root.clone()
        }
    }

    #[cfg(feature = "capture")]
    pub fn serialize_for_scene<T, P>(&self, data: &T, name: P)
    where
        T: serde::Serialize,
        P: AsRef<Path>,
    {
        self.serialize(data, self.scene_root(), name)
    }

    #[cfg(feature = "capture")]
    pub fn serialize_for_frame<T, P>(&self, data: &T, name: P)
    where
        T: serde::Serialize,
        P: AsRef<Path>,
    {
        self.serialize(data, self.frame_root(), name)
    }

    #[cfg(feature = "capture")]
    pub fn serialize_for_resource<T, P>(&self, data: &T, name: P)
    where
        T: serde::Serialize,
        P: AsRef<Path>,
    {
        self.serialize(data, self.resource_root(), name)
    }

    #[cfg(feature = "capture")]
    pub fn file_path_for_frame<P>(&self, name: P, ext: &str) -> PathBuf
    where P: AsRef<Path> {
        self.frame_root().join(name).with_extension(ext)
    }

    #[cfg(feature = "capture")]
    fn serialize<T, P>(&self, data: &T, path: PathBuf, name: P)
    where
        T: serde::Serialize,
        P: AsRef<Path>,
    {
        use std::io::Write;
        let ron = ron::ser::to_string_pretty(data, self.pretty.clone())
            .unwrap();
        let mut file = File::create(path.join(name).with_extension("ron"))
            .unwrap();
        write!(file, "{}\n", ron)
            .unwrap();
    }

    #[cfg(feature = "capture")]
    fn serialize_tree<T, P>(data: &T, root: PathBuf, name: P)
    where
        T: PrintableTree,
        P: AsRef<Path>
    {
        let path = root
            .join(name)
            .with_extension("tree");
        let file = File::create(path)
            .unwrap();
        let mut pt = PrintTree::new_with_sink("", file);
        data.print_with(&mut pt);
    }

    #[cfg(feature = "capture")]
    pub fn serialize_tree_for_frame<T, P>(&self, data: &T, name: P)
    where
        T: PrintableTree,
        P: AsRef<Path>
    {
        Self::serialize_tree(data, self.frame_root(), name)
    }

    #[cfg(feature = "replay")]
    fn deserialize<T, P>(root: &PathBuf, name: P) -> Option<T>
    where
        T: for<'a> serde::Deserialize<'a>,
        P: AsRef<Path>,
    {
        use std::io::Read;

        let mut string = String::new();
        let path = root
            .join(name.as_ref())
            .with_extension("ron");
        File::open(path)
            .ok()?
            .read_to_string(&mut string)
            .unwrap();
        match ron::de::from_str(&string) {
            Ok(out) => Some(out),
            Err(e) => panic!("File {:?} deserialization failed: {:?}", name.as_ref(), e),
        }
    }

    #[cfg(feature = "replay")]
    pub fn deserialize_for_scene<T, P>(&self, name: P) -> Option<T>
    where
        T: for<'a> serde::Deserialize<'a>,
        P: AsRef<Path>,
    {
        Self::deserialize(&self.scene_root(), name)
    }

    #[cfg(feature = "replay")]
    pub fn deserialize_for_frame<T, P>(&self, name: P) -> Option<T>
    where
        T: for<'a> serde::Deserialize<'a>,
        P: AsRef<Path>,
    {
        Self::deserialize(&self.frame_root(), name)
    }

    #[cfg(feature = "replay")]
    pub fn deserialize_for_resource<T, P>(&self, name: P) -> Option<T>
    where
        T: for<'a> serde::Deserialize<'a>,
        P: AsRef<Path>,
    {
        Self::deserialize(&self.resource_root(), name)
    }

    #[cfg(feature = "png")]
    pub fn save_png(
        path: PathBuf, size: DeviceIntSize, format: ImageFormat, stride: Option<i32>, data: &[u8],
    ) {
        use png::{BitDepth, ColorType, Encoder};
        use std::io::BufWriter;
        use std::borrow::Cow;

        // `png` expects
        let data = match stride {
            Some(stride) if stride != format.bytes_per_pixel() * size.width => {
                let mut unstrided = Vec::new();
                for y in 0..size.height {
                    let start = (y * stride) as usize;
                    unstrided.extend_from_slice(&data[start..start+(size.width * format.bytes_per_pixel()) as usize]);
                }
                Cow::from(unstrided)
            }
            _ => Cow::from(data),
        };

        let color_type = match format {
            ImageFormat::RGBA8 => ColorType::RGBA,
            ImageFormat::BGRA8 => {
                warn!("Unable to swizzle PNG of BGRA8 type");
                ColorType::RGBA
            },
            ImageFormat::R8 => ColorType::Grayscale,
            ImageFormat::RG8 => ColorType::GrayscaleAlpha,
            _ => {
                error!("Unable to save PNG of {:?}", format);
                return;
            }
        };
        let w = BufWriter::new(File::create(path).unwrap());
        let mut enc = Encoder::new(w, size.width as u32, size.height as u32);
        enc.set_color(color_type);
        enc.set_depth(BitDepth::Eight);
        enc
            .write_header()
            .unwrap()
            .write_image_data(&*data)
            .unwrap();
    }
}

/// An image that `ResourceCache` is unable to resolve during a capture.
/// The image has to be transferred to `Renderer` and locked with the
/// external image handler to get the actual contents and serialize them.
#[derive(Deserialize, Serialize)]
pub struct ExternalCaptureImage {
    pub short_path: String,
    pub descriptor: ImageDescriptor,
    pub external: ExternalImageData,
}

/// A short description of an external image to be saved separately as
/// "externals/XX.ron", redirecting into a specific texture/blob with
/// the corresponding UV rectangle.
#[derive(Deserialize, Serialize)]
pub struct PlainExternalImage {
    /// Path to the RON file describing the texel data.
    pub data: String,
    /// External image data source.
    pub external: ExternalImageData,
    /// UV sub-rectangle of the image.
    pub uv: TexelRect,
}
