#include <afxdb.h>

#define STRTOK strtok
#define LAFCMS_DL_MSSQLSERVER_SHAPE_DESC_TOKEN_CHAR        "/"
#define LAFCMS_CM_NULL_FIELD_TYPE                          LAFCMS_CM_FIELD_none
#define LAFCMS_CM_NULL_FIELD_WIDTH                         0

#define LAFCMS_ENUM_CM_FIELD_TYPES                         LAFCMS_CM_FIELD_none = 0x00, \
                                                           LAFCMS_CM_FIELD_autodetect = 0x10, \
                                                           LAFCMS_CM_FIELD_string = 0x01, \
                                                           LAFCMS_CM_FIELD_long = 0x02, \
                                                           LAFCMS_CM_FIELD_double = 0x03, \
                                                           LAFCMS_CM_FIELD_cmitem = 0x04, \
                                                           LAFCMS_CM_FIELD_old_datetime = 0x12, \
                                                           LAFCMS_CM_FIELD_datetime = 0x05, \
                                                           LAFCMS_CM_FIELD_boolean = 0x22

#define LAFCMS_ENUM_RETURN_CODES                           LAFCMS_success = 0, \
                                                           LAFCMS_success_recycled_object = 1, \
                                                           LAFCMS_fail = 2, \
                                                           LAFCMS_fail_eof = 3, \
                                                           LAFCMS_error_internal = 10, \
                                                           LAFCMS_error_mutex_timeout = 11, \
                                                           LAFCMS_error_invalid_object = 12, \
                                                           LAFCMS_error_invalid_value = 13, \
                                                           LAFCMS_error_cant_happen = 14, \
                                                           LAFCMS_error_not_implemented = 15, \
                                                           LAFCMS_error_type_mismatch = 16, \
                                                           LAFCMS_error_not_found = 17, \
                                                           LAFCMS_error_out_of_memory = 18, \
                                                           LAFCMS_error_cannot_set_property = 19, \
                                                           LAFCMS_error_no_connection = 20, \
                                                           LAFCMS_error_database_exception = 21, \
                                                           LAFCMS_error_connection_pool_failure = 22, \
                                                           LAFCMS_error_data_retrieve_failed = 23, \
                                                           LAFCMS_error_no_such_constant = 24, \
                                                           LAFCMS_error_bad_array = 25, \
                                                           LAFCMS_error_bad_variant = 26, \
                                                           LAFCMS_error_bad_bstr = 27, \
                                                           LAFCMS_error_queryinterface_failed = 28, \
                                                           LAFCMS_error_bad_object = 29, \
                                                           LAFCMS_error_connection_mismatch = 30, \
                                                           LAFCMS_error_invalid_connection = 31, \
                                                           LAFCMS_error_data_too_large = 32, \
                                                           LAFCMS_error_data_store_read_only = 33, \
                                                           LAFCMS_error_jni_api_failed = 34

enum cm_field_types                                    { LAFCMS_ENUM_CM_FIELD_TYPES };
enum return_codes                                      { LAFCMS_ENUM_RETURN_CODES };


#define LAFCMS_SET_RETURNCODE(RETURNCODE,VALUE)            do { RETURNCODE = VALUE; } while (0)
#define LAFCMS_ALLOCATE_MEMORY(VARIABLE,TYPE,COMMAND) \
                                                           (((LAFCMS_tmp_alloc = (COMMAND)) != NULL) ? ((VARIABLE = (TYPE)LAFCMS_tmp_alloc), LAFCMS_success) : LAFCMS_error_out_of_memory)
#define LAFCMS_ALLOCATE_MEMORY_PREP                        void *LAFCMS_tmp_alloc = NULL


struct shape
  {
  char *name;
  cm_field_types type;
  long width;
  };
struct field_desc
  {
  char *name;
  cm_field_types type;
  long width;
  struct field_desc *next;
  };
struct LAFCMS_tm
  {
  int year,
      month,
      day,
      hour,
      minute,
      second,
      millisecond;
  };
