///This module contains all error Types.
use core::fmt::{Display, Formatter};
#[derive(Debug)]
///This is the error type for this crate
pub enum Error {
    ///This represents an error from the regular windows api
    ///String is the method the error occurred in
    ///u32 is the Error, that occurred
    Winapi(&'static str, u32),
    ///Gets returned from NTDLL calls.
    ///This maps, if NTStatus is considered a Warning or Error.
    ///(Because typically NTDLL calls don't succeed, even if the return type is just a Warning)
    Ntdll(i32),
    ///Gets returned, if a Wide String cannot be converted into a regular string.
    WTFConvert(widestring::error::DecodeUtf16Error), //Windows u16 string stuff
    #[cfg(feature = "std")]
    ///Passes errors from std::io.
    Io(std::io::Error),
    #[cfg(target_family = "windows")]
    ///Contains errors from the pelite crate.
    ///
    ///See [pelite::Error]
    Pelite(pelite::Error),
    ///This is mapped, if a certain thing could potentially be supported with more work, but just has not been implemented yet.
    Unsupported(Option<&'static str>),
    ///This represents Error Codes generated by this crate.
    InjectLib(CustomError),
}

#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
//It is fine for additional Error variants to appear in a patch release.
//Disappearing Error variants is always a breaking change.
#[non_exhaustive]
///This Represents Error Codes generated by this Crate
pub enum CustomError {
    ///This is thrown, if all available injectors cannot be used in a certain situation.
    ///This should only happen, if the feature x86tox64 is disabled, but the library is compiled into a x86 binary, which tries injecting into a x64 process.
    NoViableInjector,
    ///Special Return codes returned only by WaitForSingleObject
    WaitForSingleObject(u32),
    ///This occurs, if some predicate is supplied, that doesn't select an element in a module list
    ModuleListLoop,
    ///If for some reason a function call returns zero bytes, but succeeded, this error will get used
    ZeroBytes,
    ///If the LDR is unpopulated during a get_module_in_proc call
    LDRUninit,
    ///This errror Indicates, that there is possibly a problem with a structure in this crate
    InvalidStructure,
    ///Dll file path name does not end in a file
    DllPathNoFile,
    ///The Memory Page was not allocated in the expected process.
    MempageInvalidProcess,
    ///The input parameters were invalid.
    InvalidInput,
    ///The requested operation would have resulted in a Permission error.
    PermissionDenied,
    ///This occures when it was expexted, that a Library is Loaded, but not found.
    LibraryNotFound(alloc::string::String),
}

impl From<CustomError> for Error {
    fn from(x: CustomError) -> Self {
        Error::InjectLib(x)
    }
}
#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
    ///Converts a std::Io::Error into a Error for use in this crate
    fn from(e: std::io::Error) -> Self {
        Error::Io(e)
    }
}
impl Error {
    ///Gets the contents of Error::Winapi, if self holds data of that type
    fn get_winapi(&self) -> Option<(&&'static str, &u32)> {
        match self {
            Error::Winapi(x, y) => Some((x, y)),
            _ => None,
        }
    }
    ///Gets the contents of Error::NTDLL, if self holds data of that type
    fn get_ntdll(&self) -> Option<&i32> {
        match self {
            Error::Ntdll(x) => Some(x),
            _ => None,
        }
    }
    ///Gets the contents of Error::WTFConvert, if self holds data of that type
    fn get_wtfconvert(&self) -> Option<&widestring::error::DecodeUtf16Error> {
        match self {
            Error::WTFConvert(x) => Some(x),
            _ => None,
        }
    }
    #[cfg(feature = "std")]
    ///Gets the contents of Error::Io, if self holds data of that type
    fn get_io(&self) -> Option<&std::io::Error> {
        match self {
            Error::Io(x) => Some(x),
            _ => None,
        }
    }
    ///Gets the contents of Error::Unsupported, if self holds data of that type
    fn get_unsupported(&self) -> Option<&Option<&'static str>> {
        match self {
            Error::Unsupported(x) => Some(x),
            _ => None,
        }
    }

