// DBF file read/write package

#include <stdio.h>
#include <io.h>
#include <dir.h>
#include <mem.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "dbftable.h"

struct file_header
{
  unsigned char id, year, month, day;
  long number_of_records;
  short header_size, record_size;
  char unused[20];
};

struct file_header_field_descriptor
{
  char name[11];
  char type;
  char unused1[4];
  unsigned char width;
  unsigned char precision;
  char unused2[14];
};


const dbf_field_descriptor *dbf_table::add_field(char type,
  const char *name, int width, int precision)
{
  if (!is_open || mode != DBF_WRITE || field_info(name) != NULL ||
    strlen(name) > 10 || nrecs > 0)
  {
    return NULL;
  }
  // determine proper parameters
  switch (type)
  {
    case 'C':
      if (width < 1 || width > 254)
        return NULL;
      precision = 0;
      break;
    case 'N':
      if (width < 1 || width > 19 || precision < 0 || precision > 15 ||
        precision != 0 && precision + 2 > width)
      {
        return NULL;
      }
      break;
    case 'D':
      width = 8;
      precision = 0;
      break;
    case 'L':
      width = 1;
      precision = 0;
      break;
    default:
      return NULL;
  }
  // write descriptor block to file header
  file_header_field_descriptor descriptor;
  memset(&descriptor, 0, sizeof(descriptor));
  {
    char *s = descriptor.name;
    const char *t = name;
    while (*t != 0)
      *s++ = toupper(*t++);
  }
  descriptor.type = type;
  descriptor.width = width;
  descriptor.precision = precision;
  write_error = write_error ||
    fwrite(&descriptor, sizeof(descriptor), 1, fp) != 1;
  internal_field_descriptor *f = new internal_field_descriptor;
  f->type = type;
  f->width = width;
  f->precision = precision;
  f->next = NULL;
  memcpy(f->name, descriptor.name, sizeof(descriptor.name));
  f->name[sizeof(descriptor.name)] = 0;
  if (first == NULL)
  {
    first = f;
    f->offset = 1;
  }
  else
  {
    internal_field_descriptor *q = first;
    while (q->next != NULL)
      q = q->next;
    q->next = f;
    f->offset = q->offset + q->width;
  }
  recsize += width;
  header_size += sizeof(file_header_field_descriptor);
  delete record;
  record = new char[recsize];
  clear_record_buffer();
  return f;
}


bool dbf_table::append(void)
{
  if (mode == DBF_APPEND || mode == DBF_WRITE)
  {
    finish_header_if_necessary();
    nrecs++;
    write_error = write_error || fwrite(record, recsize, 1, fp) != 1;
    clear_record_buffer();
    return !write_error;
  }
  return false;
}

/*----------------------------------------------------------------------------
This function finds a field with the specified name, and returns NULL if
the field does not exist or the current record number is out of range.
----------------------------------------------------------------------------*/

const dbf_field_descriptor
  *dbf_table::check_recno_and_find_field(const char *name) const
{
  return 1 <= recno && recno <= nrecs ? field_info(name) : NULL;
}


void dbf_table::clear_record_buffer(void)
{
  record[0] = ' ';
  const internal_field_descriptor *f;
  for (f = first; f != NULL; f = f->next)
  {
    switch (f->type)
    {
      case 'C':
      case 'D':
      case 'N':
        memset(record + f->offset, ' ', f->width);
        break;
      case 'L':
        record[f->offset] = 'F';
        break;
    }
  }
}


bool dbf_table::close(void)
{
  if (is_open)
  {
    finish_header_if_necessary();
    // supply terminating Ctrl-Z and patch record count in header
    if ((mode == DBF_WRITE || mode == DBF_APPEND) && !write_error)
    {
      write_error = fputc(26, fp) == EOF;
      if (!write_error)
      {
        fseek(fp, 4, SEEK_SET);
        fwrite(&nrecs, sizeof(nrecs), 1, fp);
      }
    }
    if (fclose(fp) != 0)
      write_error = true;
    fp = NULL;
    while (first != NULL)
    {
      internal_field_descriptor *next = first->next;
      delete first;
      first = next;
    }
    if (record != NULL)
    {
      delete record;
      record = NULL;
    }
    return !write_error;
  }
  return true;
}


