//! Experimental library for using SQLite with minimal SQL
//!
//! Liter generates complete SQL schema definitions from ordinary Rust `struct` definitions at compile-time.
//! Built on top of [`rusqlite`], it leverages powerful user-implementable traits to generate well-constrained and type-aware table definitions.
//!
//! SQL is not generated by the derive macros directly: they can only operate *textually*, so they invoke `const` functions instead.
//! These then have access to the types and their trait implementations, which give you control over how the SQL is generated.
//!
//! ## Basic Example
//!
//! Here's a very simple example of a database with just one table.
//!
//!```
//! use liter::{database, Table};
//!
//! #[database]
//! struct ShoppingList (
//!     Item
//! );
//!
//! #[derive(Table, PartialEq, Debug)]
//! struct Item {
//!     count: u32,
//!     name: String
//! }
//!
//! let list = ShoppingList::create_in_memory()?;
//!
//! let oranges = Item {
//!     count: 3,
//!     name: "Orange".to_string(),
//! };
//!
//! list.insert(&oranges)?;
//! let items = list.get_all::<Item>()?;
//!
//! assert_eq!(oranges, items[0]);
//! # Ok::<(), rusqlite::Error>(())
//!```
//!
//! Liter generates the following SQL schema for you, as well as the `SELECT` & `INSERT` statements.
//!
//!```sql
//! BEGIN TRANSACTION;
//! CREATE TABLE item (
//!     count INTEGER NOT NULL,
//!     name TEXT NOT NULL
//! ) STRICT;
//! END TRANSACTION;
//!```
//!
//! ## Schema Generation Overview
//!
//! There are several traits that make up a `liter` database, but it is actually quite straight-forward.
//! We'll start at the top (or root, if you'll think of it as a tree), and work our way down.
//!
//! A [`Schema`] defines a `liter` database by its constituent [`Table`]s, which is to say it defines the database by the tables it contains.
//! This trait is implemented by the `#[database]` proc-macro on a *tuple struct* declaring the [`Table`] types that are part of the database.
//!
//! The [`Table`] trait is implemented by using `#[derive(Table)]` on a regular struct.
//! Each row of the generated SQL table (named after the struct) will store an instance of this struct.
//! You might assume that each field of the struct represents a column in its table, and that is almost correct.
//!
//! The [`Value`] trait is an intermediary layer which represents one or more [`Column`]s.
//! As such, it is implemented for all types that implement [`Column`] -- which, as you may have guessed, represents a primitive data type that can be stored in a single SQL column -- but it is also implemented (and can be implemented by you) for types that require *multiple* SQL columns.
//! In fact, a [`Value`] can be defined not only as a set of [`Column`]s, but as a set of [`Value`]s.
//!
//! Though it is admittedly rather generically named, the [`Value`] trait is an important abstraction that allows defining database tables with reusable & composable components rather than just column primitives.
//! For instance, it enables easy foreign key references through the generic [`Ref`] struct, even to tables with composite primary keys.
//!
//! ## Example with Primary & Foreign Keys
//!
//! This slightly more complicated example showcases foreign key references & composite primary keys.
//!
//!```
//! use liter::{database, Table, Id, Ref};
//!
//! #[database]
//! struct Dictionary (
//!     Language,
//!     Word
//! );
//!
//! #[derive(Table)]
//! struct Language {
//!     #[key]
//!     id: Id,
//!     name: String
//! }
//!
//! #[derive(Table)]
//! struct Word {
//!     #[key]
//!     language: Ref<Language>,
//!     #[key]
//!     word: String,
//!     definition: String
//! }
//! let dict = Dictionary::create_in_memory()?;
//!
//! let mut lang = Language {
//!     id: Id::NULL,
//!     name: "Latin".to_string()
//! };
//! dict.create(&mut lang)?; // assigns the newly created Id
//!
//! let word = Word {
//!     language: Ref::make_ref(&lang),
//!     word: "nunc".to_string(),
//!     definition:
//!         "now, at present, at this time, at this very moment".to_string()
//! };
//! dict.insert(&word)?;
//! # Ok::<(), rusqlite::Error>(())
//!```
//!
//! Note that this time, a few more SQL statements were generated as part of the [`HasKey`] impls for `Language` & `Word`.
//!
//! And here's the generated schema:
//!```sql
//! BEGIN TRANSACTION;
//! CREATE TABLE language (
//!     id INTEGER NOT NULL,
//!     name TEXT NOT NULL,
//!     PRIMARY KEY ( id )
//! ) STRICT;
//! CREATE TABLE word (
//!     language INTEGER NOT NULL,
//!     word TEXT NOT NULL,
//!     definition TEXT NOT NULL,
//!     PRIMARY KEY ( language, word ),
//!     FOREIGN KEY (language) REFERENCES language
//!         ON UPDATE RESTRICT
//!         ON DELETE RESTRICT
//!         DEFERRABLE INITIALLY DEFERRED
//! ) STRICT;
//! END TRANSACTION;
//!```
//!