    #[cfg(target_family = "windows")]
    ///Gets the contents of Error::Pelite, if self holds data of that type
    fn get_pelite(&self) -> Option<&pelite::Error> {
        match self {
            Error::Pelite(x) => Some(x),
            _ => None,
        }
    }
    ///Gets the contents of Error::InjectLib, if self holds data of that type
    fn get_injectlib(&self) -> Option<&CustomError> {
        match self {
            Error::InjectLib(x) => Some(x),
            _ => None,
        }
    }
}
//due to equality testing for Error::Io, we cannot impl Eq for Error{}
impl PartialEq<Self> for Error {
    fn eq(&self, other: &Self) -> bool {
        fn helper(me: &Error, other: &Error) -> Option<bool> {
            Some(match me {
                Error::Winapi(x, y) => other.get_winapi()?.eq(&(x, y)),
                Error::Ntdll(x) => other.get_ntdll()?.eq(x),
                Error::WTFConvert(x) => other.get_wtfconvert()?.eq(x),
                #[cfg(feature = "std")]
                Error::Io(x) => other.get_io()?.kind() == x.kind(), //todo:improve equality testing for Error::Io
                #[cfg(target_family = "windows")]
                Error::Pelite(x) => other.get_pelite()?.eq(x),
                Error::InjectLib(x) => other.get_injectlib()?.eq(x),
                Error::Unsupported(x) => other.get_unsupported()?.eq(x),
            })
        }
        *helper(self, other).get_or_insert(false)
    }
}

#[cfg(target_family = "windows")]
mod windows {
    use crate::error::Error;
    use winapi::um::errhandlingapi::GetLastError;

    impl From<pelite::Error> for Error {
        fn from(e: pelite::Error) -> Self {
            Error::Pelite(e)
        }
    }
    impl From<&'static str> for Error {
        fn from(s: &'static str) -> Self {
            Error::Winapi(s, unsafe { GetLastError() })
        }
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        match self {
            Error::Winapi(s, n) => write!(f, "Winapi error:{} failed with code {}", s, n),
            Error::Ntdll(n) => write!(f, "Ntdll({:#x})", n),
            Error::WTFConvert(_) => write!(f, "WTFConvert: Buffer contained non UTF-8 characters."), //todo: should I print osstring here?
            Error::Unsupported(r) => write!(
                f,
                "Unsupported({})",
                if let Some(s) = r { s } else { "None" }
            ),
            #[cfg(feature = "std")]
            Error::Io(e) => write!(f, "io:{}", e),
            Error::InjectLib(e) => write!(f, "Inject-Lib({})", e),
            #[cfg(target_family = "windows")]
            Error::Pelite(e) => write!(f, "pelite({})", e),
        }
    }
}

impl Display for CustomError {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        match self {
            CustomError::NoViableInjector=>write!(f,"Could not find a viable injection method for the circumstances"),
            CustomError::WaitForSingleObject(x)=>write!(f,"WaitForSingleObject return code {:#x}",x),
            CustomError::ModuleListLoop=>write!(f,"We looped through the whole InLoadOrderModuleList, but still have no match. Aborting, because this would end in an endless loop."),
            CustomError::ZeroBytes=>write!(f,"Zero bytes read, but the requested type is not Zero sized."),
            CustomError::LDRUninit=>write!(f,"LDR is not initialised"),
            CustomError::InvalidStructure=>write!(f,"a structure in inject-lib is invalid."),
            CustomError::DllPathNoFile=>write!(f,"The Dll File Path provided did not point to a file."),
            CustomError::MempageInvalidProcess=>write!(f,"The Mempage did not belong to the expected Process."),
            CustomError::InvalidInput=>write!(f,"The Provided Input was invalid."),
            CustomError::PermissionDenied=>write!(f,"The requested Action would result in a Permission Error."),
            CustomError::LibraryNotFound(lib)=>write!(f,"It was expected, that the Library:'{}' would be loaded when it wasn't",lib),
        }
    }
}

