//! A simple crate implementing a struct wrapping a reader and writer that's used to create readers
//! to unique data.

use ::std::{
    cmp::Ordering::{self, Equal},
    hash::{Hash, Hasher},
    io::{self, prelude::*, SeekFrom},
    ptr,
    sync::Mutex,
};

/// A struct wrapping a `Mutex<T>` used for storing and retrieving data thought readers.
///
/// Note that `T` it's wrapped into a [`Mutex`] for ensure [`IOObj`] does not lock access to the
/// `IOInterner` and to guarantee will only lock at `Read` methods.
pub struct IOInterner<T: Write + Read + Seek> {
    /// The underlying writer wrapped into a `Mutex`.
    ///
    /// The [`IOEntry`] comparison algorithm would mess up if you do not ensure that already
    /// allocated data remain equal after releasing a lock,it's advisable to only write at the end.
    pub inner: Mutex<T>
}

impl<T: Write + Read + Seek> IOInterner<T> {
    /// Create a new `IOInterner` that uses as common storage `x`.
    #[inline]
    pub fn new(x: T) -> Self {
        Self { inner: Mutex::new(x) }
    }

    /// Invokes [`Write::flush`] on `self.inner`.
    ///
    /// # Panics
    ///
    /// This function panics if the [`Mutex`] it's poisoned.
    #[inline]
    pub fn flush(&self) -> io::Result<()> {
        self.inner.lock().unwrap().flush()
    }

    /// Creates a new `IOEntry` object that will be able to generate [`IOObj`] that always read
    /// same bytes as `buf` from `T` so `buf` will be written at the end if it's not already.
    ///
    /// The [`IOObj`] does not implement `Write` as that would mess up equality of two
    /// [`IOEntry`] instances pointing to the same position with same length which it's actually
    /// the comparison algorithm.
    ///
    /// # Panics
    ///
    /// This function panics if the [`Mutex`] it's poisoned.
    pub fn get_or_intern<U: Read + Seek>(&self, mut buf: U) -> io::Result<IOEntry<'_, T>> {
        let mut l = self.inner.lock().unwrap();

        let buf_len = buf.seek(SeekFrom::End(0))?;

        if buf_len == 0 {
            return Ok(IOEntry {
                start_init: 0,
                len: 0,
                guard: &self.inner,
            });
        }

        let len = l.seek(SeekFrom::End(0))?;

        for start in 0..len.saturating_sub(buf_len) {
            l.seek(SeekFrom::Start(start))?;
            buf.seek(SeekFrom::Start(0))?;

            if starts_with(&mut *l, &mut buf)? {
                return Ok(IOEntry {
                    start_init: start,
                    len: buf_len,
                    guard: &self.inner,
                });
            }
        }

        l.seek(SeekFrom::Start(len))?;
        buf.seek(SeekFrom::Start(0))?;
        io::copy(&mut buf, &mut *l)?;
        l.flush()?;

        Ok(IOEntry {
            start_init: len,
            len: buf_len,
            guard: &self.inner,
        })
    }
}

/// A struct generated by [`IOInterner::get_or_intern`].
#[derive(Clone, Copy)]
pub struct IOEntry<'a, T> {
    start_init: u64,
    len: u64,
    guard: &'a Mutex<T>,
}

impl<'a, T> IOEntry<'a, T> {
    /// Creates a new [`IOObj`] that can be [`Read`] and [`Seek`].
    pub fn get_object(&self) -> IOObj<'a, T> {
        IOObj {
            start_init: self.start_init,
            start: self.start_init,
            len: self.len,
            guard: self.guard
        }
    }
}

impl<'a, T> PartialEq for IOEntry<'a, T> {
    /// Compares the start position of `self` in the interner and it's length to `other`;returning
    /// `false` if either are different,`true` otherwise.
    /// 
    /// # Panics
    /// 
    /// It panics if `self` and `other` were created from different [`IOInterner`]s.
    fn eq(&self, other: &Self) -> bool {
        if ptr::eq(self.guard, other.guard) {
            self.start_init == other.start_init && self.len == other.len
        } else {
            panic!("entries from different interners cannot be compared,consider use `IOEntry::get_object` to create `IOObj`ects")
        }
    }
}

impl<'a, T> Eq for IOEntry<'a, T> {}

impl<'a, T> Hash for IOEntry<'a, T> {
    /// Hash the start position of the entry and the length.
    fn hash<H: Hasher>(&self, state: &mut H) {
        (self.start_init, self.len).hash(state);
    }
}

/// A struct generated by [`IOEntry::get_object`].
pub struct IOObj<'a, T> {
    start_init: u64,
    start: u64,
    len: u64,
    guard: &'a Mutex<T>,
}

impl<'a, T> Clone for IOObj<'a, T> {
    fn clone(&self) -> Self {
        unsafe { ptr::read(self) }
    }
}

impl<'a, T> IOObj<'a, T> {
    /// Converts back to an [`IOEntry`].
    #[inline]
    pub fn to_entry(&self) -> IOEntry<'a, T> {
        let mut a = self.clone();
        a.seek(SeekFrom::Start(0)).unwrap();

        IOEntry {
            start_init: a.start,
            len: a.len,
            guard: a.guard,
        }
    }

    fn fields_eq(&self, other: &Self) -> bool {
        ptr::eq(self.guard, other.guard) && self.start == other.start && self.len == other.len 
    }
}

