/* 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/. */

extern crate url;
use self::url::Host;
use error::SdpParserInternalError;
use std::convert::TryFrom;
use std::fmt;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;

#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub enum Address {
    Fqdn(String),
    Ip(IpAddr),
}

impl fmt::Display for Address {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Address::Fqdn(fqdn) => fqdn.fmt(f),
            Address::Ip(ip) => ip.fmt(f),
        }
    }
}

impl FromStr for Address {
    type Err = SdpParserInternalError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut e: Option<SdpParserInternalError> = None;
        if s.find(':').is_some() {
            match IpAddr::from_str(s) {
                Ok(ip) => return Ok(Address::Ip(ip)),
                Err(err) => e = Some(err.into()),
            }
        }
        Host::parse(s)
            .map(|host| match host {
                Host::Domain(s) => Address::Fqdn(s),
                Host::Ipv4(ip) => Address::Ip(IpAddr::V4(ip)),
                Host::Ipv6(ip) => Address::Ip(IpAddr::V6(ip)),
            })
            .map_err(|err| e.unwrap_or_else(|| err.into()))
    }
}

impl From<ExplicitlyTypedAddress> for Address {
    fn from(item: ExplicitlyTypedAddress) -> Self {
        match item {
            ExplicitlyTypedAddress::Fqdn { domain, .. } => Address::Fqdn(domain),
            ExplicitlyTypedAddress::Ip(ip) => Address::Ip(ip),
        }
    }
}

impl PartialEq for Address {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Address::Fqdn(a), Address::Fqdn(b)) => a.to_lowercase() == b.to_lowercase(),
            (Address::Ip(a), Address::Ip(b)) => a == b,
            (_, _) => false,
        }
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub enum AddressType {
    IpV4 = 4,
    IpV6 = 6,
}

impl fmt::Display for AddressType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AddressType::IpV4 => "IP4",
            AddressType::IpV6 => "IP6",
        }
        .fmt(f)
    }
}

impl FromStr for AddressType {
    type Err = SdpParserInternalError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_uppercase().as_str() {
            "IP4" => Ok(AddressType::IpV4),
            "IP6" => Ok(AddressType::IpV6),
            _ => Err(SdpParserInternalError::UnknownAddressType(s.to_owned())),
        }
    }
}

pub trait AddressTyped {
    fn address_type(&self) -> AddressType;
}

impl AddressTyped for IpAddr {
    fn address_type(&self) -> AddressType {
        match self {
            IpAddr::V4(_) => AddressType::IpV4,
            IpAddr::V6(_) => AddressType::IpV6,
        }
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub enum ExplicitlyTypedAddress {
    Fqdn {
        address_type: AddressType,
        domain: String,
    },
    Ip(IpAddr),
}

impl fmt::Display for ExplicitlyTypedAddress {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "IN {} ", self.address_type())?;
        match self {
            ExplicitlyTypedAddress::Fqdn { domain, .. } => domain.fmt(f),
            ExplicitlyTypedAddress::Ip(ip) => ip.fmt(f),
        }
    }
}

impl AddressTyped for ExplicitlyTypedAddress {
    fn address_type(&self) -> AddressType {
        match self {
            ExplicitlyTypedAddress::Fqdn { address_type, .. } => *address_type,
            ExplicitlyTypedAddress::Ip(ip) => ip.address_type(),
        }
    }
}

impl From<IpAddr> for ExplicitlyTypedAddress {
    fn from(item: IpAddr) -> Self {
        ExplicitlyTypedAddress::Ip(item)
    }
}

impl From<Ipv4Addr> for ExplicitlyTypedAddress {
    fn from(item: Ipv4Addr) -> Self {
        ExplicitlyTypedAddress::Ip(IpAddr::V4(item))
    }
}

impl From<Ipv6Addr> for ExplicitlyTypedAddress {
    fn from(item: Ipv6Addr) -> Self {
        ExplicitlyTypedAddress::Ip(IpAddr::V6(item))
    }
}

impl TryFrom<(AddressType, &str)> for ExplicitlyTypedAddress {
    type Error = SdpParserInternalError;
    fn try_from(item: (AddressType, &str)) -> Result<Self, Self::Error> {
        match Address::from_str(item.1)? {
            Address::Ip(ip) => {
                if ip.address_type() != item.0 {
                    Err(SdpParserInternalError::AddressTypeMismatch {
                        found: ip.address_type(),
                        expected: item.0,
                    })
                } else {
                    Ok(ExplicitlyTypedAddress::Ip(ip))
                }
            }
            Address::Fqdn(domain) => Ok(ExplicitlyTypedAddress::Fqdn {
                address_type: item.0,
                domain,
            }),
        }
    }
}

impl PartialEq for ExplicitlyTypedAddress {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (
                ExplicitlyTypedAddress::Fqdn {
                    address_type: a1,
                    domain: d1,
                },
                ExplicitlyTypedAddress::Fqdn {
                    address_type: a2,
                    domain: d2,
                },
            ) => a1 == a2 && d1.to_lowercase() == d2.to_lowercase(),
            (ExplicitlyTypedAddress::Ip(a), ExplicitlyTypedAddress::Ip(b)) => a == b,
            (_, _) => false,
        }
    }
}

#[cfg(test)]
#[path = "./address_tests.rs"]
mod address_tests;
