/*
 * Oblaat.cpp
 * SQLite .NET‚Ì‚½‚ß‚Ì”–”ç
 */
#include "Oblaat.h"
#include <vcclr.h>

namespace Oblaat {

  namespace SQLite3 {

    private struct CallbackHolder {
      gcroot<Callback^>       entry_;
      gcroot<System::Object^> arg_;
      CallbackHolder(Callback^ cb, System::Object^ a) : entry_(cb), arg_(a)  {}
    };

    Statement::Statement() : stmt_(0) {
    }

    Statement::~Statement() {
      if ( sqlite3_finalize(stmt_) == SQLITE_OK ) {
        stmt_ = 0;
      }
    }

    Result Statement::reset() {
      return Result(sqlite3_reset(stmt_));
    }

    Result Statement::bind_int(int col, int value) {
      return Result(sqlite3_bind_int(stmt_, col, value));
    }

    Result Statement::bind_double(int col, double value) {
      return Result(sqlite3_bind_double(stmt_, col, value));
    }

    Result Statement::bind_null(int col) {
      return Result(sqlite3_bind_null(stmt_, col));
    }

    Result Statement::bind_text(int col, String^ value) {
      pin_ptr<__wchar_t> pin = &value->ToCharArray()[0];
      return Result(sqlite3_bind_text16(stmt_, col, pin, -1, SQLITE_TRANSIENT));
    }

    int Statement::column_int(int col) {
      return sqlite3_column_int(stmt_, col);
    }

    double  Statement::column_double(int col) {
      return sqlite3_column_double(stmt_, col);
    }

    String^ Statement::column_text(int col) {
      return gcnew String(static_cast<const __wchar_t*>( sqlite3_column_text16(stmt_, col)));
    }

    Type    Statement::column_type(int col) {
      return Type(sqlite3_column_type(stmt_,col));
    }

    Result Statement::finalize() {
      return Result(sqlite3_finalize(stmt_));
    }

    Result Statement::step() {
      return Result(sqlite3_step(stmt_));
    }

    Database::Database() {
      db_ = 0;
    }

    Database::~Database() {
      close();
    }

    Result Database::open(System::String^ filename) {
      pin_ptr<__wchar_t> pin = &filename->ToCharArray()[0];
      sqlite3* d;
      int result = sqlite3_open16(pin, &d);
      db_ = d;
      return Result(result);
    }

    Result Database::open() {
      return open(L":memory:");
    }

    Result Database::close() {
      return Result(sqlite3_close(db_));
    }

    String^ Database::errmsg() {
      return gcnew String(static_cast<const __wchar_t*>(sqlite3_errmsg16(db_)));
    }

    static int cli_callback(void* option, int count, __wchar_t** values, __wchar_t** names) {
      array<NVPair^>^ results = gcnew array<NVPair^>(count);
      for ( int i = 0; i < count; ++i ) {
        results[i] = gcnew NVPair(names[i],values[i]);
      }
      CallbackHolder* holder = static_cast<CallbackHolder*>(option);
      Callback^ callback = holder->entry_;
      return callback(holder->arg_, results) ? 0 : 1;
    }

    Result Database::exec(String^ sql, Callback^ callback, System::Object^ option, String^% errmsg) {
      __wchar_t* err;
      CallbackHolder holder(callback,option);
      pin_ptr<__wchar_t> wp = &sql->ToCharArray()[0];
      int result = sqlite3_exec16(db_, wp, callback == nullptr ? 0 : cli_callback, &holder, &err);
      if ( result != SQLITE_OK ) {
        errmsg = gcnew String(err);
        sqlite3_free(reinterpret_cast<char*>(err));
      }
      return Result(result);
    }

    Result Database::prepare(String^ sql, Statement^% statement) {
      pin_ptr<__wchar_t> wp = &sql->ToCharArray()[0];
      sqlite3_stmt* stmt;
      int result = sqlite3_prepare16(db_, wp, -1, &stmt, 0);
      if ( result == SQLITE_OK ) {
        statement = gcnew Statement();
        statement->stmt_ = stmt;
      }
      return Result(result); 
    }

  }

}

struct sqlite3 {};
struct sqlite3_stmt {};
