use std::{
    convert::TryFrom,
    fmt::{self, Write},
    ops::Range,
};

use prost_types::field_descriptor_proto;

use crate::{join_span, Span};

#[derive(Default, Copy, Clone, Debug, PartialEq)]
pub(crate) enum Syntax {
    #[default]
    Proto2,
    Proto3,
}

#[derive(Default, Clone, Debug, PartialEq)]
pub(crate) struct File {
    pub span: Span,
    pub syntax: Syntax,
    pub syntax_span: std::option::Option<(Span, Comments)>,
    pub package: std::option::Option<Package>,
    pub imports: Vec<Import>,
    pub options: Vec<Option>,
    pub items: Vec<FileItem>,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum FileItem {
    Enum(Enum),
    Message(Message),
    Extend(Extend),
    Service(Service),
}

#[derive(Clone, Default, Debug, PartialEq)]
pub(crate) struct Comments {
    pub leading_detached_comments: Vec<std::string::String>,
    pub leading_comment: std::option::Option<std::string::String>,
    pub trailing_comment: std::option::Option<std::string::String>,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Ident {
    pub value: std::string::String,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct FullIdent {
    pub parts: Vec<Ident>,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct TypeName {
    pub leading_dot: std::option::Option<Span>,
    pub name: FullIdent,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Int {
    pub negative: bool,
    pub value: u64,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Float {
    pub value: f64,
    pub span: Span,
}

#[derive(Clone, PartialEq)]
pub(crate) struct String {
    pub value: Vec<u8>,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum OptionValue {
    Ident {
        negative: bool,
        ident: Ident,
        span: Span,
    },
    Int(Int),
    Float(Float),
    String(String),
    Aggregate(std::string::String, Span),
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Import {
    pub kind: std::option::Option<(ImportKind, Span)>,
    pub value: std::string::String,
    pub value_span: Span,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum ImportKind {
    Weak,
    Public,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Package {
    pub name: FullIdent,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Option {
    pub body: OptionBody,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum OptionNamePart {
    Ident(Ident),
    Extension(TypeName, Span),
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct OptionBody {
    pub name: Vec<OptionNamePart>,
    pub value: OptionValue,
}

#[derive(Clone, Default, Debug, PartialEq)]
pub(crate) struct OptionList {
    pub options: Vec<OptionBody>,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Message {
    pub name: Ident,
    pub body: MessageBody,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Field {
    pub label: std::option::Option<(FieldLabel, Span)>,
    pub name: Ident,
    pub kind: FieldKind,
    pub number: Int,
    pub options: std::option::Option<OptionList>,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum FieldLabel {
    Optional = 1,
    Required = 2,
    Repeated = 3,
}

#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct MessageBody {
    pub items: Vec<MessageItem>,
    pub extensions: Vec<Extensions>,
    pub options: Vec<Option>,
    pub reserved: Vec<Reserved>,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum MessageItem {
    Field(Field),
    Enum(Enum),
    Message(Message),
    Extend(Extend),
    Oneof(Oneof),
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum FieldKind {
    Normal {
        ty: Ty,
        ty_span: Span,
    },
    Group {
        ty_span: Span,
        body: MessageBody,
    },
    Map {
        ty_span: Span,
        key_ty: Ty,
        key_ty_span: Span,
        value_ty: Ty,
        value_ty_span: Span,
    },
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Ty {
    Double,
    Float,
    Int32,
    Int64,
    Uint32,
    Uint64,
    Sint32,
    Sint64,
    Fixed32,
    Fixed64,
    Sfixed32,
    Sfixed64,
    Bool,
    String,
    Bytes,
    Named(TypeName),
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Oneof {
    pub name: Ident,
    pub options: Vec<Option>,
    pub fields: Vec<Field>,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Extend {
    pub extendee: TypeName,
    pub fields: Vec<Field>,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Reserved {
    pub kind: ReservedKind,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Extensions {
    pub ranges: Vec<ReservedRange>,
    pub options: std::option::Option<OptionList>,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum ReservedKind {
    Ranges(Vec<ReservedRange>),
    Names(Vec<Ident>),
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ReservedRange {
    pub start: Int,
    pub end: ReservedRangeEnd,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum ReservedRangeEnd {
    None,
    Int(Int),
    Max(Span),
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Enum {
    pub name: Ident,
    pub options: Vec<Option>,
    pub values: Vec<EnumValue>,
    pub reserved: Vec<Reserved>,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct EnumValue {
    pub name: Ident,
    pub number: Int,
    pub options: std::option::Option<OptionList>,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Service {
    pub name: Ident,
    pub options: Vec<Option>,
    pub methods: Vec<Method>,
    pub comments: Comments,
    pub span: Span,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Method {
    pub name: Ident,
    pub input_ty: TypeName,
    pub output_ty: TypeName,
    pub options: Vec<Option>,
    pub client_streaming: std::option::Option<Span>,
    pub server_streaming: std::option::Option<Span>,
    pub comments: Comments,
    pub span: Span,
}

impl Int {
    pub fn as_i32(&self) -> std::option::Option<i32> {
        if self.negative {
            if self.value == (i32::MAX as u64 + 1) {
                Some(i32::MIN)
            } else {
                Some(-i32::try_from(self.value).ok()?)
            }
        } else {
            i32::try_from(self.value).ok()
        }
    }

    pub fn as_i64(&self) -> std::option::Option<i64> {
        if self.negative {
            if self.value == (i64::MAX as u64 + 1) {
                Some(i64::MIN)
            } else {
                Some(-i64::try_from(self.value).ok()?)
            }
        } else {
            i64::try_from(self.value).ok()
        }
    }

    pub fn as_u32(&self) -> std::option::Option<u32> {
        if self.negative {
            None
        } else {
            u32::try_from(self.value).ok()
        }
    }

    pub fn as_u64(&self) -> std::option::Option<u64> {
        if self.negative {
            None
        } else {
            Some(self.value)
        }
    }
}

impl String {
    pub fn into_utf8(self) -> Result<(std::string::String, Span), Self> {
        match std::string::String::from_utf8(self.value) {
            Ok(string) => Ok((string, self.span)),
            Err(err) => Err(String {
                value: err.into_bytes(),
                span: self.span,
            }),
        }
    }
}

impl Ident {
    pub fn new(value: impl Into<std::string::String>, span: Range<usize>) -> Self {
        Ident {
            span,
            value: value.into(),
        }
    }
}

impl FullIdent {
    pub fn span(&self) -> Span {
        self.parts.first().unwrap().span.start..self.parts.last().unwrap().span.end
    }
}

impl TypeName {
    pub fn span(&self) -> Span {
        if let Some(leading_dot) = &self.leading_dot {
            join_span(leading_dot.clone(), self.name.span())
        } else {
            self.name.span()
        }
    }
}

impl Ty {
    pub fn proto_ty(&self) -> std::option::Option<field_descriptor_proto::Type> {
        match self {
            Ty::Double => Some(field_descriptor_proto::Type::Double),
            Ty::Float => Some(field_descriptor_proto::Type::Float),
            Ty::Int32 => Some(field_descriptor_proto::Type::Int32),
            Ty::Int64 => Some(field_descriptor_proto::Type::Int64),
            Ty::Uint32 => Some(field_descriptor_proto::Type::Uint32),
            Ty::Uint64 => Some(field_descriptor_proto::Type::Uint64),
            Ty::Sint32 => Some(field_descriptor_proto::Type::Sint32),
            Ty::Sint64 => Some(field_descriptor_proto::Type::Sint64),
            Ty::Fixed32 => Some(field_descriptor_proto::Type::Fixed32),
            Ty::Fixed64 => Some(field_descriptor_proto::Type::Fixed64),
            Ty::Sfixed32 => Some(field_descriptor_proto::Type::Sfixed32),
            Ty::Sfixed64 => Some(field_descriptor_proto::Type::Sfixed64),
            Ty::Bool => Some(field_descriptor_proto::Type::Bool),
            Ty::String => Some(field_descriptor_proto::Type::String),
            Ty::Bytes => Some(field_descriptor_proto::Type::Bytes),
            Ty::Named(_) => None,
        }
    }

    pub fn ty_name(&self) -> std::option::Option<std::string::String> {
        match self {
            Ty::Named(name) => Some(name.to_string()),
            _ => None,
        }
    }
}

impl OptionNamePart {
    pub fn span(&self) -> Span {
        match self {
            OptionNamePart::Ident(ident) => ident.span.clone(),
            OptionNamePart::Extension(_, span) => span.clone(),
        }
    }
}

impl OptionBody {
    pub fn has_name(&self, name: &str) -> bool {
        matches!(self.name.as_slice(), [OptionNamePart::Ident(ident)] if ident.value == name)
    }

    pub fn name_span(&self) -> Span {
        debug_assert!(!self.name.is_empty());
        join_span(
            self.name.first().unwrap().span(),
            self.name.last().unwrap().span(),
        )
    }

    pub fn span(&self) -> Span {
        join_span(self.name_span(), self.value.span())
    }
}

impl OptionValue {
    pub fn span(&self) -> Span {
        match self {
            OptionValue::Ident { span, .. } => span.clone(),
            OptionValue::Int(int) => int.span.clone(),
            OptionValue::Float(float) => float.span.clone(),
            OptionValue::String(string) => string.span.clone(),
            OptionValue::Aggregate(_, span) => span.clone(),
        }
    }

    pub fn to_token_string(&self) -> std::string::String {
        match self {
            OptionValue::String(s) => format!("\"{}\"", s),
            _ => self.to_string(),
        }
    }

    pub fn as_f64(&self) -> std::option::Option<f64> {
        match self {
            OptionValue::Ident {
                negative, ident, ..
            } => {
                let value = if ident.value == "nan" {
                    f64::NAN
                } else if ident.value == "inf" {
                    f64::INFINITY
                } else {
                    return None;
                };

                if *negative {
                    Some(-value)
                } else {
                    Some(value)
                }
            }
            OptionValue::Int(int) => {
                if int.negative {
                    Some(-(int.value as f64))
                } else {
                    Some(int.value as f64)
                }
            }
            OptionValue::Float(float) => Some(float.value),
            OptionValue::String(_) | OptionValue::Aggregate(_, _) => None,
        }
    }

    pub fn as_bool(&self) -> std::option::Option<bool> {
        match self {
            OptionValue::Ident {
                negative: false,
                ident,
                ..
            } if ident.value == "false" => Some(false),
            OptionValue::Ident {
                negative: false,
                ident,
                ..
            } if ident.value == "true" => Some(true),
            _ => None,
        }
    }
}

impl ReservedRange {
    pub fn start_span(&self) -> Span {
        self.start.span.clone()
    }

    pub fn end_span(&self) -> Span {
        match &self.end {
            ReservedRangeEnd::None => self.start.span.clone(),
            ReservedRangeEnd::Int(end) => end.span.clone(),
            ReservedRangeEnd::Max(end_span) => end_span.clone(),
        }
    }

    pub fn span(&self) -> Span {
        join_span(self.start_span(), self.end_span())
    }
}

impl From<Ident> for FullIdent {
    fn from(part: Ident) -> Self {
        FullIdent { parts: vec![part] }
    }
}

impl From<Vec<Ident>> for FullIdent {
    fn from(parts: Vec<Ident>) -> Self {
        debug_assert!(!parts.is_empty());
        FullIdent { parts }
    }
}

impl fmt::Display for Syntax {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Syntax::Proto2 => write!(f, "proto2"),
            Syntax::Proto3 => write!(f, "proto3"),
        }
    }
}

impl fmt::Display for Ident {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.value)
    }
}

impl fmt::Display for FullIdent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.parts[0])?;
        for part in &self.parts[1..] {
            write!(f, ".{}", part)?;
        }
        Ok(())
    }
}

impl fmt::Display for TypeName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.leading_dot.is_some() {
            write!(f, ".")?;
        }
        write!(f, "{}", self.name)
    }
}

impl fmt::Display for OptionValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            OptionValue::Ident {
                negative, ident, ..
            } => {
                if *negative {
                    write!(f, "-{}", ident)
                } else {
                    write!(f, "{}", ident)
                }
            }
            OptionValue::Int(int) => int.fmt(f),
            OptionValue::Float(float) => float.fmt(f),
            OptionValue::String(string) => string.fmt(f),
            OptionValue::Aggregate(message, _) => message.fmt(f),
        }
    }
}

impl fmt::Display for Int {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.negative {
            write!(f, "-{}", self.value)
        } else {
            write!(f, "{}", self.value)
        }
    }
}

impl fmt::Display for Float {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.value.fract() == 0.0 {
            write!(f, "{:.1}", self.value)
        } else {
            write!(f, "{}", self.value)
        }
    }
}

impl fmt::Debug for String {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let value = self.to_string();
        f.debug_struct("String")
            .field("value", &value)
            .field("span", &self.span)
            .finish()
    }
}

impl fmt::Display for String {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for &ch in self.value.as_slice() {
            match ch {
                b'\t' => f.write_str("\\t")?,
                b'\r' => f.write_str("\\r")?,
                b'\n' => f.write_str("\\n")?,
                b'\\' => f.write_str("\\\\")?,
                b'\'' => f.write_str("\\'")?,
                b'"' => f.write_str("\\\"")?,
                b'\x20'..=b'\x7e' => f.write_char(ch as char)?,
                _ => {
                    write!(f, "\\{:03o}", ch)?;
                }
            }
        }

        Ok(())
    }
}

#[test]
fn option_span() {
    let options = OptionBody {
        name: vec![
            OptionNamePart::Extension(
                TypeName {
                    leading_dot: Some(1..2),
                    name: FullIdent::from(Ident {
                        value: "ext".to_owned(),
                        span: 2..5,
                    }),
                },
                0..6,
            ),
            OptionNamePart::Ident(Ident {
                value: "ident".to_owned(),
                span: 7..12,
            }),
        ],
        value: OptionValue::Int(Int {
            negative: true,
            value: 5,
            span: 14..16,
        }),
    };

    assert_eq!(options.name_span(), 0..12);
    assert_eq!(options.span(), 0..16);
}

#[test]
fn fmt_syntax() {
    assert_eq!(Syntax::Proto2.to_string(), "proto2");
    assert_eq!(Syntax::Proto3.to_string(), "proto3");
}