pub mod column;
pub use column::Column;
pub mod meta;
pub mod schema;
pub use schema::Schema;
pub mod table;
pub use table::{
	Entry,
	HasKey,
	Table
};
pub mod types;
pub use types::{
	Bind,
	Binder,
	Fetch
};
pub mod util;
pub mod value;
pub use value::Value;

pub use liter_derive::{
	database,
	Table,
	Value
};

use std::marker::PhantomData;
use std::path::Path;

use rusqlite::{
	Connection,
	OpenFlags,
	Error,
	Result as SqlResult
};
use rusqlite::types::{
	FromSql,
	ToSql,
	ValueRef,
	FromSqlResult,
	ToSqlOutput
};

use crate::column::Affinity;
use crate::meta::tuple::CloneFromRef;
use crate::table::HasSingleKey;
use crate::types::Fetcher;
use crate::value::{
	ForeignKey,
	ValueDef
};

/// The default flags minus SQLITE_OPEN_CREATE
const DB_OPEN_FLAGS: OpenFlags = OpenFlags::SQLITE_OPEN_READ_WRITE
	.union(OpenFlags::SQLITE_OPEN_URI)
	.union(OpenFlags::SQLITE_OPEN_NO_MUTEX);

#[derive(Debug)]
pub struct Database<S: Schema> {
	connection: Connection,
	schema: PhantomData<S>
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Id(Option<i64>);

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Ref<T: HasKey + ?Sized>(pub T::Key);

/* DATABASE */

impl<S: Schema> Database<S> {
	fn from_connection(connection: Connection) -> SqlResult<Self> {
		connection.pragma_update(None, "foreign_keys", "on")?;
		Ok(Self { connection, schema: PhantomData })
	}
	/// Open the database at the path
	pub fn open(path: &Path) -> SqlResult<Self> {
		Connection::open_with_flags(path, DB_OPEN_FLAGS)
			.and_then(Self::from_connection)
	}
	/// Create, initialize & open the database at the path
	///
	/// Note: this (incorrectly) returns an [`Error::InvalidPath`] if `path` already exists.
	pub fn init(path: &Path) -> SqlResult<Self> {
		if path.exists() {
			return Err(Error::InvalidPath(path.to_path_buf()));
		}
		let connection = Connection::open(path)?;
		connection.pragma_update(None, "foreign_keys", "on")?;
		connection.execute_batch(S::CREATE)?;
		Ok(Self { connection, schema: PhantomData })
	}
	pub fn create_in_memory() -> SqlResult<Self> {
		let new = Connection::open_in_memory().and_then(Self::from_connection)?;
		new.connection.execute_batch(S::CREATE)?;
		Ok(new)
	}

