/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! This module implements database statements and provides precompiled query capabilities.

use core::ffi::c_void;
use std::ffi::CStr;

use asset_definition::{log_throw_error, ErrCode, Result, Value};
use asset_log::loge;

use crate::{
    database::Database,
    types::{sqlite_err_handle, SQLITE_DONE, SQLITE_OK, SQLITE_ROW},
};

type BindCallback = extern "C" fn(p: *mut c_void);
extern "C" {
    fn SqliteFinalize(stmt: *mut c_void) -> i32;
    fn SqlitePrepareV2(db: *mut c_void, z_sql: *const u8, pp_stmt: *mut *mut c_void, pz_tail: *mut *mut u8) -> i32;
    fn SqliteBindBlob(stmt: *mut c_void, index: i32, blob: *const u8, n: i32, callback: Option<BindCallback>) -> i32;
    fn SqliteBindInt64(stmt: *mut c_void, index: i32, value: i64) -> i32;
    fn SqliteStep(stmt: *mut c_void) -> i32;
    fn SqliteColumnName(stmt: *mut c_void, n: i32) -> *const u8;
    fn SqliteDataCount(stmt: *mut c_void) -> i32;
    fn SqliteColumnBlob(stmt: *mut c_void, i_col: i32) -> *const u8;
    fn SqliteColumnInt64(stmt: *mut c_void, i_col: i32) -> i64;
    fn SqliteColumnBytes(stmt: *mut c_void, i_col: i32) -> i32;
    fn SqliteColumnType(stmt: *mut c_void, i_col: i32) -> i32;
    fn SqliteReset(stmt: *mut c_void) -> i32;
}

const SQLITE_INTEGER: i32 = 1;
const SQLITE_BLOB: i32 = 4;
const SQLITE_NULL: i32 = 5;

#[repr(C)]
pub(crate) struct Statement<'b> {
    pub(crate) sql: String,
    db: &'b Database,
    handle: usize, // Poiner to statement.
}

impl<'b> Statement<'b> {
    /// Prepare a sql, you can use '?' for datas and bind datas later.
    pub(crate) fn prepare(sql: &str, db: &'b Database) -> Result<Statement<'b>> {
        let mut tail = 0usize;
        let mut sql_s = sql.to_string();
        sql_s.push('\0');
        let mut stmt = Statement { sql: sql_s, handle: 0, db };
        let ret = unsafe {
            SqlitePrepareV2(
                db.handle as _,
                stmt.sql.as_ptr(),
                &mut stmt.handle as *mut usize as _,
                &mut tail as *mut usize as _,
            )
        };
        if ret == 0 {
            Ok(stmt)
        } else {
            db.print_db_msg();
            log_throw_error!(sqlite_err_handle(ret), "Prepare statement failed, err={}", ret)
        }
    }

    /// Executing the precompiled sql. if succ
    /// If the execution is successful, return SQLITE_DONE for update, insert, delete and return SQLITE_ROW for select.
    pub(crate) fn step(&self) -> Result<i32> {
        let ret = unsafe { SqliteStep(self.handle as _) };
        if ret != SQLITE_ROW && ret != SQLITE_DONE {
            self.db.print_db_msg();
            log_throw_error!(sqlite_err_handle(ret), "Step statement failed, err={}", ret)
        } else {
            Ok(ret)
        }
    }

    /// Reset statement before bind data for insert statement.
    #[allow(dead_code)]
    pub(crate) fn reset(&self) -> Result<()> {
        let ret = unsafe { SqliteReset(self.handle as _) };
        if ret != SQLITE_OK {
            self.db.print_db_msg();
            log_throw_error!(sqlite_err_handle(ret), "Reset statement failed, err={}", ret)
        } else {
            Ok(())
        }
    }

    /// Bind data to prepared statement. The index is start from 1.
    pub(crate) fn bind_data(&self, index: i32, data: &Value) -> Result<()> {
        let ret = match data {
            Value::Bytes(b) => unsafe { SqliteBindBlob(self.handle as _, index, b.as_ptr(), b.len() as _, None) },
            Value::Number(i) => unsafe { SqliteBindInt64(self.handle as _, index, *i as _) },
            Value::Bool(b) => unsafe { SqliteBindInt64(self.handle as _, index, *b as _) },
        };
        if ret != SQLITE_OK {
            self.db.print_db_msg();
            log_throw_error!(sqlite_err_handle(ret), "Bind data failed, index={}, err={}", index, ret)
        } else {
            Ok(())
        }
    }

    /// Query the column name.
    pub(crate) fn query_column_name(&self, n: i32) -> Result<&str> {
        let s = unsafe { SqliteColumnName(self.handle as _, n) };
        if !s.is_null() {
            let name = unsafe { CStr::from_ptr(s as _) };
            if let Ok(rn) = name.to_str() {
                return Ok(rn);
            }
        }
        log_throw_error!(ErrCode::DatabaseError, "[FATAL][DB]Get asset column name failed.")
    }

    /// Get the count of columns in the query result.
    pub(crate) fn data_count(&self) -> i32 {
        unsafe { SqliteDataCount(self.handle as _) }
    }

    /// Query column and return a value of the Value type.
    pub(crate) fn query_column_auto_type(&self, i: i32) -> Result<Option<Value>> {
        let tp = self.column_type(i);
        let data = match tp {
            SQLITE_INTEGER => Some(Value::Number(self.query_column_int(i))),
            SQLITE_BLOB => {
                let blob = self.query_column_blob(i);
                if blob.is_empty() {
                    None
                } else {
                    Some(Value::Bytes(blob.to_vec()))
                }
            },
            SQLITE_NULL => None,
            t => return log_throw_error!(ErrCode::DatabaseError, "Unexpect column type: {}.", t),
        };
        Ok(data)
    }

    /// Query column datas in result set of blob type
    /// The index is start from 0.
    pub(crate) fn query_column_blob(&self, index: i32) -> &[u8] {
        let blob = unsafe { SqliteColumnBlob(self.handle as _, index) };
        let len = self.column_bytes(index);
        unsafe { core::slice::from_raw_parts(blob, len as _) }
    }

    /// Query column datas in result set of int type.
    /// The index is start with 0.
    pub(crate) fn query_column_int(&self, index: i32) -> u32 {
        unsafe { SqliteColumnInt64(self.handle as _, index) as u32 }
    }

    /// Get the bytes of data, you should first call query_column_text or query_column_blob,
    pub(crate) fn column_bytes(&self, index: i32) -> i32 {
        unsafe { SqliteColumnBytes(self.handle as _, index) }
    }

    /// Get the type of column.
    pub(crate) fn column_type(&self, index: i32) -> i32 {
        unsafe { SqliteColumnType(self.handle as _, index) }
    }
}

impl<'b> Drop for Statement<'b> {
    fn drop(&mut self) {
        if self.handle != 0 {
            let ret = unsafe { SqliteFinalize(self.handle as _) };
            if ret != SQLITE_OK {
                loge!("sqlite3 finalize fail ret {}", ret);
            }
        }
    }
}