void dbf_table::copy_fields(const dbf_table &t)
{
  int n = 0;
  const dbf_field_descriptor *p;
  while ((p = t.field_info(n++)) != NULL)
    add_field(p->type, p->name, p->width, p->precision);
}


bool dbf_table::delimited(char *buffer, int buflen) const
{
  if (!is_open || (mode != DBF_READ && mode != DBF_UPDATE) || recno < 1L
    || recno > nrecs)
  {
    return false;
  }
  const internal_field_descriptor *f;
  // check buffer capacity
  for (f = first; f != NULL; f = f->next)
  {
    buflen -= f->width;
    if (f->type == 'C') {buflen -= 2;} // quotation marks
    if (f->next != NULL) {buflen -= 2;}  // comma and space
  }
  if (buflen < 1)
    return false;
  char *buf = buffer;
  for (f = first; f != NULL; f = f->next)
  {
    char tempbuf[256];
    bool blank = true;
    get(tempbuf, sizeof(tempbuf), f);
    // change quotation marks and control characters to spaces
    // check for a blank field
    for (char *t = tempbuf; *t != 0; t++)
    {
      if (*t == '"' || *t < ' ') *t = ' ';
      if (*t != ' ') blank = false;
    }
    if (f->type == 'C')
    {
      *buf++ = blank ? ' ' : '"';
    }
    for (char *t = tempbuf; *t != 0; t++) {*buf++ = *t;}
    if (f->type == 'C')
    {
      if (blank)
      {
        *buf++ = ' ';
      }
      else // put trailing quotation mark after last non-blank
      {
        char *t = buf;
        while (t[-1] == ' ') t--;
        if (t < buf)
        {
          *t = '"';
          *buf++ = ' ';
        }
        else
          *buf++ = '"';
      }
    }
    if (f->next != NULL) {*buf++ = ','; *buf++ = ' ';}
  }
  *buf = 0;
  // remove trailing spaces and commas
  while (buf > buffer && (buf[-1] == ' ' || buf[-1] == ',')) *--buf = 0;
  return true;
}


int dbf_table::edit(char *buffer, __int64 n, int precision)
{
  char buf[22];
  bool negative = false;
  unsigned __int64 un;
  if (n < 0)
  {
    un = -n;
    negative = true;
  }
  else
    un = n;
  int number_of_digits = 0;
  char *t = buf + sizeof(buf);
  *--t = 0;
  do
  {
    *--t = un % 10 + '0';
    un /= 10;
    if (++number_of_digits == precision)
      *--t = '.';
  } while (un != 0 || number_of_digits < precision + 1);
  if (negative)
    *--t = '-';
  int width = buf + sizeof(buf) - t;
  strcpy(buffer, t);
  return width;
}


const dbf_field_descriptor *dbf_table::field_info(int n) const
{
  const internal_field_descriptor *f = first;
  while (n > 0 && f != NULL)
  {
    f = f->next;
    n--;
  }
  return f;
}

const dbf_field_descriptor *dbf_table::field_info(const char *name) const
{
  const internal_field_descriptor *f = first;
  while (f != NULL && stricmp(name, f->name) != 0)
    f = f->next;
  return f;
}


/*----------------------------------------------------------------------------
This function is called for a DBF_WRITE table after all fields have been
added and before the first record is written or the table is closed. It
patches the header size and record size fields in the file header and
appends the carriage return to the end of the header.
----------------------------------------------------------------------------*/

void dbf_table::finish_header_if_necessary(void)
{
  if (nrecs == 0 && mode == DBF_WRITE && !write_error)
  {
    fseek(fp, 8, SEEK_SET);
    fwrite(&header_size, 2, 1, fp);
    fwrite(&recsize, 2, 1, fp);
    fseek(fp, header_size - 1, SEEK_SET);
    write_error = fputc(0x0D, fp) == EOF;
  }
}

