use std::ffi::c_void;
use std::ptr::null_mut;

use crate::error::*;
use crate::{bindgen, serialization::CompressionType, Context, FromBytes, ToBytes};

/// Class to store a ciphertext element. The data for a ciphertext consists
/// of two or more polynomials, which are in Microsoft SEAL stored in a CRT
/// form with respect to the factors of the coefficient modulus. This data
/// itself is not meant to be modified directly by the user, but is instead
/// operated on by functions in the Evaluator class. The size of the backing
/// array of a ciphertext depends on the encryption parameters and the size
/// of the ciphertext (at least 2). If the PolyModulusDegree encryption
/// parameter is N, and the number of primes in the CoeffModulus encryption
/// parameter is K, then the ciphertext backing array requires precisely
/// 8*N*K*size bytes of memory. A ciphertext also carries with it the
/// parmsId of its associated encryption parameters, which is used to check
/// the validity of the ciphertext for homomorphic operations and decryption.
///
/// # Memory Management
/// The size of a ciphertext refers to the number of polynomials it contains,
/// whereas its capacity refers to the number of polynomials that fit in the
/// current memory allocation. In high-performance applications unnecessary
/// re-allocations should be avoided by reserving enough memory for the
/// ciphertext to begin with either by providing the desired capacity to the
/// constructor as an extra argument, or by calling the reserve function at
/// any time.
#[derive(Debug)]
pub struct Ciphertext {
	handle: *mut c_void,
}

unsafe impl Sync for Ciphertext {}
unsafe impl Send for Ciphertext {}

impl Clone for Ciphertext {
	fn clone(&self) -> Self {
		let mut handle = null_mut();

		convert_seal_error(unsafe { bindgen::Ciphertext_Create2(self.handle, &mut handle) })
			.expect("Fatal error: Failed to clone ciphertext");

		Self {
			handle,
		}
	}
}

impl AsRef<Ciphertext> for Ciphertext {
	fn as_ref(&self) -> &Self {
		self
	}
}

impl Ciphertext {
	/// Returns the handle to the underlying SEAL object.
	pub fn get_handle(&self) -> *mut c_void {
		self.handle
	}

	/// Creates a new empty plaintext. Use an encoder to populate with a value.
	pub fn new() -> Result<Self> {
		let mut handle: *mut c_void = null_mut();

		convert_seal_error(unsafe { bindgen::Ciphertext_Create1(null_mut(), &mut handle) })?;

		Ok(Self {
			handle,
		})
	}

	/// Returns the number of polynomials in this ciphertext.
	pub fn num_polynomials(&self) -> u64 {
		let mut size: u64 = 0;

		convert_seal_error(unsafe { bindgen::Ciphertext_Size(self.handle, &mut size) }).unwrap();

		size
	}

	/// Returns the number of components in the coefficient modulus.
	pub fn coeff_modulus_size(&self) -> u64 {
		let mut size: u64 = 0;

		convert_seal_error(unsafe { bindgen::Ciphertext_CoeffModulusSize(self.handle, &mut size) })
			.unwrap();

		size
	}

	/// Returns the value at a specific point in the coefficient array. This is
	/// not publically exported as it leaks the encoding of the array.
	#[allow(dead_code)]
	pub(crate) fn get_data(&self, index: usize) -> Result<u64> {
		let mut value: u64 = 0;

		convert_seal_error(unsafe {
			bindgen::Ciphertext_GetDataAt1(self.handle, index as u64, &mut value)
		})?;

		Ok(value)
	}

	/// Returns the coefficient in the form the ciphertext is currently in (NTT
	/// form or not). For BFV, this will be the coefficient in the residual
	/// number system (RNS) format.
	pub fn get_coefficient(&self, poly_index: usize, coeff_index: usize) -> Result<Vec<u64>> {
		let size = self.coeff_modulus_size();
		let mut data: Vec<u64> = Vec::with_capacity(size as usize);

		convert_seal_error(unsafe {
			let data_ptr = data.as_mut_ptr();

			bindgen::Ciphertext_GetDataAt2(
				self.handle,
				poly_index as u64,
				coeff_index as u64,
				data_ptr,
			)
		})?;

		unsafe { data.set_len(size as usize) };

		Ok(data.clone())
	}

	/// Returns whether the ciphertext is in NTT form.
	pub fn is_ntt_form(&self) -> bool {
		let mut result = false;

		convert_seal_error(unsafe { bindgen::Ciphertext_IsNTTForm(self.handle, &mut result) })
			.expect("Fatal error in Plaintext::is_ntt_form().");

		result
	}
}

impl PartialEq for Ciphertext {
	fn eq(&self, other: &Self) -> bool {
		self.as_bytes() == other.as_bytes()
	}
}

impl ToBytes for Ciphertext {
	fn as_bytes(&self) -> Result<Vec<u8>> {
		let mut num_bytes: i64 = 0;

		convert_seal_error(unsafe {
			bindgen::Ciphertext_SaveSize(self.handle, CompressionType::ZStd as u8, &mut num_bytes)
		})?;

		let mut data: Vec<u8> = Vec::with_capacity(num_bytes as usize);
		let mut bytes_written: i64 = 0;

		convert_seal_error(unsafe {
			let data_ptr = data.as_mut_ptr();

			bindgen::Ciphertext_Save(
				self.handle,
				data_ptr,
				num_bytes as u64,
				CompressionType::ZStd as u8,
				&mut bytes_written,
			)
		})?;

		unsafe { data.set_len(bytes_written as usize) };

		Ok(data)
	}
}

impl FromBytes for Ciphertext {
	fn from_bytes(context: &Context, bytes: &[u8]) -> Result<Self> {
		let ciphertext = Self::new()?;
		let mut bytes_read = 0i64;

		convert_seal_error(unsafe {
			bindgen::Ciphertext_Load(
				ciphertext.handle,
				context.handle,
				bytes.as_ptr() as *mut u8,
				bytes.len() as u64,
				&mut bytes_read,
			)
		})?;

		Ok(ciphertext)
	}
}

impl Drop for Ciphertext {
	fn drop(&mut self) {
		convert_seal_error(unsafe { bindgen::Ciphertext_Destroy(self.handle) })
			.expect("Internal error in Ciphertext::drop");
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn can_create_and_destroy_ciphertext() {
		let ciphertext = Ciphertext::new().unwrap();

		std::mem::drop(ciphertext);
	}
}
