/*
* Copyright 2024 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0

#![allow(unknown_lints)] // backward compatibility
#![allow(unused_unsafe)]
#![allow(clippy::useless_transmute)]
#![allow(clippy::redundant_closure_call)]
#![allow(clippy::missing_transmute_annotations)] // introduced in 1.79

mod tests;

#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
mod bindings {
	include!("bindings.rs");
}

use bindings::*;

use paste::paste;
use std::ffi::{c_char, c_int, c_void, CStr, CString, NulError};
use std::fmt::{Display, Formatter};
use std::marker::PhantomData;
use std::mem::transmute;
use std::ptr::null;
use std::rc::Rc;
use std::slice;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
	#[error("{0}")]
	InvalidInput(String),

	#[error("NulError from CString::new")]
	NulError(#[from] NulError),
	//
	// #[error("data store disconnected")]
	// IOError(#[from] std::io::Error),
	// #[error("the data for key `{0}` is not available")]
	// Redaction(String),
	// #[error("invalid header (expected {expected:?}, found {found:?})")]
	// InvalidHeader {
	//     expected: String,
	//     found: String,
	// },
	// #[error("unknown data store error")]
	// Unknown,
}

// see https://github.com/dtolnay/thiserror/issues/62
impl From<std::convert::Infallible> for Error {
	fn from(_: std::convert::Infallible) -> Self {
		unreachable!()
	}
}

fn c2r_str(str: *mut c_char) -> String {
	let mut res = String::new();
	if !str.is_null() {
		unsafe { res = CStr::from_ptr(str).to_string_lossy().to_string() };
		unsafe { ZXing_free(str as *mut c_void) };
	}
	res
}

fn c2r_vec(buf: *mut u8, len: c_int) -> Vec<u8> {
	let mut res = Vec::<u8>::new();
	if !buf.is_null() && len > 0 {
		unsafe { res = std::slice::from_raw_parts(buf, len as usize).to_vec() };
		unsafe { ZXing_free(buf as *mut c_void) };
	}
	res
}

fn last_error() -> Error {
	match unsafe { ZXing_LastErrorMsg().as_mut() } {
		None => panic!("Internal error: ZXing_LastErrorMsg() returned NULL"),
		Some(error) => Error::InvalidInput(c2r_str(error)),
	}
}

// MARK: - Convenience macros

macro_rules! last_error_or {
	($expr:expr) => {
		match unsafe { ZXing_LastErrorMsg().as_mut() } {
			None => Ok($expr),
			Some(error) => Err(Error::InvalidInput(c2r_str(error))),
		}
	};
}

macro_rules! last_error_if_null_or {
	($ptr:ident, $expr:expr) => {
		match $ptr.is_null() {
			true => Err(last_error()),
			false => Ok($expr),
		}
	};
}

macro_rules! make_zxing_class {
	($r_class:ident, $c_class:ident) => {
		paste! {
			pub struct $r_class(*mut $c_class);

			impl Drop for $r_class {
				fn drop(&mut self) {
					unsafe { [<$c_class _delete>](self.0) }
				}
			}
		}
	};
}

macro_rules! make_zxing_class_with_default {
	($r_class:ident, $c_class:ident) => {
		make_zxing_class!($r_class, $c_class);
		paste! {
			impl $r_class {
				pub fn new() -> Self {
					unsafe { $r_class([<$c_class _new>]()) }
				}
			}

			impl Default for $r_class {
				fn default() -> Self {
					Self::new()
				}
			}
		}
	};
}

macro_rules! getter {
	($class:ident, $c_name:ident, $r_name:ident, $conv:expr, $type:ty) => {
		pub fn $r_name(&self) -> $type {
			paste! { unsafe { $conv([<ZXing_ $class _ $c_name>](self.0)) } }
		}
	};
	($class:ident, $c_name:ident, $conv:expr, $type:ty) => {
		paste! { getter! { $class, $c_name, [<$c_name:snake>], $conv, $type } }
	};
}

/// Expands to a set of convenience accessors for a given wrapper type.
///
/// - A builder-style setter: `fn name(self, v: impl AsRef/Into) -> Self`
/// - A mutable setter: `fn set_name(&mut self, v: impl AsRef/Into) -> &mut Self`
/// - A getter: `fn get_name(&self) -> T`
///
/// Works for `String` and other value types. TODO: support slices/arrays
macro_rules! property {
	($class:ident, $c_name:ident, $r_name:ident, String) => {
		pub fn $r_name(self, v: impl AsRef<str>) -> Self {
			let cstr = CString::new(v.as_ref()).unwrap();
			paste! { unsafe { [<ZXing_ $class _set $c_name>](self.0, cstr.as_ptr()) } };
			self
		}

		paste! {
			pub fn [<set_ $r_name>](&mut self, v : impl AsRef<str>) -> &mut Self {
				let cstr = CString::new(v.as_ref()).unwrap();
				unsafe { [<ZXing_ $class _set $c_name>](self.0, cstr.as_ptr()) };
				self
			}

			pub fn [<get_ $r_name>](&self) -> String {
				unsafe { c2r_str([<ZXing_ $class _get $c_name>](self.0)) }
			}
		}
	};

	($class:ident, $c_name:ident, $r_name:ident, $type:ty) => {
		pub fn $r_name(self, v: impl Into<$type>) -> Self {
			paste! { unsafe { [<ZXing_ $class _set $c_name>](self.0, transmute(v.into())) } };
			self
		}

		paste! {
			pub fn [<set_ $r_name>](&mut self, v : impl Into<$type>) -> &mut Self {
				unsafe { [<ZXing_ $class _set $c_name>](self.0, transmute(v.into())) };
				self
			}

			pub fn [<get_ $r_name>](&self) -> $type {
				unsafe { transmute([<ZXing_ $class _get $c_name>](self.0)) }
			}
		}
	};

	($class:ident, $c_name:ident, $type:ty) => {
		paste! { property! { $class, $c_name, [<$c_name:snake>], $type } }
	};
}

macro_rules! make_zxing_enum {
	($name:ident { $($field:ident),* }) => {
		#[repr(u32)]
		#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
		pub enum $name {
			$($field = paste! { [<ZXing_ $name _ $field>] },)*
		}
	}
}

// MARK: - Enums

#[rustfmt::skip] // workaround for broken #[rustfmt::skip::macros(make_zxing_enum)]
make_zxing_enum!(ImageFormat { Lum, LumA, RGB, BGR, RGBA, ARGB, BGRA, ABGR });
#[rustfmt::skip]
make_zxing_enum!(ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI });
#[rustfmt::skip]
make_zxing_enum!(Binarizer { LocalAverage, GlobalHistogram, FixedThreshold, BoolCast });
#[rustfmt::skip]
make_zxing_enum!(TextMode { Plain, ECI, HRI, Escaped, Hex, HexECI });
#[rustfmt::skip]
make_zxing_enum!(EanAddOnSymbol { Ignore, Read, Require });

#[rustfmt::skip]
make_zxing_enum!(BarcodeFormat {
	Invalid, None, All, AllReadable, AllCreatable, AllLinear, AllMatrix, AllGS1,
	Codabar, Code39, PZN, Code93, Code128, ITF,
	DataBar, DataBarOmni, DataBarStk, DataBarStkOmni, DataBarLtd, DataBarExp, DataBarExpStk,
	EANUPC, EAN13, EAN8, EAN5, EAN2, ISBN, UPCA, UPCE,
	Telepen, TelepenAlpha, TelepenNumeric, OtherBarcode, DXFilmEdge,
	PDF417, CompactPDF417, MicroPDF417,
	Aztec, AztecCode, AztecRune,
	QRCode, QRCodeModel1, QRCodeModel2, MicroQRCode, RMQRCode,
	DataMatrix, MaxiCode
});

impl Display for BarcodeFormat {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", unsafe { c2r_str(ZXing_BarcodeFormatToString(transmute(*self))) })
	}
}

impl Display for ContentType {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", unsafe { c2r_str(ZXing_ContentTypeToString(transmute(*self))) })
	}
}

impl BarcodeFormat {
	pub fn symbology(self) -> BarcodeFormat {
		unsafe { transmute(ZXing_BarcodeFormatSymbology(transmute(self))) }
	}
}

// MARK: - BarcodeFormats

#[derive(Clone, Debug, Default)]
pub struct BarcodeFormats(pub Vec<BarcodeFormat>);

impl BarcodeFormats {
	pub fn as_slice(&self) -> &[BarcodeFormat] {
		&self.0
	}
	pub fn is_empty(&self) -> bool {
		self.0.is_empty()
	}
	pub fn len(&self) -> usize {
		self.0.len()
	}
	pub fn contains(&self, f: BarcodeFormat) -> bool {
		self.0.contains(&f)
	}
	pub fn iter(&self) -> std::slice::Iter<'_, BarcodeFormat> {
		self.0.iter()
	}

	pub fn list(filter: BarcodeFormat) -> Self {
		unsafe {
			let mut size: c_int = 0;
			let ptr = ZXing_BarcodeFormatsList(transmute(filter), &mut size) as *const BarcodeFormat;
			if ptr.is_null() || size == 0 {
				BarcodeFormats::default()
			} else {
				BarcodeFormats(slice::from_raw_parts(ptr, size as usize).to_vec())
			}
		}
	}
}

impl PartialEq<[BarcodeFormat]> for BarcodeFormats {
	fn eq(&self, other: &[BarcodeFormat]) -> bool {
		self.0.as_slice() == other
	}
}

impl PartialEq<BarcodeFormats> for [BarcodeFormat] {
	fn eq(&self, other: &BarcodeFormats) -> bool {
		self == other.0.as_slice()
	}
}

impl Display for BarcodeFormats {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", unsafe {
			c2r_str(ZXing_BarcodeFormatsToString(transmute(self.0.as_ptr()), self.0.len() as c_int))
		})
	}
}

// Add conversions so single values/slices/vecs can be passed conveniently
// impl From<BarcodeFormat> for BarcodeFormats {
// 	fn from(f: BarcodeFormat) -> Self {
// 		BarcodeFormats(vec![f])
// 	}
// }
impl From<Vec<BarcodeFormat>> for BarcodeFormats {
	fn from(v: Vec<BarcodeFormat>) -> Self {
		BarcodeFormats(v)
	}
}
impl From<&[BarcodeFormat]> for BarcodeFormats {
	fn from(s: &[BarcodeFormat]) -> Self {
		BarcodeFormats(s.to_vec())
	}
}

impl AsRef<[BarcodeFormat]> for BarcodeFormats {
	fn as_ref(&self) -> &[BarcodeFormat] {
		&self.0
	}
}

impl AsRef<[BarcodeFormat]> for BarcodeFormat {
	fn as_ref(&self) -> &[BarcodeFormat] {
		std::slice::from_ref(self)
	}
}

pub trait FromStr: Sized {
	fn from_str(str: impl AsRef<str>) -> Result<Self, Error>;
}

impl FromStr for BarcodeFormat {
	fn from_str(str: impl AsRef<str>) -> Result<BarcodeFormat, Error> {
		let cstr = CString::new(str.as_ref())?;
		let fmt = unsafe { ZXing_BarcodeFormatFromString(cstr.as_ptr()) };
		if fmt == ZXing_BarcodeFormat_Invalid {
			last_error_or!(BarcodeFormat::Invalid)
		} else {
			Ok(unsafe { transmute(fmt) })
		}
	}
}

impl FromStr for BarcodeFormats {
	fn from_str(str: impl AsRef<str>) -> Result<BarcodeFormats, Error> {
		let cstr = CString::new(str.as_ref())?;
		let mut size: c_int = 0;
		let ptr = unsafe { ZXing_BarcodeFormatsFromString(cstr.as_ptr(), &mut size) as *const BarcodeFormat };
		if ptr.is_null() || size == 0 {
			last_error_or!(BarcodeFormats::default())
		} else {
			Ok(BarcodeFormats(unsafe { slice::from_raw_parts(ptr, size as usize).to_vec() }))
		}
	}
}

// MARK: - ImageView

#[derive(Debug, PartialEq)]
struct ImageViewOwner<'a>(*mut ZXing_ImageView, PhantomData<&'a u8>);

impl Drop for ImageViewOwner<'_> {
	fn drop(&mut self) {
		unsafe { ZXing_ImageView_delete(self.0) }
	}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImageView<'a>(Rc<ImageViewOwner<'a>>);

impl<'a> From<&'a ImageView<'a>> for ImageView<'a> {
	fn from(img: &'a ImageView) -> Self {
		img.clone()
	}
}

impl<'a> ImageView<'a> {
	fn try_into_int<T: TryInto<c_int>>(val: T) -> Result<c_int, Error> {
		val.try_into().map_err(|_| Error::InvalidInput("Could not convert Integer into c_int.".to_string()))
	}

	/// Constructs an ImageView from a raw pointer and the width/height (in pixels)
	/// and row_stride/pix_stride (in bytes).
	///
	/// # Safety
	///
	/// The memory gets accessed inside the c++ library at random places between
	/// `ptr` and `ptr + height * row_stride` or `ptr + width * pix_stride`.
	/// Note that both the stride values could be negative, e.g. if the image
	/// view is rotated.
	pub unsafe fn from_ptr<T: TryInto<c_int>, U: TryInto<c_int>>(
		ptr: *const u8,
		width: T,
		height: T,
		format: ImageFormat,
		row_stride: U,
		pix_stride: U,
	) -> Result<Self, Error> {
		let iv = ZXing_ImageView_new(
			ptr,
			Self::try_into_int(width)?,
			Self::try_into_int(height)?,
			format as ZXing_ImageFormat,
			Self::try_into_int(row_stride)?,
			Self::try_into_int(pix_stride)?,
		);
		last_error_if_null_or!(iv, ImageView(Rc::new(ImageViewOwner(iv, PhantomData))))
	}

	pub fn from_slice<T: TryInto<c_int>>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result<Self, Error> {
		unsafe {
			let iv = ZXing_ImageView_new_checked(
				data.as_ptr(),
				data.len() as c_int,
				Self::try_into_int(width)?,
				Self::try_into_int(height)?,
				format as ZXing_ImageFormat,
				0,
				0,
			);
			last_error_if_null_or!(iv, ImageView(Rc::new(ImageViewOwner(iv, PhantomData))))
		}
	}

	pub fn cropped(self, left: i32, top: i32, width: i32, height: i32) -> Self {
		unsafe { ZXing_ImageView_crop((self.0).0, left, top, width, height) }
		self
	}

	pub fn rotated(self, degree: i32) -> Self {
		unsafe { ZXing_ImageView_rotate((self.0).0, degree) }
		self
	}
}

#[cfg(feature = "image")]
use image;

#[cfg(feature = "image")]
impl<'a> From<&'a image::GrayImage> for ImageView<'a> {
	fn from(img: &'a image::GrayImage) -> Self {
		ImageView::from_slice(img.as_ref(), img.width(), img.height(), ImageFormat::Lum).unwrap()
	}
}

#[cfg(feature = "image")]
impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> {
	type Error = Error;

	fn try_from(img: &'a image::DynamicImage) -> Result<Self, Error> {
		let format = match img {
			image::DynamicImage::ImageLuma8(_) => Some(ImageFormat::Lum),
			image::DynamicImage::ImageLumaA8(_) => Some(ImageFormat::LumA),
			image::DynamicImage::ImageRgb8(_) => Some(ImageFormat::RGB),
			image::DynamicImage::ImageRgba8(_) => Some(ImageFormat::RGBA),
			_ => None,
		};
		match format {
			Some(format) => Ok(ImageView::from_slice(img.as_bytes(), img.width(), img.height(), format)?),
			None => Err(Error::InvalidInput("Invalid image format (must be either luma8|lumaA8|rgb8|rgba8)".to_string())),
		}
	}
}

// MARK: - Image, Error

make_zxing_class!(Image, ZXing_Image);

impl Image {
	getter!(Image, width, transmute, i32);
	getter!(Image, height, transmute, i32);
	getter!(Image, format, transmute, ImageFormat);

	pub fn data(&self) -> Vec<u8> {
		let ptr = unsafe { ZXing_Image_data(self.0) };
		if ptr.is_null() {
			Vec::<u8>::new()
		} else {
			unsafe { std::slice::from_raw_parts(ptr, (self.width() * self.height()) as usize).to_vec() }
		}
	}
}

#[cfg(feature = "image")]
impl From<&Image> for image::GrayImage {
	fn from(img: &Image) -> image::GrayImage {
		image::GrayImage::from_vec(img.width() as u32, img.height() as u32, img.data()).unwrap()
	}
}

#[derive(Error, Debug, PartialEq)]
pub enum BarcodeError {
	#[error("")]
	None(),

	#[error("{0}")]
	Checksum(String),

	#[error("{0}")]
	Format(String),

	#[error("{0}")]
	Unsupported(String),
}

// MARK: - Point, Position

pub type PointI = ZXing_PointI;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Position {
	pub top_left: PointI,
	pub top_right: PointI,
	pub bottom_right: PointI,
	pub bottom_left: PointI,
}

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

impl Display for Position {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", unsafe {
			c2r_str(ZXing_PositionToString(*(self as *const Position as *const ZXing_Position)))
		})
	}
}

// MARK: - Barcode

make_zxing_class!(Barcode, ZXing_Barcode);

impl Barcode {
	getter!(Barcode, isValid, transmute, bool);
	getter!(Barcode, format, transmute, BarcodeFormat);
	getter!(Barcode, symbology, transmute, BarcodeFormat);
	getter!(Barcode, contentType, transmute, ContentType);
	getter!(Barcode, text, c2r_str, String);
	getter!(Barcode, symbologyIdentifier, c2r_str, String);
	getter!(Barcode, position, transmute, Position);
	getter!(Barcode, orientation, transmute, i32);
	getter!(Barcode, hasECI, has_eci, transmute, bool);
	getter!(Barcode, isInverted, transmute, bool);
	getter!(Barcode, isMirrored, transmute, bool);
	getter!(Barcode, lineCount, transmute, i32);
	getter!(Barcode, sequenceSize, transmute, i32);
	getter!(Barcode, sequenceIndex, transmute, i32);
	getter!(Barcode, sequenceId, c2r_str, String);

	pub fn bytes(&self) -> Vec<u8> {
		let mut len: c_int = 0;
		unsafe { c2r_vec(ZXing_Barcode_bytes(self.0, &mut len), len) }
	}
	pub fn bytes_eci(&self) -> Vec<u8> {
		let mut len: c_int = 0;
		unsafe { c2r_vec(ZXing_Barcode_bytesECI(self.0, &mut len), len) }
	}

	pub fn extra(&self) -> String {
		unsafe { c2r_str(ZXing_Barcode_extra(self.0, null())) }
	}

	pub fn extra_with_key(&self, key: impl AsRef<str>) -> String {
		let cstr = CString::new(key.as_ref()).unwrap();
		unsafe { c2r_str(ZXing_Barcode_extra(self.0, cstr.as_ptr())) }
	}

	pub fn error(&self) -> BarcodeError {
		let error_type = unsafe { ZXing_Barcode_errorType(self.0) };
		let error_msg = unsafe { c2r_str(ZXing_Barcode_errorMsg(self.0)) };
		#[allow(non_upper_case_globals)]
		match error_type {
			ZXing_ErrorType_None => BarcodeError::None(),
			ZXing_ErrorType_Format => BarcodeError::Format(error_msg),
			ZXing_ErrorType_Checksum => BarcodeError::Checksum(error_msg),
			ZXing_ErrorType_Unsupported => BarcodeError::Unsupported(error_msg),
			_ => panic!("Internal error: invalid ZXing_ErrorType"),
		}
	}

	pub fn to_svg_with(&self, opts: &BarcodeWriter) -> Result<String, Error> {
		let str = unsafe { ZXing_WriteBarcodeToSVG(self.0, opts.0) };
		last_error_if_null_or!(str, c2r_str(str))
	}

	pub fn to_svg(&self) -> Result<String, Error> {
		self.to_svg_with(&BarcodeWriter::default())
	}

	pub fn to_image_with(&self, opts: &BarcodeWriter) -> Result<Image, Error> {
		let img = unsafe { ZXing_WriteBarcodeToImage(self.0, opts.0) };
		last_error_if_null_or!(img, Image(img))
	}

	pub fn to_image(&self) -> Result<Image, Error> {
		self.to_image_with(&BarcodeWriter::default())
	}
}

// MARK: - BarcodeReader

make_zxing_class_with_default!(BarcodeReader, ZXing_ReaderOptions);

impl BarcodeReader {
	property!(ReaderOptions, TryHarder, bool);
	property!(ReaderOptions, TryRotate, bool);
	property!(ReaderOptions, TryInvert, bool);
	property!(ReaderOptions, TryDownscale, bool);
	property!(ReaderOptions, IsPure, bool);
	property!(ReaderOptions, ValidateOptionalChecksum, bool);
	property!(ReaderOptions, ReturnErrors, bool);
	property!(ReaderOptions, Binarizer, Binarizer);
	property!(ReaderOptions, EanAddOnSymbol, EanAddOnSymbol);
	property!(ReaderOptions, TextMode, TextMode);
	property!(ReaderOptions, MinLineCount, i32);
	property!(ReaderOptions, MaxNumberOfSymbols, i32);

	pub fn formats(self, v: impl AsRef<[BarcodeFormat]>) -> Self {
		unsafe { ZXing_ReaderOptions_setFormats(self.0, transmute(v.as_ref().as_ptr()), v.as_ref().len() as c_int) };
		self
	}

	pub fn set_formats(&mut self, v: impl AsRef<[BarcodeFormat]>) -> &mut Self {
		unsafe { ZXing_ReaderOptions_setFormats(self.0, transmute(v.as_ref().as_ptr()), v.as_ref().len() as c_int) };
		self
	}

	pub fn get_formats(&self) -> BarcodeFormats {
		unsafe {
			let mut size: c_int = 0;
			let ptr = ZXing_ReaderOptions_getFormats(self.0, &mut size) as *const BarcodeFormat;
			if ptr.is_null() || size == 0 {
				BarcodeFormats::default()
			} else {
				BarcodeFormats(slice::from_raw_parts(ptr, size as usize).to_vec())
			}
		}
	}

	pub fn from<'a, IV>(&self, image: IV) -> Result<Vec<Barcode>, Error>
	where
		IV: TryInto<ImageView<'a>>,
		IV::Error: Into<Error>,
	{
		let iv_: ImageView = image.try_into().map_err(Into::into)?;
		unsafe {
			let results = ZXing_ReadBarcodes((iv_.0).0, self.0);
			if !results.is_null() {
				let size = ZXing_Barcodes_size(results);
				let mut vec = Vec::<Barcode>::with_capacity(size as usize);
				for i in 0..size {
					vec.push(Barcode(ZXing_Barcodes_move(results, i)));
				}
				ZXing_Barcodes_delete(results);
				Ok(vec)
			} else {
				Err(last_error())
			}
		}
	}
}

// MARK: - BarcodeCreator

make_zxing_class!(BarcodeCreator, ZXing_CreatorOptions);

impl BarcodeCreator {
	pub fn new(format: BarcodeFormat) -> Self {
		unsafe { BarcodeCreator(ZXing_CreatorOptions_new(format as ZXing_BarcodeFormat)) }
	}

	property!(CreatorOptions, Options, String);

	pub fn from_str(&self, str: impl AsRef<str>) -> Result<Barcode, Error> {
		let cstr = CString::new(str.as_ref())?;
		let bc = unsafe { ZXing_CreateBarcodeFromText(cstr.as_ptr(), 0, self.0) };
		last_error_if_null_or!(bc, Barcode(bc))
	}

	pub fn from_slice(&self, data: impl AsRef<[u8]>) -> Result<Barcode, Error> {
		let data = data.as_ref();
		let bc = unsafe { ZXing_CreateBarcodeFromBytes(data.as_ptr() as *const c_void, data.len() as i32, self.0) };
		last_error_if_null_or!(bc, Barcode(bc))
	}
}

// MARK: - BarcodeWriter

make_zxing_class_with_default!(BarcodeWriter, ZXing_WriterOptions);

impl BarcodeWriter {
	property!(WriterOptions, Scale, i32);
	property!(WriterOptions, Rotate, i32);
	property!(WriterOptions, AddHRT, add_hrt, bool);
	property!(WriterOptions, AddQuietZones, bool);
}

// MARK: - Convenience Functions

pub fn read() -> BarcodeReader {
	BarcodeReader::default()
}

pub fn create(format: BarcodeFormat) -> BarcodeCreator {
	BarcodeCreator::new(format)
}

pub fn write() -> BarcodeWriter {
	BarcodeWriter::default()
}