bool dbf_table::isblank(const dbf_field_descriptor *f) const
{
  if (f != NULL)
  {

    int i;
    for (i = 0; i < f->width; i++)
      if (record[f->offset + i] != ' ') return false;
  }
  return true;
}


bool dbf_table::get(char *buffer, int bufsize, const dbf_field_descriptor *f)
  const
{
  if (f != NULL)
  {
    int i;
    for (i = 0; i < f->width && i + 1 < bufsize; i++)
      buffer[i] = record[f->offset + i];
    buffer[i] = 0;
    return true;
  }
  return false;
}

bool dbf_table::get(struct date &value, const dbf_field_descriptor *f,
  struct date default_date) const
{
  if (f != NULL && f->type == 'D')
  {
    int offset = f->offset;
    if (record[offset] == ' ')
      value = default_date;
    else
    {
      value.da_year = record[offset] * 1000 + record[offset+1] * 100 +
        record[offset+2] * 10 + record[offset+3] - (1111 * '0');
      value.da_mon = record[offset+4] * 10 + record[offset+5] - (11 * '0');
      value.da_day = record[offset+6] * 10 + record[offset+7] - (11 * '0');
    }
    return true;
  }
  return false;
}

bool dbf_table::get(bool &value, const dbf_field_descriptor *f) const
{
  if (f != NULL && f->type == 'L')
  {
    value = record[f->offset] == 'T';
    return true;
  }
  return false;
}

bool dbf_table::get(__int64 &value, const dbf_field_descriptor *f) const
{
  if (f != NULL && f->type == 'N')
  {
    char *t = record + f->offset;
    char *end = t + f->width;
    bool negative = false;
    __int64 x = 0;
    while (t < end && *t == ' ')
      t++;
    if (t < end && *t == '-')
    {
      negative = true;
      t++;
    }
    while (t < end)
    {
      if (*t != '.')
        x = 10 * x + (*t - '0');
      t++;
    }
    value = negative ? -x : x;
    return true;
  }
  return false;
}

bool dbf_table::get(double &value, const dbf_field_descriptor *f) const
{
  __int64 n;
  if (get(n, f))
  {
    value = (double) n / pow10(f->precision);
    return true;
  }
  return false;
}

bool dbf_table::get(int &value, const dbf_field_descriptor *f) const
{
  __int64 n;
  if (get(n, f))
  {
    for (int p = f->precision; p > 0; p--)
      n /= 10;
    value = (int) n;
    return true;
  }
  return false;
}

bool dbf_table::isdeleted(void) const
{
  return record != NULL && record[0] == '*';
}


void dbf_table::makedeleted(bool deleted) const
{
  if (record != NULL)
    record[0] = deleted ? '*' : ' ';
}


using namespace std;

