#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "mysql.h"

static void test_error(MYSQL *mysql, int status) {
  if (status) {
    fprintf(stderr, "# Error: %s (errno: %d)\r\n", mysql_error(mysql), mysql_errno(mysql));
    exit(1);
  }
}

static void test_stmt_error(MYSQL_STMT *stmt, int status) {
  if (status) {
    fprintf(stderr, "# Error: %s (errno: %d)\r\n", mysql_stmt_error(stmt), mysql_stmt_errno(stmt));
    exit(1);
  }
}

void test(const char* statement) {
  MYSQL* mysql = mysql_init(NULL);

  printf("Statement: %s\r\n", statement);
  if (!mysql_real_connect(mysql, "", "root", "test", "demo", 0, "/tmp/mysql.sock", CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS|CLIENT_COMPRESS)) {
    test_error(mysql, 1);
  }

  printf("Server info: %s\r\n", mysql_get_server_info(mysql));

  MYSQL_STMT* stmt = mysql_stmt_init(mysql);
  if (!stmt) {
    test_error(mysql, 1);
  }

  int status;

  status = mysql_stmt_prepare(stmt, statement, strlen(statement));
  test_stmt_error(stmt, status);

  status = mysql_stmt_execute(stmt);
  test_stmt_error(stmt, status);

  do {
    int num_fields = mysql_stmt_field_count(stmt);
    if (num_fields>0) {
      printf(" Fields: %d\r\n", (int)num_fields);

      MYSQL_RES* rs_metadata = mysql_stmt_result_metadata(stmt);
      test_stmt_error(stmt, rs_metadata==NULL);

      MYSQL_FIELD* fields = mysql_fetch_fields(rs_metadata);

      MYSQL_BIND* rs_bind = (MYSQL_BIND*)malloc(sizeof(MYSQL_BIND)*num_fields);
      if (!rs_bind) {
        printf("Cannot allocate output buffers\r\n");
        exit(1);
      }

      memset(rs_bind, 0, sizeof (MYSQL_BIND)*num_fields);

      int int_data[16];
      my_bool is_null[16];

      for (int i = 0; i<num_fields; ++i) {
        rs_bind[i].buffer_type = fields[i].type;
        rs_bind[i].is_null = &is_null[i];

        switch (fields[i].type) {
        case MYSQL_TYPE_LONG:
          rs_bind[i].buffer = (char*)&(int_data[i]);
          rs_bind[i].buffer_length = sizeof(int_data);
          break;
        default:
          fprintf(stderr, "# ERROR: unexpected type: %d.\r\n", fields[i].type);
          exit(1);
        }
      }

      status = mysql_stmt_bind_result(stmt, rs_bind);
      test_stmt_error(stmt, status);

      while (1) {
        status = mysql_stmt_fetch(stmt);

        if (status==1) {
          test_stmt_error(stmt, status);
          break;
        }

        if (status==MYSQL_NO_DATA) {
          break;
        }

        for (int i = 0; i<num_fields; ++i) {
          switch (rs_bind[i].buffer_type) {
          case MYSQL_TYPE_LONG:
            if (*rs_bind[i].is_null) {
              printf("  val[%d] = NULL;", i);
            } else {
              printf("  val[%d] = %ld;", i, (long) *((int *) rs_bind[i].buffer));
            }

            break;
          default:
            printf("# unexpected type (%d)\r\n", rs_bind[i].buffer_type);
          }
        }
      }

      printf("\r\n");
      mysql_free_result(rs_metadata);
      free(rs_bind);
    }

    status = mysql_stmt_next_result(stmt);
    if (status>0) {
      test_stmt_error(stmt, status);
    }
  } while (status==0);

  mysql_stmt_close(stmt);
  mysql_close(mysql);
}

int main(int argc, char** argv) {
  mysql_library_init(0, NULL, NULL);
  printf("Client info: %s\r\n", mysql_get_client_info());
  test("CALL test1()");
  test("CALL test2()");
  mysql_library_end();
  return 0;
}
