//! Which ranges a client requests (http-delivery §7, §10.1): the head first, then the
//! picked component and what it applies, one request per range and priority class,
//! or the rest of the object when ranges cannot be checked or would cost nearly as
//! much. Deterministic, so the corpus pins its output.

use super::head::Head;
use super::{ByteRange, Fragment, MIN_RANGE_OBJECT};

/// A request's priority class (§10.1): a base never waits on a refinement.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Class {
    Base,
    Refinement,
}

impl Class {
    /// `base` or `refinement`.
    pub fn label(self) -> &'static str {
        match self {
            Class::Base => "base",
            Class::Refinement => "refinement",
        }
    }
}

/// A client's reference policy: how much to request first, when ranges are not worth
/// it, and how long it waits for a refinement. None of it is normative (§7: the 16369
/// bytes and the 90 % are what the reference client uses).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FetchPolicy {
    /// The first request's length when no `h` fragment is given: sixteen chunks of
    /// `X` less the domain prefix.
    pub default_head: u64,
    /// Fetch the rest instead when the planned ranges reach this share, in ‰, of the
    /// bytes after `covered_len`.
    pub full_threshold_permille: u16,
    /// The deadline on a refinement request, in milliseconds from the moment the base
    /// verified (§10.2), on the caller's clock ([`super::session::FetchSession::tick`]).
    pub refinement_deadline_ms: Option<u32>,
}

impl Default for FetchPolicy {
    fn default() -> Self {
        FetchPolicy { default_head: 16 * 1024 - super::DOMAIN_LEN, full_threshold_permille: 900, refinement_deadline_ms: None }
    }
}

/// Why a client fetched the rest of the object rather than ranges of it, or why it
/// dropped a refinement.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WhyWhole {
    /// The object is below one chunk (§5.4): fetched whole from the start.
    BelowOneChunk,
    /// A `WAI1`: its one component is the rest of the object.
    SingleComponent,
    /// A layer object (§10.4): fetched whole and handed to staged delivery.
    Layer,
    /// §7 step 5: a component the sink uses states no `sha256`.
    DigestAbsent { index: usize },
    /// §7 step 6: a component did not match its `sha256`.
    DigestMismatch { index: usize },
    /// §7: the planned base ranges reach the policy's share of the rest.
    Threshold,
    /// §7 step 4: a refinement of an envelope whose components do not all conform (SPEC
    /// §7.1): dropped, never requested.
    ComponentsNonconforming { index: usize },
}

impl WhyWhole {
    /// `below_one_chunk`, `single_component`, `layer`, `component_digest_absent`,
    /// `component_digest_mismatch`, `range_threshold` or `components_nonconforming`.
    pub fn code(self) -> &'static str {
        match self {
            WhyWhole::BelowOneChunk => "below_one_chunk",
            WhyWhole::SingleComponent => "single_component",
            WhyWhole::Layer => "layer",
            WhyWhole::DigestAbsent { .. } => "component_digest_absent",
            WhyWhole::DigestMismatch { .. } => "component_digest_mismatch",
            WhyWhole::Threshold => "range_threshold",
            WhyWhole::ComponentsNonconforming { .. } => "components_nonconforming",
        }
    }
}

/// The requests for a pick: ranges by class, or the rest of the object and why; and
/// the refinements dropped because they state no digest.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Plan {
    pub requests: Vec<(ByteRange, Class)>,
    pub whole_rest: Option<(ByteRange, WhyWhole)>,
    pub dropped: Vec<(usize, WhyWhole)>,
}

/// The first request (§7 step 1): bytes [0, E), with `E` the `h` fragment or the
/// policy's default, and never past a known `n`. `None` when `n` is below one chunk
/// (§5.4): the client fetches the object whole.
pub fn plan_head(policy: &FetchPolicy, frag: &Fragment) -> Option<ByteRange> {
    if frag.n.is_some_and(|n| n < MIN_RANGE_OBJECT) {
        return None;
    }
    let mut e = frag.h.filter(|&h| h > 0).unwrap_or(policy.default_head);
    if let Some(n) = frag.n {
        e = e.min(n);
    }
    ByteRange::from_len(0, e)
}

/// The rest of an object after its covered head, or `None` when the head covers it.
pub fn rest_of(head: &Head) -> Option<ByteRange> {
    let covered = head.covered_len();
    ByteRange::from_len(covered, head.envelope_len - covered)
}

/// Subtract the `held` ranges from [first, end).
fn subtract(first: u64, end: u64, held: &[ByteRange]) -> Vec<(u64, u64)> {
    let mut out = vec![(first, end)];
    for h in held {
        let mut next = Vec::new();
        for (a, b) in out {
            let (hs, he) = (h.first, h.end());
            if he <= a || hs >= b {
                next.push((a, b));
                continue;
            }
            if a < hs {
                next.push((a, hs));
            }
            if he < b {
                next.push((he, b));
            }
        }
        out = next;
    }
    out
}

/// The requests for the components in `wanted` (§7 steps 5 and 6), each with its
/// class (§10.1):
///
/// - a base-class component with no digest sends the base to the rest of the object
///   (`component_digest_absent`); a refinement with no digest is dropped, alone;
/// - otherwise each component's range less the `held` ranges, ranges that touch merged
///   within one class only, refinements always in requests of their own;
/// - and the rest of the object instead when the base-class ranges alone reach the
///   policy's threshold of the bytes after `covered_len`. A refinement never moves the
///   base to the rest: its bytes do not count.
pub fn plan_components(
    head: &Head,
    wanted: &[(usize, Class)],
    digests_present: &[bool],
    held: &[ByteRange],
    policy: &FetchPolicy,
) -> Plan {
    let mut plan = Plan { requests: Vec::new(), whole_rest: None, dropped: Vec::new() };
    let Some(rest) = rest_of(head) else { return plan };
    let has_digest = |i: usize| digests_present.get(i).copied().unwrap_or(false);
    if let Some(&(index, _)) = wanted.iter().find(|&&(i, c)| c == Class::Base && !has_digest(i)) {
        plan.whole_rest = Some((rest, WhyWhole::DigestAbsent { index }));
        return plan;
    }
    let mut pieces: Vec<(Class, u64, u64)> = Vec::new();
    for &(index, class) in wanted {
        if !has_digest(index) {
            plan.dropped.push((index, WhyWhole::DigestAbsent { index }));
            continue;
        }
        let Some(c) = head.component(index) else { continue };
        for (a, b) in subtract(c.start, c.start + c.len, held) {
            pieces.push((class, a, b));
        }
    }
    pieces.sort();
    let mut merged: Vec<(Class, u64, u64)> = Vec::new();
    for (class, a, b) in pieces {
        match merged.last_mut() {
            Some((c, _, e)) if *c == class && *e >= a => *e = (*e).max(b),
            _ => merged.push((class, a, b)),
        }
    }
    let base: u128 = merged.iter().filter(|(c, _, _)| *c == Class::Base).map(|(_, a, b)| u128::from(b - a)).sum();
    if base * 1000 >= u128::from(policy.full_threshold_permille) * u128::from(rest.len()) && base > 0 {
        plan.whole_rest = Some((rest, WhyWhole::Threshold));
        plan.dropped.clear();
        return plan;
    }
    plan.requests = merged
        .into_iter()
        .filter_map(|(class, a, b)| ByteRange::from_len(a, b - a).map(|r| (r, class)))
        .collect();
    plan.requests.sort_by_key(|(r, class)| (*class, r.first));
    plan
}