bool dbf_table::open(const char *specs, int pmode)
{
  close();
  fp = fopen(specs, pmode == DBF_READ ? "rb" : pmode == DBF_WRITE ? "wb" :
    "r+b");
  if (fp == NULL)
    return false;
  // write header
  if (pmode == DBF_WRITE)
  {
    nrecs = 0;
    header_size = sizeof(file_header) + 1;
    recsize = 1;
    record = new char[1];
    clear_record_buffer();
    file_header header;
    memset(&header, 0, sizeof(header));
    header.id = 3;
    header.header_size = header_size;
    header.record_size = recsize;
    struct date d;
    getdate(&d);
    header.month = d.da_mon;
    header.day = d.da_day;
    header.year = d.da_year % 100;
    if (!fwrite(&header, sizeof(header), 1, fp))
    {
      fclose(fp);
      fp = NULL;
      return false;
    }
  }
  else // read header and glean information from it
  {
    {
      file_header header;
      if (fread(&header, sizeof(header), 1, fp) != 1 || header.id != 3)
      {
        close();
        return false;
      }
      nrecs = header.number_of_records;
      header_size = header.header_size;
      recsize = header.record_size;
      record = new char[recsize];
      hdate.da_year = header.year + 1900;
      if (hdate.da_year < 1980)
        hdate.da_year += 100;
      hdate.da_mon = header.month;
      hdate.da_day = header.day;
      struct ftime ft;
      getftime(fileno(fp), &ft);
      ffdate.da_year = ft.ft_year + 1980;
      ffdate.da_mon = ft.ft_month;
      ffdate.da_day = ft.ft_day;
      fftime.ti_hour = ft.ft_hour;
      fftime.ti_min = ft.ft_min;
      fftime.ti_sec = ft.ft_tsec * 2;
    }
    // glean information about each field
    {
      internal_field_descriptor *last;
      file_header_field_descriptor field;
      int offset = 1;
      while (ftell(fp) + (int) sizeof(file_header_field_descriptor) <
        header_size)
      {
        if (fread(&field, sizeof(field), 1, fp) != 1)
        {
          close();
          return false;
        }
        // consistency check on field parameters
        switch (field.type)
        {
          case 'C':
            if (field.width < 1 || field.width > 254)
            {
              close();
              return false;
            }
            field.precision = 0;
            break;
          case 'N':
            if (field.width < 1 || field.width > 19 ||
              field.precision != 0 && field.precision + 2 > field.width)
            {
              close();
              return false;
            }
            break;
          case 'D':
            field.width = 8;
            field.precision = 0;
            break;
          case 'L':
            field.width = 1;
            field.precision = 0;
            break;
          default:
            close();
            return false;
        }
        internal_field_descriptor *f = new internal_field_descriptor;
        f->next = NULL;
        if (first == NULL)
          first = f;
        else
          last->next = f;
        last = f;
        memcpy(f->name, field.name, sizeof(field.name));
        f->name[11] = 0;
        f->type = field.type;
        f->width = field.width;
        f->precision = field.precision;
        f->offset = offset;
        offset += f->width;
      }
      if (offset > recsize)
      {
        close();
        return false;
      }
    }
    if (pmode == DBF_APPEND)
    {
      fseek(fp, header_size + recsize * nrecs, SEEK_SET);
      clear_record_buffer();
    }
  }
  xmode = pmode;
  write_error = false;
  return true;
}

// This function creates a backup copy of a file and returns a
// pointer to an internal buffer containing its specs. The backup
// copy has the same name and resides in the same directory as
// the original file, but it has the extension .BAK. If the
// operation fails, it returns NULL.

static char *backup(const char *filespecs)
{
  static char backspecs[MAXPATH];
  char drive[MAXDRIVE];
  char dir[MAXDIR];
  char name[MAXFILE];
  char ext[MAXEXT];
  fnsplit(filespecs, drive, dir, name, ext);
  if (strcmpi(ext, ".BAK") == 0) return NULL;
  fnmerge(backspecs, drive, dir, name, ".BAK");
  FILE *f = fopen(filespecs, "rb");
  if (f == NULL) return NULL;
  FILE *b = fopen(backspecs, "wb");
  if (b == NULL) return NULL;
  int c;
  while ((c = getc(f)) != EOF) putc(c, b);
  fclose(f);
  fclose(b);
  return backspecs;
}

long dbf_table::pack(const char *filespecs)
{
  dbf_table unpacked, packed;
  char *backspecs = backup(filespecs);
  if (backspecs == NULL ||
    !unpacked.open(backspecs, DBF_READ) ||
    !packed.open(filespecs, DBF_WRITE))
  {
    return -1L;
  }
  packed.copy_fields(unpacked);
  long i;
  long packed_records = 0L;
  for (i = 1; i <= unpacked.number_of_records; i++)
  {
    unpacked.record_number = i;
    if (!unpacked.deleted)
    {
      packed << unpacked;
      packed.append();
      packed_records++;
    }
  }
  packed.close();
  unpacked.close();
  return packed_records;
}