#[derive(Debug, Clone)]
///Abstracts a NTStatus return type.
pub enum Ntdll {
    ///Maps, if Ntdll considers the NTStatus a Success
    Success(i32),
    ///Maps, if Ntdll considers the NTStatus a Information
    Information(i32),
    ///Maps, if Ntdll considers the NTStatus a Warning
    Warning(i32),
    ///Maps, if Ntdll considers the NTStatus an Error
    Error(i32),
    ///Maps, if nothing else maps. Ideally this should go unused
    Other(i32),
}

impl Ntdll {
    ///Get the contained Variable in the Ntdll enum
    pub fn get_status(&self) -> &i32 {
        match self {
            Ntdll::Error(v) => v,
            Ntdll::Warning(v) => v,
            Ntdll::Other(v) => v,
            Ntdll::Success(v) => v,
            Ntdll::Information(v) => v,
        }
    }
}
impl Display for Ntdll {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        match self {
            Ntdll::Error(v) => write!(f, "Ntdll::Error({:#x})", v),
            Ntdll::Warning(v) => write!(f, "Ntdll::Warning({:#x})", v),
            Ntdll::Other(v) => write!(f, "Ntdll::Other({:#x})", v),
            Ntdll::Success(v) => write!(f, "Ntdll::Success({:#x})", v),
            Ntdll::Information(v) => write!(f, "Ntdll::Information({:#x})", v),
        }
    }
}

#[cfg(windows)]
mod ntdll {
    use crate::error::{Error, Ntdll};
    use log::error;
    use winapi::shared::ntdef::{NTSTATUS, NT_ERROR, NT_INFORMATION, NT_SUCCESS, NT_WARNING};

    impl crate::error::Ntdll {
        ///Create a new Ntdll enum from a NTSTATUS
        pub fn new(n: NTSTATUS) -> Self {
            if NT_ERROR(n) {
                Ntdll::Error(n)
            } else if NT_WARNING(n) {
                Ntdll::Warning(n)
            } else if NT_INFORMATION(n) {
                Ntdll::Information(n)
            } else if NT_SUCCESS(n) {
                Ntdll::Success(n)
            } else {
                error!("");
                Ntdll::Other(n)
            }
        }
        ///Returns true, if the enum contains Error discriminant
        pub fn is_error(&self) -> bool {
            match self {
                Ntdll::Error(_) => true,
                _ => false,
            }
        }
        ///Returns true, if the enum contains Warning discriminant
        pub fn is_warning(&self) -> bool {
            match self {
                Ntdll::Warning(_) => true,
                _ => false,
            }
        }
        ///Returns true, if the enum contains Information discriminant
        pub fn is_info(&self) -> bool {
            match self {
                Ntdll::Information(_) => true,
                _ => false,
            }
        }
        ///Returns true, if the enum contains Success discriminant
        pub fn is_success(&self) -> bool {
            match self {
                Ntdll::Success(_) => true,
                _ => false,
            }
        }
        ///Returns true, if the enum contains Other discriminant
        pub fn is_other(&self) -> bool {
            match self {
                Ntdll::Other(_) => true,
                _ => false,
            }
        }
    }
    impl Into<Error> for Ntdll {
        ///Transform Ntdll enum into Error
        fn into(self) -> Error {
            match self {
                Ntdll::Error(v) => Error::Ntdll(v),
                Ntdll::Warning(v) => Error::Ntdll(v),
                Ntdll::Information(v) => Error::Ntdll(v),
                Ntdll::Success(v) => Error::Ntdll(v),
                Ntdll::Other(v) => Error::Ntdll(v),
            }
        }
    }
}