	pub fn debug_show(&self) -> SqlResult<()> {
		let mut q = self.connection.prepare("SELECT * FROM pragma_table_list")?;
		let mut rows = q.query([])?;
		println!("(schema, name, ty, ncol, wr, strict)");
		while let Some(row) = rows.next()? {
			let r: (String, String, String, u64, bool, bool) =
				row.try_into()?;
			let (schema, name, ty, ncol, wr, strict) = r;
			println!("{schema}, {name}, {ty}, {ncol}, {wr}, {strict}");
		}

		let mut q = self.connection.prepare("SELECT * FROM sqlite_schema")?;
		let mut rows = q.query([])?;
		println!();
		println!("Schema:");
		while let Some(row) = rows.next()? {
			let r: (String, String, String, u64, Option<String>) =
				row.try_into()?;
			let (ty, name, tbl_name, rootpage, sql) = r;
			match name == tbl_name {
				true => print!("{ty} {name}:  (@ {rootpage})"),
				false => print!("{ty} {name}:	(→ {tbl_name} | @ {rootpage})"),
			}
			match sql {
				Some(sql) => println!("\n{sql}"),
				None => println!("\t<no SQL>")
			}
		}
		println!();

		Ok(())

	}

	pub fn get_all<T: Entry>(&self) -> SqlResult<Vec<T>> {
		let mut stmt = self.connection.prepare(T::GET_ALL)?;
		let mut rows = stmt.query([])?;
		let mut entries = Vec::new();
		while let Some(row) = rows.next()? {
			entries.push(T::from_row(row)?);
		}
		Ok(entries)
	}

	pub fn get<T>(&self, key: <T as HasKey>::Key) -> SqlResult<Option<T>>
		where T: Entry + HasKey
	{
		let mut stmt = self.connection.prepare(T::GET_BY_KEY)?;
		Binder::make(&mut stmt).bind(&key)?;
		let mut rows = stmt.raw_query();
		rows.next()?
			.map(T::from_row)
			.transpose()
	}

	/// Special method to insert and set id to `last_insert_rowid`
	pub fn create<T>(&self, entry: &mut T) -> SqlResult<()>
		where T: Entry + HasSingleKey<Id>
	{
		if *entry.get_key() != Id::NULL {
			return Err(Error::ToSqlConversionFailure(format!(
				"tried to create entry that already had the ID {:?}",
				*entry.get_key()
			).into()));
		}
		let mut stmt = self.connection.prepare(T::INSERT)?;
		Binder::make(&mut stmt).bind(&*entry)?;
		let changes = stmt.raw_execute()?;
		if changes != 1 {
			return Err(Error::StatementChangedRows(changes));
		}
		let id = self.connection.last_insert_rowid();
		*entry.get_key_mut() = Id::from_i64(id);
		Ok(())
	}

	pub fn insert<T: Entry>(&self, entry: &T) -> SqlResult<usize> {
		let mut stmt = self.connection.prepare(T::INSERT)?;
		Binder::make(&mut stmt).bind(entry)?;
		stmt.raw_execute()
	}

	pub fn upsert<T: HasKey + Entry>(&self, entry: &T) -> SqlResult<usize> {
		let mut stmt = self.connection.prepare(T::UPSERT)?;
		Binder::make(&mut stmt).bind(entry)?;
		stmt.raw_execute()
	}
	pub fn update<T: HasKey + Entry>(&self, entry: &T) -> SqlResult<usize> {
		let mut stmt = self.connection.prepare(T::UPDATE)?;
		Binder::make(&mut stmt).bind(entry)?;
		stmt.raw_execute()
	}
	pub fn delete<T>(&self, key: &<T as HasKey>::Key) -> SqlResult<bool>
		where T: Entry + HasKey
	{
		let mut stmt = self.connection.prepare(T::DELETE)?;
		Binder::make(&mut stmt).bind(key)?;
		stmt.raw_execute().map(|i| i == 1)
	}