bool dbf_table::insert_blank(const char *filespecs, long next_record)
{
  dbf_table oldtable, newtable;
  char *backspecs = backup(filespecs);
  if (backspecs == NULL ||
    !oldtable.open(backspecs, DBF_READ) ||
    !newtable.open(filespecs, DBF_WRITE))
  {
    return false;
  }
  newtable.copy_fields(oldtable);
  long i;
  for (i = 1; i <= oldtable.number_of_records; i++)
  {
    if (i == next_record)
      newtable.append();
    oldtable.record_number = i;
    newtable << oldtable;
    newtable.append();
  }
  if (next_record == oldtable.number_of_records+1)
    newtable.append();
  newtable.close();
  oldtable.close();
  return true;
}



bool dbf_table::put(const char *buffer, const dbf_field_descriptor *f)
{
  if (f != NULL)
  {
    int i;
    for (i = 0; i < f->width; i++)
      record[f->offset + i] = *buffer != 0 ? *buffer++ : ' ';
    return true;
  }
  return false;
}

bool dbf_table::put(struct date value, const dbf_field_descriptor *f)
{
  if (f != NULL && f->type == 'D')
  {
    unsigned year = value.da_year;
    record[f->offset + 3] = year % 10 + '0';
    year /= 10;
    record[f->offset + 2] = year % 10 + '0';
    year /= 10;
    record[f->offset + 1] = year % 10 + '0';
    record[f->offset + 0] = year / 10 + '0';
    record[f->offset + 4] = value.da_mon / 10 + '0';
    record[f->offset + 5] = value.da_mon % 10 + '0';
    record[f->offset + 6] = value.da_day / 10 + '0';
    record[f->offset + 7] = value.da_day % 10 + '0';
    return true;
  }
  return false;
}

bool dbf_table::put(bool value, const dbf_field_descriptor *f)
{
  if (f != NULL && f->type == 'L')
  {
    record[f->offset] = value ? 'T' : 'F';
    return true;
  }
  return false;
}

bool dbf_table::put(__int64 value, const dbf_field_descriptor *f)
{
  if (f != NULL && f->type == 'N')
  {
    bool negative = false;
    if (value < 0.0)
    {
      value = -value;
      negative = true;
    }
    int number_of_digits = 0;
    char *beginrec = record + f->offset;
    char *t = beginrec + f->width;
    do
    {
      if (t == beginrec)
        return false;
      *--t = value % 10 + '0';
      value /= 10;
      if (++number_of_digits == f->precision)
        *--t = '.';
    } while (value != 0 || number_of_digits < f->precision + 1);
    if (negative)
    {
      if (t == beginrec)
        return false;
      *--t = '-';
    }
    while (t > beginrec)
      *--t = ' ';
    return true;
  }
  return false;
}

bool dbf_table::put(double value, const dbf_field_descriptor *f)
{
  __int64 n = value * pow10(f->precision);
  return put(n, f);
}

bool dbf_table::put(int value, const dbf_field_descriptor *f)
{
  __int64 n = value;
  if (f == NULL) return false;
  for (int p = f->precision; p > 0; p--)
    n *= 10;
  return put(n, f);
}

bool dbf_table::scan(__int64 &n, const char *buffer, int precision)
{
  const char *t = buffer;
  bool negative = false;
  bool result = false;
  __int64 x = 0;
  while (*t == ' ')
    t++;
  if (*t == '-')
  {
    negative = true;
    t++;
  }
  while (*t != 0 && *t != '.')
  {
    if (isdigit(*t) && *t != ',')
    {
      x = 10 * x + (*t - '0');
      result = true;
    }
    t++;
  }
  if (*t == '.')
  {
    t++;
    while (*t != 0 && precision > 0)
    {
      if (isdigit(*t) && *t != ',')
      {
        x = 10 * x + (*t - '0');
        precision--;
        result = true;
      }
      t++;
    }
  }
  while (precision > 0)
  {
    x = 10 * x;
    precision--;
  }
  n = negative ? -x : x;
  return result;
}

