use super::{BasicGenerator, Generator, TestCase};
use crate::cbor_utils::{cbor_array, cbor_map, map_insert};
use ciborium::Value;

/// Generator for Unicode text strings. Created by [`text()`].
pub struct TextGenerator {
    min_size: usize,
    max_size: Option<usize>,
}

impl TextGenerator {
    /// Set the minimum length in characters.
    pub fn min_size(mut self, min_size: usize) -> Self {
        self.min_size = min_size;
        self
    }

    /// Set the maximum length in characters.
    pub fn max_size(mut self, max_size: usize) -> Self {
        self.max_size = Some(max_size);
        self
    }

    fn build_schema(&self) -> Value {
        if let Some(max) = self.max_size {
            assert!(self.min_size <= max, "Cannot have max_size < min_size");
        }

        let mut schema = cbor_map! {
            "type" => "string",
            "min_size" => self.min_size as u64
        };

        if let Some(max) = self.max_size {
            map_insert(&mut schema, "max_size", max as u64);
        }

        schema
    }
}

impl Generator<String> for TextGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &self.build_schema())
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(self.build_schema(), |raw| {
            super::deserialize_value(raw)
        }))
    }
}

/// Generate arbitrary Unicode text strings.
pub fn text() -> TextGenerator {
    TextGenerator {
        min_size: 0,
        max_size: None,
    }
}

/// Generator for strings matching a regex pattern. Created by [`from_regex()`].
///
/// By default generates strings that contain a match. Use [`fullmatch()`](Self::fullmatch)
/// to require the entire string to match.
pub struct RegexGenerator {
    pattern: String,
    fullmatch: bool,
}

impl RegexGenerator {
    /// Set whether the entire string must match the pattern, not just contain a match.
    pub fn fullmatch(mut self, fullmatch: bool) -> Self {
        self.fullmatch = fullmatch;
        self
    }

    fn build_schema(&self) -> Value {
        cbor_map! {
            "type" => "regex",
            "pattern" => self.pattern.as_str(),
            "fullmatch" => self.fullmatch
        }
    }
}

impl Generator<String> for RegexGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &self.build_schema())
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(self.build_schema(), |raw| {
            super::deserialize_value(raw)
        }))
    }
}

/// Generate strings matching a regex pattern.
pub fn from_regex(pattern: &str) -> RegexGenerator {
    RegexGenerator {
        pattern: pattern.to_string(),
        fullmatch: false,
    }
}

/// Generator for arbitrary byte sequences. Created by [`binary()`].
pub struct BinaryGenerator {
    min_size: usize,
    max_size: Option<usize>,
}

impl BinaryGenerator {
    /// Set the minimum length in bytes.
    pub fn min_size(mut self, min_size: usize) -> Self {
        self.min_size = min_size;
        self
    }

    /// Set the maximum length in bytes.
    pub fn max_size(mut self, max_size: usize) -> Self {
        self.max_size = Some(max_size);
        self
    }

    fn build_schema(&self) -> Value {
        if let Some(max) = self.max_size {
            assert!(self.min_size <= max, "Cannot have max_size < min_size");
        }

        let mut schema = cbor_map! {
            "type" => "binary",
            "min_size" => self.min_size as u64
        };

        if let Some(max) = self.max_size {
            map_insert(&mut schema, "max_size", max as u64);
        }

        schema
    }
}

fn parse_binary(raw: Value) -> Vec<u8> {
    match raw {
        Value::Bytes(bytes) => bytes,
        _ => panic!("expected Value::Bytes, got {:?}", raw),
    }
}

impl Generator<Vec<u8>> for BinaryGenerator {
    fn do_draw(&self, tc: &TestCase) -> Vec<u8> {
        parse_binary(super::generate_raw(tc, &self.build_schema()))
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, Vec<u8>>> {
        Some(BasicGenerator::new(self.build_schema(), parse_binary))
    }
}

/// Generate arbitrary byte sequences (`Vec<u8>`).
pub fn binary() -> BinaryGenerator {
    BinaryGenerator {
        min_size: 0,
        max_size: None,
    }
}

/// Generator for email address strings. Created by [`emails()`].
pub struct EmailGenerator;

impl Generator<String> for EmailGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &cbor_map! {"type" => "email"})
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(cbor_map! {"type" => "email"}, |raw| {
            super::deserialize_value(raw)
        }))
    }
}

