1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Traits for parts of CRIs / CRI references, which are typically used as GATs of 
//!
//! These are shared between the accessor and the iterator interfaces.

use crate::characterclasses::{AsciiSet, HOST_UE};

/// A scheme as is part of every CRI.
pub trait Scheme {
    /// Give the CRI registered negative integer for the used scheme, if one exits.
    ///
    /// ## Open questions
    ///
    /// More correct would be an nint type that has precisely nint semantics, 
    fn to_cri_id(&self) -> Option<i16>;

    /// Give the textual representation of the scheme
    ///
    /// ## Open questions
    ///
    /// Should this be fallible? Not being, this makes it hard for a Scheme implementation to carry
    /// unknown schemes around.
    fn to_text_scheme(&self) -> &str;

    fn equals(&self, other: impl Scheme) -> bool {
        match (self.to_cri_id(), other.to_cri_id()) {
            (Some(s), Some(o)) => s == o,
            _ => self.to_text_scheme() == other.to_text_scheme(),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Authority {
    HostPort,
    NoAuthoritySlashless,
    NoAuthoritySlashStart,
}

/// The limited list of shapes a [Host] can have.
///
/// This is the main method for reading out a [Host], although it may later change into three
/// `fn as_variant(&self) -> &variant_type` methods (but these can't be matched as easily).
#[derive(Debug)]
pub enum HostRef<'a, HostItem: TextOrPet<HOST_UE>, HostIter: Clone + IntoIterator<Item=HostItem>> {
    IPv4(&'a no_std_net::Ipv4Addr),
    IPv6 { address: &'a no_std_net::Ipv6Addr, zone: Option<&'a str> },
    Hostname(HostIter),
}

impl<'a, HostItem: TextOrPet<HOST_UE>, HostIter: Clone + IntoIterator<Item=HostItem>> HostRef<'a, HostItem, HostIter> {
    pub fn format_uri_host(&self, w: &mut impl core::fmt::Write) -> core::fmt::Result {
        match self {
            HostRef::IPv4(a) => write!(w, "{}", a)?,
            HostRef::IPv6 { address, zone } => {
                write!(w, "[{address}")?;
                if let Some(zone) = zone {
                    write!(w, "%25{zone}")?;
                }
                write!(w, "]")?;
            }
            HostRef::Hostname(s) => {
                let mut first = true;
                for component in s.clone().into_iter() {
                    if first {
                        first = false;
                    } else {
                        write!(w, ".")?;
                    }
                    write!(w, "{}", component.to_uri_component())?;
                }
            }
        }
        Ok(())
    }
}

/// The host copmoonent of a CRI, which may take one of the three shapes of [HostRef].
///
/// It is only present in a CRI that has an authority component.
pub trait Host {
    type HostItem<'a>: TextOrPet<HOST_UE> where Self: 'a;
    type HostIter<'a>: Clone + IntoIterator<Item=Self::HostItem<'a>> where Self: 'a;

    fn as_ref(&self) -> HostRef<'_, Self::HostItem<'_>, Self::HostIter<'_>>;

    fn format_uri_host(&self, w: &mut impl core::fmt::Write) -> core::fmt::Result {
        self.as_ref().format_uri_host(w)
    }

    fn equals(&self, other: &impl Host) -> bool {
        // Could also be implemented as PartialEq on HostRef
        match (self.as_ref(), other.as_ref()) {
            (HostRef::IPv4(s), HostRef::IPv4(o)) => s == o,
            (HostRef::IPv6 { address: sa, zone: sz }, HostRef::IPv6 { address: oa, zone: oz }) => sa == oa && sz == oz,
            (HostRef::Hostname(s), HostRef::Hostname(o)) => TextOrPet::iter_equals(s.into_iter(), o.into_iter()),
            _ => false,
        }
    }
}

impl Scheme for ! {
    fn to_cri_id(&self) -> Option<i16> {
        *self
    }

    fn to_text_scheme(&self) -> &str {
        *self
    }
}

/// A text (or PET) component of a CRI
///
/// It does not need to implement Display on its own (but rather delegates it to an
/// `.to_uri_component()` function) in order to allow implementing a more IRI-ish Display (suitable
/// for printing). on the TextOrPet; if that is not desired, `.to_uri_component()` can return &self.
pub trait TextOrPet<const UNESCAPED: AsciiSet> {
    type UriEncoded<'a>: core::fmt::Display where Self: 'a;

    fn to_uri_component(&self) -> Self::UriEncoded<'_>;

    fn contains_unescaped(&self, needle: char) -> bool {
        use core::fmt::Write;

        struct SeenCharacter {
            seen: bool,
            needle: char,
        }

        impl core::fmt::Write for SeenCharacter {
            fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
                self.seen |= s.contains(self.needle);
                Ok(())
            }
        }

        let mut result = SeenCharacter { seen: false, needle };

        write!(result, "{}", self.to_uri_component())
            .expect("SeenCharacter never fails writes");

        result.seen
    }

    fn equals(&self, other: &impl TextOrPet<UNESCAPED>) -> bool {
        // FIXME outrageously inefficient
        let s = format!("{}", self.to_uri_component());
        let o = format!("{}", other.to_uri_component());
        s == o
    }

    fn iter_equals<O: TextOrPet<UNESCAPED>>(mut s: impl Iterator<Item=Self>, mut o: impl Iterator<Item=O>) -> bool where Self: Sized /* FIXME: really necessary? */ {
        loop {
            match (s.next().as_ref(), o.next().as_ref()) {
                (None, None) => { return true; }
                (Some(si), Some(oi)) => if !si.equals(oi) { return false; }
                _ => { return false; }
            }
        }
    }

    // TBD find good type here
    // fn to_cri_items(&self, );
}