use std::any::{Any, TypeId};
use std::borrow::Cow;
use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt;
use std::str;
use crate::kinds::UserError;
use crate::kinds::{error_name, tag_check};
use crate::traits::{ErrorKind, Field};
const FIELD_HINT: u16 = 0x_00_01;
const FIELD_DETAILS: u16 = 0x_00_02;
const FIELD_SERVER_TRACEBACK: u16 = 0x_01_01;
const FIELD_POSITION_START: u16 = 0x_FF_F1;
const FIELD_POSITION_END: u16 = 0x_FF_F2;
const FIELD_LINE: u16 = 0x_FF_F3;
const FIELD_COLUMN: u16 = 0x_FF_F4;
#[derive(Debug)]
pub struct Error(pub(crate) Box<Inner>);
pub struct Chain<'a>(Option<&'a (dyn StdError + 'static)>);
#[derive(Clone, Copy)]
pub struct Tag {
    pub(crate) bit: u32,
}
pub(crate) enum Source {
    Box(Box<dyn StdError + Send + Sync + 'static>),
    Ref(Box<dyn AsRef<dyn StdError + Send + Sync + 'static> + Send + Sync + 'static>),
}
#[derive(Debug)]
pub(crate) struct Inner {
    pub code: u32,
    pub messages: Vec<Cow<'static, str>>,
    pub error: Option<Source>,
    pub headers: HashMap<u16, bytes::Bytes>,
    pub fields: HashMap<(&'static str, TypeId), Box<dyn Any + Send + Sync>>,
}
impl Error {
    pub fn is<T: ErrorKind>(&self) -> bool {
        T::is_superclass_of(self.0.code)
    }
    pub fn has_tag(&self, tag: Tag) -> bool {
        tag_check(self.0.code, tag.bit)
    }
    pub fn chain(&self) -> Chain {
        Chain(Some(self))
    }
    pub fn context<S: Into<Cow<'static, str>>>(mut self, msg: S) -> Error {
        self.0.messages.push(msg.into());
        self
    }
    pub fn headers(&self) -> &HashMap<u16, bytes::Bytes> {
        &self.0.headers
    }
    pub fn with_headers(mut self, headers: HashMap<u16, bytes::Bytes>) -> Error {
        self.0.headers = headers;
        self
    }
    pub fn kind_name(&self) -> &str {
        error_name(self.0.code)
    }
    pub fn kind_debug(&self) -> impl fmt::Display {
        format!("{} [0x{:08X}]", error_name(self.0.code), self.0.code)
    }
    pub fn initial_message(&self) -> Option<&str> {
        self.0.messages.first().map(|m| &m[..])
    }
    pub fn contexts(&self) -> impl DoubleEndedIterator<Item = &str> {
        self.0.messages.iter().skip(1).map(|m| &m[..])
    }
    fn header(&self, field: u16) -> Option<&str> {
        if let Some(value) = self.headers().get(&field) {
            if let Ok(value) = str::from_utf8(value) {
                return Some(value);
            }
        }
        None
    }
    fn usize_header(&self, field: u16) -> Option<usize> {
        self.header(field)
            .and_then(|x| x.parse::<u32>().ok())
            .map(|x| x as usize)
    }
    pub fn hint(&self) -> Option<&str> {
        self.header(FIELD_HINT)
    }
    pub fn details(&self) -> Option<&str> {
        self.header(FIELD_DETAILS)
    }
    pub fn server_traceback(&self) -> Option<&str> {
        self.header(FIELD_SERVER_TRACEBACK)
    }
    pub fn position_start(&self) -> Option<usize> {
        self.usize_header(FIELD_POSITION_START)
    }
    pub fn position_end(&self) -> Option<usize> {
        self.usize_header(FIELD_POSITION_END)
    }
    pub fn line(&self) -> Option<usize> {
        self.usize_header(FIELD_LINE)
    }
    pub fn column(&self) -> Option<usize> {
        self.usize_header(FIELD_COLUMN)
    }
    pub(crate) fn unknown_headers(&self) -> impl Iterator<Item = (&u16, &bytes::Bytes)> {
        self.headers().iter().filter(|(key, _)| {
            **key != FIELD_HINT
                && **key != FIELD_DETAILS
                && **key != FIELD_POSITION_START
                && **key != FIELD_POSITION_END
                && **key != FIELD_LINE
                && **key != FIELD_COLUMN
        })
    }
    pub fn from_code(code: u32) -> Error {
        Error(Box::new(Inner {
            code,
            messages: Vec::new(),
            error: None,
            headers: HashMap::new(),
            fields: HashMap::new(),
        }))
    }
    pub fn code(&self) -> u32 {
        self.0.code
    }
    pub fn refine_kind<T: ErrorKind>(mut self) -> Error {
        self.0.code = T::CODE;
        self
    }
    pub fn set<T: Field>(mut self, value: impl Into<T::Value>) -> Error {
        self.0
            .fields
            .insert((T::NAME, TypeId::of::<T::Value>()), Box::new(value.into()));
        self
    }
    pub fn get<T: Field>(&self) -> Option<&T::Value> {
        self.0
            .fields
            .get(&(T::NAME, TypeId::of::<T::Value>()))
            .and_then(|bx| bx.downcast_ref::<T::Value>())
    }
}
impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let kind = self.kind_name();
        if f.alternate() {
            write!(f, "{}", kind)?;
            for msg in self.0.messages.iter().rev() {
                write!(f, ": {}", msg)?;
            }
            if let Some(mut src) = self.source() {
                write!(f, ": {}", src)?;
                while let Some(next) = src.source() {
                    write!(f, ": {}", next)?;
                    src = next;
                }
            }
        } else if let Some(last) = self.0.messages.last() {
            write!(f, "{}: {}", kind, last)?;
        } else {
            write!(f, "{}", kind)?;
        }
        if let Some((line, col)) = self.line().zip(self.column()) {
            write!(f, " (on line {}, column {})", line, col)?;
        }
        if let Some(hint) = self.hint() {
            write!(f, "\n  Hint: {}", hint)?;
        }
        if let Some(detail) = self.details() {
            write!(f, "\n  Detail: {}", detail)?;
        }
        Ok(())
    }
}
impl StdError for Error {
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
        self.0.error.as_ref().map(|s| match s {
            Source::Box(b) => b.as_ref() as &dyn std::error::Error,
            Source::Ref(b) => (**b).as_ref() as &dyn std::error::Error,
        })
    }
}
impl fmt::Debug for Source {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Source::Box(b) => fmt::Debug::fmt(b.as_ref(), f),
            Source::Ref(b) => fmt::Debug::fmt((**b).as_ref(), f),
        }
    }
}
impl<T> From<T> for Error
where
    T: AsRef<dyn StdError + Send + Sync + 'static> + Send + Sync + 'static,
{
    fn from(err: T) -> Error {
        UserError::with_source_ref(err)
    }
}
impl<'a> Iterator for Chain<'a> {
    type Item = &'a (dyn StdError + 'static);
    fn next(&mut self) -> Option<Self::Item> {
        let result = self.0.take();
        self.0 = result.and_then(|e| e.source());
        result
    }
}