struct stacker
  {
  char *string;
  struct stacker *next;
  };



void LAFCMS_string_free(char **target)
  {
  if ((target != NULL) &&
      (*target != NULL))
    {
    free(*target);
    *target = NULL;
    }

  return;
  }

return_codes LAFCMS_string_copy_malloc(char **destination, char *source, unsigned int *strlen_destination = NULL)
  {
  return_codes return_code = LAFCMS_success;
  unsigned int tmp_strlen = 0;
  LAFCMS_ALLOCATE_MEMORY_PREP;

  if (destination != NULL)
    if (source == NULL)
      *destination = NULL;
    else
      {
      tmp_strlen = strlen(source);
      if ((return_code = LAFCMS_ALLOCATE_MEMORY(*destination, char *, malloc(tmp_strlen + 1))) == LAFCMS_success)
        strcpy(*destination, source);
      }

  if (strlen_destination != NULL)
    *strlen_destination = tmp_strlen;

  return(return_code);
  }

return_codes LAFCMS_string_copy_realloc(char **destination, char *source, unsigned int *strlen_destination = NULL)
  {
  return_codes return_code = LAFCMS_success;
  unsigned int tmp_strlen = 0;
  LAFCMS_ALLOCATE_MEMORY_PREP;

  if (destination != NULL)
    if (source == NULL)
      LAFCMS_string_free(destination);
    else
      {
      tmp_strlen = strlen(source);
      if ((return_code = LAFCMS_ALLOCATE_MEMORY(*destination, char *, realloc(*destination, tmp_strlen + 1))) == LAFCMS_success)
        strcpy(*destination, source);
      }

  if (strlen_destination != NULL)
    *strlen_destination = tmp_strlen;

  return(return_code);
  }

return_codes shape_init(struct shape *target_shape)
  {
  return_codes return_code = LAFCMS_success;

  if (target_shape != NULL)
    {
    target_shape->name = NULL;
    target_shape->type = LAFCMS_CM_NULL_FIELD_TYPE;
    target_shape->width = LAFCMS_CM_NULL_FIELD_WIDTH;
    }
  else
    LAFCMS_SET_RETURNCODE(return_code, LAFCMS_error_invalid_value);

  return(return_code);
  };

