/***************************************************************************

  CField.c

  (c) 2000-2012 Benoît Minisini <gambas@users.sourceforge.net>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#define __CFIELD_C

#include "main.h"

#include "CField.h"


static int valid_field(CFIELD *_object)
{
  return !THIS->table || !THIS->table->conn || !THIS->table->conn->db.handle;
}

static bool exist_field(CTABLE *table, const char *name)
{
  DB_FIELD *fp;

	if (!name || !*name)
		return FALSE;

  if (table->create)
  {
    for (fp = table->new_fields; fp; fp = fp->next)
    {
      if (!strcmp(fp->name, name))
        return TRUE;
    }

    return FALSE;
  }
  else
    return table->driver->Field.Exist(&table->conn->db, table->name, (char *)name);
}

static bool check_field(CTABLE *table, char *name, bool must_exist)
{
  bool exist = exist_field(table, name);

  if (must_exist)
  {
    if (!exist)
    {
      GB.Error("Unknown field: &1.&2", table->name, name);
      return TRUE;
    }
  }
  else
  {
    if (exist)
    {
      GB.Error("Field already exists: &1.&2", table->name, name);
      return TRUE;
    }
  }

  return FALSE;
}


static bool check_type(int type)
{
  if (type == GB_T_BOOLEAN
      || type == GB_T_INTEGER
      || type == GB_T_LONG
      || type == GB_T_FLOAT
      || type == GB_T_DATE
      || type == GB_T_STRING
      || type == DB_T_SERIAL
      || type == DB_T_BLOB)
    return FALSE;

  GB.Error("Bad field type");
  return TRUE;
}


static CFIELD *make_field(CTABLE *table, const char *name, bool must_exist)
{
  CFIELD *_object;

  if (check_field(table, (char *)name, must_exist))
    return NULL;

  _object = GB.New(GB.FindClass("Field"), NULL, NULL);
  THIS->table = table;
  THIS->driver = table->conn->driver;
  THIS->name = GB.NewZeroString(name);

  return _object;
}


void CFIELD_free_info(DB_FIELD *info)
{
  GB.FreeString(&info->name);
  GB.StoreVariant(NULL, &info->def);
  info->type = GB_T_NULL;
  info->length = 0;
}

static void add_new_field(CTABLE *table, DB_FIELD *field)
{
  DB_FIELD **fp;

  fp = &table->new_fields;

  for(;;)
  {
    if (!*fp)
    {
      *fp = field;
      field->next = NULL;
      return;
    }

    fp = &((*fp)->next);
  }
}


void *CFIELD_get(CTABLE *table, const char *name)
{
  CFIELD *field = make_field(table, name, TRUE);
  table->driver->Field.Info(&table->conn->db, table->name, (char *)name, &field->info);
  return field;
}


int CFIELD_exist(CTABLE *table, const char *name)
{
  return exist_field(table, name);
}

void CFIELD_list(CTABLE *table, char ***list)
{
  table->driver->Field.List(&table->conn->db, table->name, list);
}

void CFIELD_release(CTABLE *table, void *_object)
{
	THIS->table = NULL;
}


/***************************************************************************

  Field

***************************************************************************/

BEGIN_METHOD_VOID(CFIELD_free)

	if (!valid_field(THIS))
  	GB.SubCollection.Remove(THIS->table->fields, THIS->name, 0);

  GB.FreeString(&THIS->name);

  CFIELD_free_info(&THIS->info);

END_METHOD



/*BEGIN_METHOD_VOID(CFIELD_delete)

  THIS->table->conn->driver->User.Delete(THIS->conn->handle, THIS->name);

END_METHOD*/


BEGIN_PROPERTY(CFIELD_name)

  GB.ReturnString(THIS->name);

END_PROPERTY


BEGIN_PROPERTY(CFIELD_type)

  GB.ReturnInteger(THIS->info.type);

END_PROPERTY


BEGIN_PROPERTY(CFIELD_length)

  GB.ReturnInteger(THIS->info.length);

END_PROPERTY


BEGIN_PROPERTY(CFIELD_default)

  GB.ReturnVariant(&THIS->info.def);

END_PROPERTY


BEGIN_PROPERTY(CFIELD_table)

  GB.ReturnObject(THIS->table);

END_PROPERTY


GB_DESC CFieldDesc[] =
{
  GB_DECLARE("Field", sizeof(CFIELD)),
  GB_NOT_CREATABLE(),
  GB_HOOK_CHECK(valid_field),

  GB_METHOD("_free", NULL, CFIELD_free, NULL),

  //GB_METHOD("Delete", NULL, CFIELD_delete, NULL),

  GB_PROPERTY_READ("Name", "s", CFIELD_name),
  GB_PROPERTY_READ("Type", "i", CFIELD_type),
  GB_PROPERTY_READ("Length", "i", CFIELD_length),
  GB_PROPERTY_READ("Default", "v", CFIELD_default),
  GB_PROPERTY_READ("Table", "Table", CFIELD_table),

  GB_END_DECLARE
};


/***************************************************************************

  .Table.Fields

***************************************************************************/

#undef THIS
#define THIS ((GB_SUBCOLLECTION)_object)

BEGIN_METHOD(CFIELD_add, GB_STRING name; GB_INTEGER type; GB_INTEGER length; GB_VARIANT def)

  CTABLE *table = GB.SubCollection.Container(THIS);
  char *name = GB.ToZeroString(ARG(name));
  DB_FIELD new_field, *info;

  if (!table->create)
  {
    GB.Error("Table already exists");
    return;
  }

  if (DB_CheckName(name, "field"))
    return;

  if (check_field(table, name, FALSE))
    return;

  /*field = make_field(table, name, FALSE);
  if (!field)
    return;

  GB.SubCollection.Add(THIS, STRING(name), LENGTH(name), field);*/

  new_field.next = NULL;

  new_field.type = VARG(type);
  if (check_type(new_field.type))
    return;

  new_field.length = VARGOPT(length, 0);
  if (new_field.length < 0)
    new_field.length = 0;
  else if (new_field.length > 65535)
    new_field.length = 65535;

  /*field->info = new_field;*/

  GB.Alloc(POINTER(&info), sizeof(DB_FIELD));

  info->next = NULL;
  info->type = new_field.type;
  info->length = new_field.length;

  info->def.type = GB_T_NULL;
  if (!MISSING(def))
    GB.StoreVariant(ARG(def), &info->def);

  info->name = GB.NewString(STRING(name), LENGTH(name));
  //DB_LowerString(info->name);

  add_new_field(table, info);

END_METHOD


GB_DESC CTableFieldsDesc[] =
{
  GB_DECLARE(".Table.Fields", 0), GB_INHERITS(".SubCollection"),

  GB_METHOD("Add", NULL, CFIELD_add, "(Name)s(Type)i[(Length)i(Default)v]"),
  //GB_METHOD("Remove", NULL, CFIELD_remove, "(Name)s"),

  GB_END_DECLARE
};


