/*
 * くだもの屋さんLite (C++/CLI版)
 */
#include <sqlite3.h>
#include <vcclr.h>

// ----- C++/CLIのためのちょっとしたラッパー

// 名前と値の組
ref class sqlite3_cli_result {
public:
  System::String^ name;
  System::String^ value;
  sqlite3_cli_result(const wchar_t* n, const wchar_t* v) {
    name = gcnew System::String(n);
    value = gcnew System::String(v);
  }
};

// コールバック関数改めdelegate
delegate bool sqlite3_cli_callback(array<sqlite3_cli_result^>^ results);

namespace detail {

  // delegateを引き渡すためのホルダ
  struct sqlite3_callback_holder {
    gcroot<sqlite3_cli_callback^> callback_entry;
    sqlite3_callback_holder(sqlite3_cli_callback^ cb) : callback_entry(cb) {}
  };

  // sqlite3_exec16()からはこれがコールバックされる
  int cli_callback(void* option, int columnCount, wchar_t** columnValues, wchar_t** columnNames) {
    array<sqlite3_cli_result^>^ results = gcnew array<sqlite3_cli_result^>(columnCount);
    for ( int i = 0; i < columnCount; ++i ) {
      results[i] = gcnew sqlite3_cli_result(columnNames[i],columnValues[i]);
    }
    sqlite3_callback_holder* holder = static_cast<sqlite3_callback_holder*>(option);
    sqlite3_cli_callback^ callback = holder->callback_entry;
    return callback(results) ? 0 : 1;
  }

}

// .NET専用exec16
int sqlite3_exec16_cli(sqlite3* db, System::String^ sql, sqlite3_cli_callback^ callback, wchar_t **errmsg) {
  detail::sqlite3_callback_holder holder(callback);
  pin_ptr<wchar_t> wp = &sql->ToCharArray()[0];
  return sqlite3_exec16(db, wp, detail::cli_callback, &holder, errmsg);
}

// ここからおためし。callbackもC++/CLIのインタフェースで。

bool print_result(array<sqlite3_cli_result^>^ results) {
  for each ( sqlite3_cli_result^ result in results ) {
    System::Console::Write("{0} = [{1}] ", result->name, result->value);
  }
  System::Console::WriteLine();
  return true;
}

int main() {

  sqlite3* db;
  if ( sqlite3_open16(L":memory:", &db) ) {
    System::Console::Error->WriteLine("Error : Can't open database : {0}", 
      gcnew System::String(static_cast<const wchar_t*>(sqlite3_errmsg16(db)))); 
    sqlite3_close(db);
    return 1;
  }

  array<System::String^>^ statements = {
    "CREATE TABLE item (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL NOT NULL)", 
    "INSERT INTO item VALUES ( 1, 'ばなな', 123)", 
    "INSERT INTO item VALUES ( 2, 'りんご', 234)", 
    "INSERT INTO item VALUES ( 3, 'みかん', 345)", 
    "INSERT INTO item VALUES ( 4, 'なし',   456)", 
    "INSERT INTO item VALUES ( 5, 'もも',   567)", 

    "CREATE TABLE sales (id INTEGER PRIMARY KEY, quantity NOT NULL )", 
    "INSERT INTO sales VALUES ( 1, 6 )",
    "INSERT INTO sales VALUES ( 3, 4 )",
    "INSERT INTO sales VALUES ( 5, 2 )",

    "SELECT name, price, quantity, price*quantity FROM item, sales WHERE item.id = sales.id ORDER BY price*quantity",
  };

  for each ( System::String^ statement in statements ) {
    wchar_t * errmsg;
    System::Console::WriteLine(statement);
    if ( sqlite3_exec16_cli(db, statement, gcnew sqlite3_cli_callback(print_result), &errmsg) != SQLITE_OK ) {
      System::Console::Error->WriteLine("Error : {0}", gcnew System::String(errmsg));
      sqlite3_free(reinterpret_cast<char*>(errmsg));
      sqlite3_close(db);
      return 1;
    }
  }
  sqlite3_close(db);

  return 0;
}

struct sqlite3 {}; /* dirty workaround to suppress warning :LNK4248 */