return_codes create_content_shape(char *shape_desc, struct shape **return_shape, long *return_num_shapes)
  {
  return_codes return_code = LAFCMS_success;
  struct field_desc *fields = NULL,
                    *tmp_field;
  char *tmp_shape_desc = NULL,
       *token = NULL;
  long i,
       num_fields,
       total_strlen;
  LAFCMS_ALLOCATE_MEMORY_PREP;

  if (shape_desc != NULL)
    {
    if ((return_code = LAFCMS_string_copy_malloc(&tmp_shape_desc, shape_desc)) == LAFCMS_success)
      {
      token = STRTOK(tmp_shape_desc, LAFCMS_DL_MSSQLSERVER_SHAPE_DESC_TOKEN_CHAR);
      num_fields = 0;
      total_strlen = 0;
      while ((token != NULL) &&
             (return_code == LAFCMS_success))
        {
        num_fields++;
        if (fields == NULL)
          {
          if ((return_code = LAFCMS_ALLOCATE_MEMORY(fields, struct field_desc *, malloc(sizeof(struct field_desc)))) == LAFCMS_success)
            tmp_field = fields;
          }
        else
          {
          if ((return_code = LAFCMS_ALLOCATE_MEMORY(tmp_field->next, struct field_desc *, malloc(sizeof(struct field_desc)))) == LAFCMS_success)
            tmp_field = tmp_field->next;
          }

        if (return_code == LAFCMS_success)
          {
          tmp_field->next = NULL;
          tmp_field->name = token;
          total_strlen += strlen(token);

          if ((token = STRTOK(NULL, LAFCMS_DL_MSSQLSERVER_SHAPE_DESC_TOKEN_CHAR)) != NULL)
            {
            tmp_field->type = (cm_field_types)atol(token);
            if ((token = STRTOK(NULL, LAFCMS_DL_MSSQLSERVER_SHAPE_DESC_TOKEN_CHAR)) != NULL)
              {
              tmp_field->width = atol(token);
              token = STRTOK(NULL, LAFCMS_DL_MSSQLSERVER_SHAPE_DESC_TOKEN_CHAR);
              }
            else
              LAFCMS_SET_RETURNCODE(return_code, LAFCMS_error_invalid_value);
            }
          else
            LAFCMS_SET_RETURNCODE(return_code, LAFCMS_error_invalid_value);
          }
        }

      if ((return_code == LAFCMS_success) &&
          ((return_code = LAFCMS_ALLOCATE_MEMORY(*return_shape, struct shape *, malloc(sizeof(struct shape) * (num_fields + 1)))) == LAFCMS_success))
        {
        *return_num_shapes = num_fields;
        shape_init(&(*return_shape)[num_fields]);

        tmp_field = fields;
        i = 0;
        while (tmp_field != NULL)
          {
          shape_init(&(*return_shape)[i]);
          if ((return_code = LAFCMS_string_copy_malloc(&(*return_shape)[i].name, tmp_field->name)) != LAFCMS_success)
            break;

          (*return_shape)[i].type = tmp_field->type;
          (*return_shape)[i].width = tmp_field->width;

          i++;
          tmp_field = tmp_field->next;
          }
        }

      tmp_field = fields;
      while (tmp_field != NULL)
        {
        fields = tmp_field;
        tmp_field = tmp_field->next;

        free(fields);
        }

      LAFCMS_string_free(&tmp_shape_desc);
      }
    }
  else
    LAFCMS_SET_RETURNCODE(return_code, LAFCMS_error_invalid_value);

  return(return_code);
  }

return_codes shape_destroy(struct shape *target_shape)
  {
  return_codes return_code = LAFCMS_success;

  if (target_shape != NULL)
    {
    LAFCMS_string_free(&target_shape->name);
    target_shape->type = LAFCMS_CM_NULL_FIELD_TYPE;
    target_shape->width = LAFCMS_CM_NULL_FIELD_WIDTH;
    }
  else
    LAFCMS_SET_RETURNCODE(return_code, LAFCMS_error_invalid_value);

  return(return_code);
  };

return_codes shape_array_free(struct shape **target_array)
  {
  long i;

  if (*target_array != NULL)
    {
    for (i = 0; (*target_array)[i].type != LAFCMS_CM_NULL_FIELD_TYPE; i++)
      shape_destroy(&(*target_array)[i]);

    free(*target_array);
    *target_array = NULL;
    }

  return(LAFCMS_success);
  }

void LAFCMS_tm_encode(unsigned long *destination, struct LAFCMS_tm *source)
  {
  if (source != NULL)
    {
    destination[0] = ((source->year & 0x3FFF) << 9) | ((source->month & 0x000F) << 5) | (source->day & 0x001F);
    destination[1] = ((source->hour & 0x001F) << 22) | ((source->minute & 0x003F) << 16) | ((source->second & 0x003F) << 10) | (source->millisecond & 0x000003FF);
    }
  else
    {
    destination[0] = 0;
    destination[1] = 0;
    }

  return;
  }

void trace_statement(char *statement)
  {
  long strlen_statement = (long)strlen(statement);
  char tmp_char = '\0';

  while (strlen_statement > 0)
    {
    if (strlen_statement >= 511)
      {
      tmp_char = statement[510];
      statement[510] = '\0';
      }

    TRACE("%s\n", statement);

    if (strlen_statement >= 511)
      statement[510] = tmp_char;

    strlen_statement -= 510;
    statement += 510;
    }

  return;
  }

