//! How to read samples (a grid of `f32`, `f16` or `u32` values).

use crate::{
    block::{chunk::TileCoordinates, lines::LineRef},
    error::{Result, UnitResult},
    image::{
        read::{
            any_channels::{ReadSamples, SamplesReader},
            levels::{ReadAllLevels, ReadLargestLevel, ReadSamplesLevel},
        },
        *,
    },
    math::Vec2,
    meta::{
        attribute::{ChannelDescription, SampleType},
        header::Header,
    },
};
// use crate::image::read::layers::ReadChannels;

/// Specify to read only flat samples and no "deep data"
// FIXME do not throw error on deep data but just skip it!
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ReadFlatSamples;
// pub struct ReadAnySamples;

impl ReadFlatSamples {
    // TODO
    // e. g. `let sum = reader.any_channels_with(|sample, sum| sum += sample)`
    // pub fn any_channels_with <S> (self, storage: S) -> {  }

    /// Specify to read only the highest resolution level, skipping all smaller
    /// variations.
    pub fn largest_resolution_level(self) -> ReadLargestLevel<Self> {
        ReadLargestLevel {
            read_samples: self,
        }
    }

    /// Specify to read all contained resolution levels from the image, if any.
    pub fn all_resolution_levels(self) -> ReadAllLevels<Self> {
        ReadAllLevels {
            read_samples: self,
        }
    }

    // TODO pub fn specific_resolution_level<F: Fn(&[Vec2<usize>])->usize >(self,
    // select_level: F) -> ReadLevelBy<Self> { ReadAllLevels { read_samples: self }
    // }
}

// pub struct AnySamplesReader { TODO
// resolution: Vec2<usize>,
// samples: DeepAndFlatSamples
// }

/// Processes pixel blocks from a file and accumulates them into a grid of
/// samples, for example "Red" or "Alpha".
#[derive(Debug, Clone, PartialEq)]
pub struct FlatSamplesReader {
    level: Vec2<usize>,
    resolution: Vec2<usize>,
    samples: FlatSamples,
}

// only used when samples is directly inside a channel, without levels
impl ReadSamples for ReadFlatSamples {
    type Reader = FlatSamplesReader;

    fn create_sample_reader(
        &self,
        header: &Header,
        channel: &ChannelDescription,
    ) -> Result<Self::Reader> {
        self.create_samples_level_reader(header, channel, Vec2(0, 0), header.layer_size)
    }
}

impl ReadSamplesLevel for ReadFlatSamples {
    type Reader = FlatSamplesReader;

    fn create_samples_level_reader(
        &self,
        _header: &Header,
        channel: &ChannelDescription,
        level: Vec2<usize>,
        resolution: Vec2<usize>,
    ) -> Result<Self::Reader> {
        Ok(FlatSamplesReader {
            level,
            resolution, // TODO sampling
            samples: match channel.sample_type {
                SampleType::F16 => FlatSamples::F16(vec![f16::ZERO; resolution.area()]),
                SampleType::F32 => FlatSamples::F32(vec![0.0; resolution.area()]),
                SampleType::U32 => FlatSamples::U32(vec![0; resolution.area()]),
            },
        })
    }
}

impl SamplesReader for FlatSamplesReader {
    type Samples = FlatSamples;

    fn filter_block(&self, tile: TileCoordinates) -> bool {
        tile.level_index == self.level
    }

    fn read_line(&mut self, line: LineRef<'_>) -> UnitResult {
        let index = line.location;
        let resolution = self.resolution;

        // the index is generated by ourselves and must always be correct
        debug_assert_eq!(index.level, self.level, "line should have been filtered");
        debug_assert!(
            index.position.x() + index.sample_count <= resolution.width(),
            "line index calculation bug"
        );
        debug_assert!(index.position.y() < resolution.height(), "line index calculation bug");
        debug_assert_ne!(resolution.0, 0, "sample size bug");

        let start_index = index.position.y() * resolution.width() + index.position.x();
        let end_index = start_index + index.sample_count;

        debug_assert!(
            start_index < end_index && end_index <= self.samples.len(),
            "for resolution {:?}, this is an invalid line: {:?}",
            self.resolution,
            line.location
        );

        match &mut self.samples {
            FlatSamples::F16(samples) => line
                .read_samples_into_slice(&mut samples[start_index..end_index])
                .expect("writing line bytes failed"),

            FlatSamples::F32(samples) => line
                .read_samples_into_slice(&mut samples[start_index..end_index])
                .expect("writing line bytes failed"),

            FlatSamples::U32(samples) => line
                .read_samples_into_slice(&mut samples[start_index..end_index])
                .expect("writing line bytes failed"),
        }

        Ok(())
    }

    fn into_samples(self) -> FlatSamples {
        self.samples
    }
}