/*----------------------------------------------------------------------------
This function, which is called whenever an assignment is made to the
property record_number, changes the recno member and also reads the
appropriate record into the record buffer.
----------------------------------------------------------------------------*/

void dbf_table::set_recno(long n)
{
  if (is_open && (mode == DBF_READ || mode == DBF_UPDATE))
  {
    recno = n;
    if (1 <= n && n <= nrecs)
    {
      fseek(fp, header_size + (n-1) * recsize, SEEK_SET);
      fread(record, recsize, 1, fp);
    }
  }
}

bool dbf_table::sort(const char *output_filespecs, const char *keys)
{
  const int MAX_KEYS = 10;
  struct key_struct
  {
    char name[12];
    bool descending;
    bool case_insensitive;
  } key[MAX_KEYS];
  if (!is_open || mode != DBF_READ) return false;
  int number_of_keys = 0;
  {
    const char *t = keys;
    while (number_of_keys < MAX_KEYS)
    {
      while (*t == ' ' || *t == '\t') t++;
      if (*t == 0) break;
      struct key_struct &f = key[number_of_keys++];
      int n = 0;
      while (*t != '/' && *t != ' ' && *t != '\t' && *t != 0)
      {
        if (n < 11) f.name[n++] = toupper(*t);
        t++;
      }
      f.name[n] = 0;
      if (field_info(f.name) == NULL) return false;
      f.descending = false;
      f.case_insensitive = false;
      if (*t == '/')
      {
        t++;
        if (toupper(t[0]) == 'I' && toupper(t[1]) == 'D' ||
          toupper(t[0]) == 'D' && toupper(t[1]) == 'I')
        {
          f.descending = true;
          f.case_insensitive = true;
          t += 2;
        }
        else if (toupper(*t) == 'I')
        {
          f.case_insensitive = true;
          t++;
        }
        else if (toupper(*t) == 'D')
        {
          f.descending = true;
          t++;
        }
      }
    }
  }

//   {
//     int i;
//     for (i = 0; i < number_of_keys; i++)
//     {
//       printf("%s %c %c\n",
//         key[i].name, key[i].case_insensitive ? 'I' : ' ',
//         key[i].descending ? 'D' : ' ');
//     }
//   }

  dbf_table output;
  if (!output.open(output_filespecs, DBF_WRITE)) return false;
  output.copy_fields(*this);

  // handle special cases with no records or one record
  if (number_of_records <= 1L)
  {
    if (number_of_records == 1L)
    {
      record_number = 1;
      output << *this;
      output.append();
    }
    output.close();
    return true;
  }

  struct temp_table
  {
    dbf_table table;
    char filespecs[L_tmpnam+1];
  } tape[4];

  tmpnam(tape[0].filespecs);
  tmpnam(tape[1].filespecs);
  tmpnam(tape[2].filespecs);
  tmpnam(tape[3].filespecs);

  tape[0].table.open(tape[0].filespecs, DBF_WRITE);
  tape[0].table.copy_fields(*this);
  tape[1].table.open(tape[1].filespecs, DBF_WRITE);
  tape[1].table.copy_fields(*this);

  // distribute records equally to tape[0] and tape[1]
  {
    long i;
    int j;
    for (i = 1, j = 0; i <= number_of_records; i++, j = 1-j)
    {
      record_number = i;
      tape[j].table << *this;
      tape[j].table.append();
    }
  }
  tape[0].table.close();
  tape[1].table.close();

  long blocksize;
  int base;
  for (blocksize = 1L, base = 0; ; blocksize *= 2, base = 2-base)
  {
    // input tapes
    dbf_table *input[2];
    input[0] = &tape[base].table;
    input[0]->open(tape[base].filespecs, DBF_READ);
    input[1] = &tape[base+1].table;
    input[1]->open(tape[base+1].filespecs, DBF_READ);

    // find key descriptors for input tapes to speed comparisons

    const dbf_field_descriptor *tapekey[2][MAX_KEYS];
    {
      int i;
      for (i = 0; i < number_of_keys; i++)
      {
        tapekey[0][i] = input[0]->field_info(key[i].name);
        tapekey[1][i] = input[1]->field_info(key[i].name);
      }
    }

    // output tapes
    dbf_table *tape_output[2];
    if (tape[base].table.number_of_records +
      tape[base+1].table.number_of_records <= 2*blocksize)
    {
      tape_output[0] = &output;
    }
    else
    {
      tape_output[0] = &tape[2-base].table;
      tape_output[0]->open(tape[2-base].filespecs, DBF_WRITE);
      tape_output[0]->copy_fields(*this);
      tape_output[1] = &tape[3-base].table;
      tape_output[1]->open(tape[3-base].filespecs, DBF_WRITE);
      tape_output[1]->copy_fields(*this);
    }
    long rec[2];
    rec[0] = 1L;
    rec[1] = 1L;
    int output_index = 0;
    while (true)
    {
      long reclimit[2];
      reclimit[0] = rec[0] + blocksize;
      if (reclimit[0] > input[0]->number_of_records)
        reclimit[0] = input[0]->number_of_records + 1;
      reclimit[1] = rec[1] + blocksize;
      if (reclimit[1] > input[1]->number_of_records)
        reclimit[1] = input[1]->number_of_records + 1;
      while (rec[0] < reclimit[0] || rec[1] < reclimit[1])
      {
        if (rec[0] == reclimit[0])
        {
          input[1]->record_number = rec[1];
          *tape_output[output_index] << *input[1];
          tape_output[output_index]->append();
          rec[1] += 1;
        }
        else if (rec[1] == reclimit[1])
        {
          input[0]->record_number = rec[0];
          *tape_output[output_index] << *input[0];
          tape_output[output_index]->append();
          rec[0] += 1;
        }
        else
        {
          input[0]->record_number = rec[0];
          input[1]->record_number = rec[1];
          int comp = 0;
          for (int i = 0; i < number_of_keys && comp == 0; i++)
          {
            if (tapekey[0][i]->type == 'N')
            {
              __int64 n0, n1;
              input[0]->get(n0,  tapekey[0][i]);
              input[1]->get(n1,  tapekey[1][i]);
              if (n0 > n1)
                comp = 1;
              else if (n0 < n1)
                comp = -1;
            }
            else
            {
              char buffer0[256], buffer1[256];
              input[0]->get(buffer0, 256, tapekey[0][i]);
              input[1]->get(buffer1, 256, tapekey[1][i]);
              if (key[0].case_insensitive)
                comp = strcmpi(buffer0, buffer1);
              else
                comp = strcmp(buffer0, buffer1);
            }
            if (key[0].descending) comp = -comp;
          }
          if (comp > 0)
          {
            input[1]->record_number = rec[1];
            *tape_output[output_index] << *input[1];
            tape_output[output_index]->append();
            rec[1] += 1;
          }
          else
          {
            input[0]->record_number = rec[0];
            *tape_output[output_index] << *input[0];
            tape_output[output_index]->append();
            rec[0] += 1;
          }
        }
      }
      output_index = 1- output_index;
      if (rec[0] > input[0]->number_of_records &&
        rec[1] > input[1]->number_of_records)
      {
        break;
      }
    }
    tape_output[0]->close();
    input[0]->close();
    input[1]->close();
    if (tape_output[0] == &output) break;
    tape_output[1]->close();
  }
  output.close();
  remove(tape[0].filespecs);
  remove(tape[1].filespecs);
  remove(tape[2].filespecs);
  remove(tape[3].filespecs);
  return true;
}

void dbf_table::update(void)
{
  if (isopen() && mode == DBF_UPDATE)
  {
    fseek(fp, header_size + (recno-1) * recsize, SEEK_SET);
    fwrite(record, recsize, 1, fp) != 1;
    clear_record_buffer();
  }
}