impl<'a, T: Read + Seek> Read for IOObj<'a, T> {
    /// Invokes `Read::read` in the underlying reader seeking to the position this entry starts
    /// and only taking as much bytes as the length of this entry.
    #[inline]
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        if self.len == 0 {
            return Ok(0);
        }

        let mut l = self.guard.lock().unwrap();
        l.seek(SeekFrom::Start(self.start))?;

        let len = Read::take(&mut *l, self.len).read(buf)?;
        self.seek(SeekFrom::Current(len as _))?;
        Ok(len)
    }
}

impl<'a, T: Read + Seek> PartialEq for IOObj<'a, T> {
    fn eq(&self, other: &Self) -> bool {
        self.fields_eq(other) || eq(self.clone(), other.clone()).expect("io error while testing for equality")
    }
}

impl<'a, T: Read + Seek> Eq for IOObj<'a, T> {}

impl<'a, T: Read + Seek> Hash for IOObj<'a, T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        hash(self.clone(), state).expect("io error while hashing")
    }
}

impl<'a, T: Read + Seek> PartialOrd for IOObj<'a, T> {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl<'a, T: Read + Seek> Ord for IOObj<'a, T> {
    fn cmp(&self, other: &Self) -> Ordering {
        if self.fields_eq(other) {
            return Equal;
        }

        cmp(self.clone(), other.clone()).expect("io error while comparing the order")
    }
}

impl<'a, T> Seek for IOObj<'a, T> {
    #[inline]
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        match pos {
            SeekFrom::Current(x) => {
                if x.is_negative() {
                    let x = x.abs() as u64;

                    let new_start = self.start - x;

                    if new_start < self.start_init {
                        return Err(io::ErrorKind::InvalidInput.into());
                    }

                    self.start = new_start;
                    self.len += x;
                } else {
                    let x = x.abs() as u64;

                    if x > self.len {
                        self.start += self.len;
                        self.len = 0;
                    } else {
                        self.start += x;
                        self.len -= x;
                    }
                }

                Ok(self.start - self.start_init)
            }
            SeekFrom::Start(x) => {
                let new_start = self.start_init + x;

                if new_start > self.start {
                    self.seek(SeekFrom::Current((new_start - self.start) as i64))
                } else {
                    self.seek(SeekFrom::Current(-((self.start - new_start) as i64)))
                }
            }
            SeekFrom::End(x) => {
                self.start += self.len;
                self.len = 0;

                self.seek(SeekFrom::Current(x))
            }
        }
    }
}

/// Pass to `callback` slices with lenght up to 512 contained in both readers until either one
/// reach EOF and returns `None` or when `callback` returns `Some`,that case this function will
/// return it.
/// 
/// # Errors
/// 
/// See [`io::copy`].
pub fn io_op<R1: Read, R2: Read, T>(
    mut x: R1,
    mut y: R2,
    callback: impl Fn(&[u8], &[u8]) -> Option<T>,
) -> io::Result<Option<T>> {
    let mut buf1 = [0; 512];
    let mut buf2 = [0; 512];

    Ok(loop {
        let mut buf1r = &mut buf1[..];
        let mut buf2r = &mut buf2[..];

        let mut x = Read::take(&mut x, buf1r.len() as _);
        let mut y = Read::take(&mut y, buf1r.len() as _);

        let readed1 = io::copy(&mut x, &mut buf1r)? as usize;
        let readed2 = io::copy(&mut y, &mut buf2r)? as usize;
    
        let a = callback(&buf1[..readed1], &buf2[..readed2]);

        if a.is_some() {
            break a;
        }

        if readed1 == 0 || readed2 == 0 {
            break None;
        }
    })
}

/// Checks if the contents of the reader `x` are the ones of `y`.
///
/// # Errors
///
/// See [`io_op`].
pub fn eq<R1: Read, R2: Read>(x: R1, y: R2) -> io::Result<bool> {
    io_op(x, y, |x, y| if x == y { None } else { Some(()) }).map(|e| e.is_none())
}

/// Checks if the first contents of the reader `haystack` are the ones of `needle`,an empty needle
/// is always true.
///
/// # Errors
///
/// See [`io_op`].
pub fn starts_with<R1: Read, R2: Read>(haystack: R1, needle: R2) -> io::Result<bool> {
    io_op(haystack, needle, |haystack, needle| if haystack.starts_with(needle) { None } else { Some(()) }).map(|e| e.is_none())
}

/// Compares the contents of `x` to the ones of `y`,see
/// [`lexicographical comparison`][`Ord#lexicographical-comparison`].
/// 
/// # Errors
///
/// See [`io_op`].
pub fn cmp<R1: Read, R2: Read>(x: R1, y: R2) -> io::Result<Ordering> {
    io_op(x, y, |x, y| match x.cmp(y) {
        Equal => None,
        x => Some(x)
    }).map(|e| e.unwrap_or(Equal))
}

/// Hash the contents of the reader `x` to `state`.
///
/// # Errors
///
/// See [`io::copy`].
pub fn hash<R1: Read, H: Hasher>(
    mut x: R1,
    state: &mut H,
) -> io::Result<()> {
    let mut buf1 = [0; 512];

    let mut len = 0;

    loop {
        let mut buf1r = &mut buf1[..];

        let mut x = Read::take(&mut x, buf1r.len() as _);

        let readed1 = io::copy(&mut x, &mut buf1r)? as usize;

        Hash::hash_slice(&buf1[..readed1], state);
        len += readed1;        

        if readed1 == 0 {
            break;
        }
    }

    len.hash(state);

    Ok(())
}