/// Generate email address strings.
pub fn emails() -> EmailGenerator {
    EmailGenerator
}

/// Generator for URL strings. Created by [`urls()`].
pub struct UrlGenerator;

impl Generator<String> for UrlGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &cbor_map! {"type" => "url"})
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(cbor_map! {"type" => "url"}, |raw| {
            super::deserialize_value(raw)
        }))
    }
}

/// Generate URL strings.
pub fn urls() -> UrlGenerator {
    UrlGenerator
}

/// Generator for domain name strings. Created by [`domains()`].
pub struct DomainGenerator {
    max_length: usize,
}

impl DomainGenerator {
    /// Set the maximum length (must be between 4 and 255).
    pub fn max_length(mut self, max_length: usize) -> Self {
        self.max_length = max_length;
        self
    }

    fn build_schema(&self) -> Value {
        assert!(
            self.max_length >= 4 && self.max_length <= 255,
            "max_length must be between 4 and 255"
        );

        cbor_map! {
            "type" => "domain",
            "max_length" => self.max_length as u64
        }
    }
}

impl Generator<String> for DomainGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &self.build_schema())
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(self.build_schema(), |raw| {
            super::deserialize_value(raw)
        }))
    }
}

/// Generate domain name strings.
pub fn domains() -> DomainGenerator {
    DomainGenerator { max_length: 255 }
}

#[derive(Clone, Copy)]
pub enum IpVersion {
    V4,
    V6,
}

/// Generator for IP address strings. Created by [`ip_addresses()`].
///
/// By default generates both IPv4 and IPv6 addresses.
pub struct IpAddressGenerator {
    version: Option<IpVersion>,
}

impl IpAddressGenerator {
    /// Only generate IPv4 addresses.
    pub fn v4(mut self) -> Self {
        self.version = Some(IpVersion::V4);
        self
    }

    /// Only generate IPv6 addresses.
    pub fn v6(mut self) -> Self {
        self.version = Some(IpVersion::V6);
        self
    }

    fn build_schema(&self) -> Value {
        match self.version {
            Some(IpVersion::V4) => cbor_map! {"type" => "ipv4"},
            Some(IpVersion::V6) => cbor_map! {"type" => "ipv6"},
            None => cbor_map! {
                "one_of" => cbor_array![
                    cbor_map!{"type" => "ipv4"},
                    cbor_map!{"type" => "ipv6"}
                ]
            },
        }
    }
}

impl Generator<String> for IpAddressGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &self.build_schema())
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(self.build_schema(), |raw| {
            super::deserialize_value(raw)
        }))
    }
}

/// Generate IP address strings (IPv4 or IPv6).
pub fn ip_addresses() -> IpAddressGenerator {
    IpAddressGenerator { version: None }
}

/// Generator for date strings in YYYY-MM-DD format. Created by [`dates()`].
pub struct DateGenerator;

impl Generator<String> for DateGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &cbor_map! {"type" => "date"})
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(cbor_map! {"type" => "date"}, |raw| {
            super::deserialize_value(raw)
        }))
    }
}

/// Generate date strings in YYYY-MM-DD format.
pub fn dates() -> DateGenerator {
    DateGenerator
}

/// Generator for time strings in HH:MM:SS format. Created by [`times()`].
pub struct TimeGenerator;

impl Generator<String> for TimeGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &cbor_map! {"type" => "time"})
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(cbor_map! {"type" => "time"}, |raw| {
            super::deserialize_value(raw)
        }))
    }
}

/// Generate time strings in HH:MM:SS format.
pub fn times() -> TimeGenerator {
    TimeGenerator
}

/// Generator for ISO 8601 datetime strings. Created by [`datetimes()`].
pub struct DateTimeGenerator;

impl Generator<String> for DateTimeGenerator {
    fn do_draw(&self, tc: &TestCase) -> String {
        super::generate_from_schema(tc, &cbor_map! {"type" => "datetime"})
    }

    fn as_basic(&self) -> Option<BasicGenerator<'_, String>> {
        Some(BasicGenerator::new(
            cbor_map! {"type" => "datetime"},
            super::deserialize_value,
        ))
    }
}

/// Generate ISO 8601 datetime strings.
pub fn datetimes() -> DateTimeGenerator {
    DateTimeGenerator
}