int migrate(int argc, char *argv[])
  {
  CDatabase tmp_db,
            tmp_db2;
  CRecordset tmp_rs,
             tmp_rs2;
  CDBVariant tmp_dbvariant;
  char tmp_guid[27],
       tmp_id[27];
  char *tmp_shape = NULL,
       *tmp_ptr;
  char *tmp_tabledesc = NULL;
  struct shape *shapes = NULL;
  long num_shapes;
  char tmp_sql[1024];
  int success = 1;
  unsigned long new_value[2];
  struct LAFCMS_tm tmp_lafcms_tm;
  struct tm *tmp_tm;
  long i,
       num_statements = 0;
  struct stacker *stack = NULL,
                 *end_stack = NULL,
                 *tmp_stack;

  if (argc == 2)
    {
    try
      {
      tmp_db.OpenEx(argv[1], CDatabase::noOdbcDialog);
      tmp_db.BeginTrans();
      tmp_db2.OpenEx(argv[1], CDatabase::noOdbcDialog);

      tmp_rs.m_pDatabase = &tmp_db;
      tmp_rs2.m_pDatabase = &tmp_db2;

      printf("Generating SQL statements for Columns, Locations and Values...\n\n");

      if (tmp_rs.Open(CRecordset::forwardOnly, (char *)"SELECT * FROM [lafcms_location] WHERE ([shape] LIKE '%/18/0/%')"))
        {
        while (!tmp_rs.IsEOF())
          {
          tmp_rs.GetFieldValue((short)0, tmp_dbvariant, SQL_C_CHAR);
          strcpy(tmp_guid, (LPCTSTR)(*tmp_dbvariant.m_pstring));

          tmp_rs.GetFieldValue((short)1, tmp_dbvariant, SQL_C_CHAR);
          tmp_shape = (char *)malloc(tmp_dbvariant.m_pstring->GetLength() + 1024);
          strcpy(tmp_shape, (LPCTSTR)(*tmp_dbvariant.m_pstring));

          create_content_shape(tmp_shape, &shapes, &num_shapes);

          tmp_ptr = tmp_shape + sprintf(tmp_shape, "UPDATE [LAFCMS_location] SET [shape] = '");
          for (i = 0; i < num_shapes; i++)
            {
            if (shapes[i].type == LAFCMS_CM_FIELD_old_datetime)
              {
              shapes[i].type = LAFCMS_CM_FIELD_datetime;
              TRACE("Altering column %s in table %s\n", shapes[i].name, tmp_guid);
              printf("C");
              sprintf(tmp_sql, "ALTER TABLE [LAFCMS__%s] ALTER COLUMN [%s] BINARY(8)", tmp_guid, shapes[i].name);

              tmp_stack = (struct stacker *)malloc(sizeof(struct stacker));
              LAFCMS_string_copy_malloc(&tmp_stack->string, tmp_sql);
              tmp_stack->next = NULL;
              if (end_stack == NULL)
                {
                end_stack = tmp_stack;
                stack = tmp_stack;
                }
              else
                {
                end_stack->next = tmp_stack;
                end_stack = tmp_stack;
                }
              num_statements++;
              }

            tmp_ptr += sprintf(tmp_ptr, "%s/%ld/%ld/", shapes[i].name, shapes[i].type, shapes[i].width);
            }

          sprintf(tmp_ptr, "' WHERE ([location_id] = '%s')", tmp_guid);
          TRACE("Fixing location entry for %s\n", tmp_guid);
          printf("L");
          tmp_stack = (struct stacker *)malloc(sizeof(struct stacker));
          tmp_stack->string = tmp_shape;
          tmp_stack->next = NULL;
          if (end_stack == NULL)
            {
            end_stack = tmp_stack;
            stack = tmp_stack;
            }
          else
            {
            end_stack->next = tmp_stack;
            end_stack = tmp_stack;
            }
          num_statements++;

          sprintf(tmp_sql, "SELECT * FROM [LAFCMS__%s]", tmp_guid);
          if (tmp_rs2.Open(CRecordset::forwardOnly, tmp_sql))
            {
            while (!tmp_rs2.IsEOF())
              {
              tmp_rs2.GetFieldValue((short)0, tmp_dbvariant, SQL_C_CHAR);
              strcpy(tmp_id, (LPCTSTR)(*tmp_dbvariant.m_pstring));

              for (i = 0; i < num_shapes; i++)
                {
                if (shapes[i].type == LAFCMS_CM_FIELD_datetime)
                  {
                  tmp_rs2.GetFieldValue((short)(i + 1), tmp_dbvariant, SQL_C_SLONG);
                  tmp_tm = localtime(&tmp_dbvariant.m_lVal);
                  tmp_lafcms_tm.year = tmp_tm->tm_year + 1900;
                  tmp_lafcms_tm.month = tmp_tm->tm_mon + 1;
                  tmp_lafcms_tm.day = tmp_tm->tm_mday;
                  tmp_lafcms_tm.hour = tmp_tm->tm_hour;
                  tmp_lafcms_tm.minute = tmp_tm->tm_min;
                  tmp_lafcms_tm.second = tmp_tm->tm_sec;
                  tmp_lafcms_tm.millisecond = 0;

                  LAFCMS_tm_encode(new_value, &tmp_lafcms_tm);

                  TRACE("Converting %s of record %s (%d/%d/%d %d:%d:%d.%d) in table %s from %ld to 0x%.8X%.8X\n", shapes[i].name, tmp_id, tmp_lafcms_tm.month, tmp_lafcms_tm.day, tmp_lafcms_tm.year, tmp_lafcms_tm.hour, tmp_lafcms_tm.minute, tmp_lafcms_tm.second, tmp_lafcms_tm.millisecond, tmp_guid, tmp_dbvariant.m_lVal, new_value[0], new_value[1]);
                  printf("v");
                  sprintf(tmp_sql, "UPDATE [LAFCMS__%s] SET [%s] = 0x%.8X%.8X WHERE ([lafcms_item_id] = '%s')", tmp_guid, shapes[i].name, new_value[0], new_value[1], tmp_id);

                  tmp_stack = (struct stacker *)malloc(sizeof(struct stacker));
                  LAFCMS_string_copy_malloc(&tmp_stack->string, tmp_sql);
                  tmp_stack->next = NULL;
                  if (end_stack == NULL)
                    {
                    end_stack = tmp_stack;
                    stack = tmp_stack;
                    }
                  else
                    {
                    end_stack->next = tmp_stack;
                    end_stack = tmp_stack;
                    }
                  num_statements++;
                  }
                }

              tmp_rs2.MoveNext();
              }

            tmp_rs2.Close();
            }
          else
            {
            success = 0;
            break;
            }

          shape_array_free(&shapes);

          tmp_rs.MoveNext();
          }

        tmp_rs.Close();
        }
      else
        success = 0;

      if (success)
        {
        if (num_statements > 0)
          {
          printf("\n\nExecuting SQL statements...\n\n");
          tmp_stack = stack;
          i = 0;
          while (tmp_stack != NULL)
            {
            if (((long)((i / (num_statements * 1.0)) * 100) % 10) == 0)
              printf("%d", (long)((i / (num_statements * 1.0)) * 10));
            else
              printf(".");

            trace_statement(tmp_stack->string);
            tmp_db.ExecuteSQL(tmp_stack->string);
            LAFCMS_string_free(&tmp_stack->string);
            stack = stack->next;
            free(tmp_stack);
            tmp_stack = stack;
            i++;
            }

          printf("\n");
          }
        else
          printf("\n\nNothing to do!\n");

        tmp_db.CommitTrans();
        }
      else
        tmp_db.Rollback();

      tmp_db.Close();
      tmp_db2.Close();
      }
    catch (CDBException *tmp_exception)
      {
      printf("ERROR: %s\n", (char *)(LPCTSTR)tmp_exception->m_strError);

      tmp_exception->Delete();
      success = 0;
      }
    }
  else
    printf("USAGE: %s connection_string\n", argv[0]);

  return(!success);
  }