	pub fn execute<T: Bind>(&self, sql: &str, params: &T) -> SqlResult<usize> {
		let mut stmt = self.prepare(sql)?;
		Binder::make(&mut stmt).bind(params)?;
		stmt.raw_execute()
	}
	pub fn query_one<T: Fetch>(&self, sql: &str) -> SqlResult<T> {
		let mut stmt = self.prepare(sql)?;
		let mut rows = stmt.raw_query();
		rows.next()?
			.ok_or(Error::QueryReturnedNoRows)
			.and_then(T::from_row)
	}
	pub fn query_all<T: Fetch>(&self, sql: &str) -> SqlResult<Vec<T>> {
		let mut stmt = self.prepare(sql)?;
		let mut items = Vec::new();
		let mut rows = stmt.raw_query();
		while let Some(row) = rows.next()? {
			items.push(T::from_row(row)?);
		}
		Ok(items)
	}
	pub fn query_one_with<T, P>(&self, sql: &str, params: &P) -> SqlResult<T>
		where T: Fetch, P: Bind
	{
		let mut stmt = self.prepare(sql)?;
		Binder::make(&mut stmt).bind(params)?;
		let mut rows = stmt.raw_query();
		rows.next()?
			.ok_or(Error::QueryReturnedNoRows)
			.and_then(T::from_row)
	}
	pub fn query_all_with<T, P>(&self, sql: &str, params: &P)
		-> SqlResult<Vec<T>>
		where T: Fetch, P: Bind
	{
		let mut stmt = self.prepare(sql)?;
		Binder::make(&mut stmt).bind(params)?;
		let mut items = Vec::new();
		let mut rows = stmt.raw_query();
		while let Some(row) = rows.next()? {
			items.push(T::from_row(row)?);
		}
		Ok(items)
	}
}

impl<S: Schema> std::ops::Deref for Database<S> {
	type Target = Connection;
	fn deref(&self) -> &Self::Target {&self.connection}
}
impl<S: Schema> std::ops::DerefMut for Database<S> {
	fn deref_mut(&mut self) -> &mut Self::Target {&mut self.connection}
}

/* ID */

impl Id {
	pub const NULL: Self = Self(None);
	pub fn from_i64(id: i64) -> Self {Self(Some(id))}
}

impl FromSql for Id {
	fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
		i64::column_result(value).map(Some).map(Self)
	}
}
impl ToSql for Id {
	fn to_sql(&self) -> SqlResult<ToSqlOutput<'_>> {
		self.0.to_sql()
	}
}
crate::types::impl_from_to_sql_2!(Id);

impl Column for Id {
	const AFFINITY: Affinity = Affinity::Integer;
}

/* REFERENCE */

impl<T: HasKey<Key = Id>> Ref<T> {
	pub const NULL: Self = Self(Id::NULL);
}
impl<T: HasKey<Key = K>, K: CloneFromRef<T::Marker>> Ref<T> {
	pub fn make_ref(from: &T) -> Self {
		from.make_ref()
	}
}

impl<T: Table + HasKey> Value for Ref<T> {
	const DEFINITION: ValueDef = ValueDef {
		unique: false,
		nullable: false,
		inner: T::KEY_VALUE,
		reference: Some(ForeignKey::define_for::<T>()),
		checks: &[],
	};
	type References = T;
}

impl<T: Table + HasKey> Fetch for Ref<T> {
	fn fetch(fetcher: &mut Fetcher<'_>) -> SqlResult<Self> {
		T::Key::fetch(fetcher).map(Self)
	}
	fn try_fetch(fetcher: &mut Fetcher<'_>) -> SqlResult<Option<Self>> {
		T::Key::try_fetch(fetcher).map(|opt| opt.map(Self))
	}
}
impl<T: Table + HasKey> Bind for Ref<T> {
	const COLUMNS: usize = T::Key::COLUMNS;
	fn bind(&self, binder: &mut Binder<'_, '_>) -> SqlResult<()> {
		self.0.bind(binder)
	}
}
impl<T: Table + HasKey> Bind for &Ref<T> {
	const COLUMNS: usize = T::Key::COLUMNS;
	fn bind(&self, binder: &mut Binder<'_, '_>) -> SqlResult<()> {
		self.0.bind(binder)
	}
}
