/* m4file.c   (c)Copyright Sequiter Software Inc., 1990-1994.  All rights reserved.  */

#include "d4all.h"
#ifndef S4UNIX
#ifdef __TURBOC__
#pragma hdrstop
#endif
#endif

#ifndef S4MEMO_OFF

#ifndef S4SINGLE
/* ndx/clipper versions have no free chain--do not lock memo file */
#ifndef N4OTHER
/* the lock is forced since a memo file lock only lasts if the .dbf file is locked */
int S4FUNCTION memo4file_lock( MEMO4FILE *f4memo )
{
  int rc, old_attempts ;

  if ( f4memo->file_lock == 1 )
    return 0 ;

  if ( f4memo->file.hand == -1 )
    return -1 ;

  old_attempts = f4memo->file.code_base->lock_attempts ;
  f4memo->file.code_base->lock_attempts = -1 ;

#ifdef S4MDX
  rc = file4lock( &f4memo->file, L4LOCK_POS - 1L, 2L ) ;
#endif

#ifdef S4FOX
  rc = file4lock( &f4memo->file, L4LOCK_POS, 1L ) ;
#endif

  f4memo->file.code_base->lock_attempts = old_attempts ;
  if ( rc == 0 )
    f4memo->file_lock = 1 ;
#ifndef S4OPTIMIZE_OFF
  file4refresh( &f4memo->file ) ;   /* make sure all up to date */
#endif
  return rc ;
}

int S4FUNCTION memo4file_unlock( MEMO4FILE *f4memo )
{
  int rc ;

  if ( f4memo->file_lock == 0 )
    return 0 ;
#ifdef S4MDX
  rc = file4unlock( &f4memo->file, L4LOCK_POS - 1L, 2L ) ;
#endif
#ifdef S4FOX
  rc = file4unlock( &f4memo->file, L4LOCK_POS, 1L ) ;
#endif
  if ( rc == 0 )
    f4memo->file_lock = 0 ;
  return rc ;
}
#endif
#endif

int memo4file_open( MEMO4FILE *f4memo, DATA4 *d4, char *name )
{
  MEMO4HEADER  header ;
  int rc ;

  f4memo->data = d4 ;

  if ( file4open( &f4memo->file, d4->code_base, name, 1 ) )
    return -1 ;

#ifndef S4OPTIMIZE_OFF
  file4optimize( &f4memo->file, d4->code_base->optimize, OPT4OTHER ) ;
#endif

  if ( (rc = file4read_all(&f4memo->file, 0L, &header, sizeof(header))) < 0 )
    return -1 ;

#ifdef S4BYTE_SWAP
  header.next_block = x4reverse_long( (void *)&header.next_block ) ;
#ifndef S4MNDX
#ifndef S4MFOX
  header.x102 = 0x201 ;
#endif
  header.block_size = x4reverse_short( (void *)&header.block_size ) ;
#endif
#endif

#ifdef S4MFOX
  f4memo->block_size = x4reverse_short( (void *)&header.block_size ) ;
#else
#ifdef S4MNDX
  f4memo->block_size = MEMO4SIZE ;
#else
  f4memo->block_size = header.block_size ;
#endif
#endif

  return rc ;
}

#ifdef S4MFOX
/* offset is # bytes from start of memo that reading should begin, read_max is
   the maximum possible that can be read (limited to an unsigned int, so is
   16-bit/32-bit compiler dependent.
   */
int memo4file_read_part( MEMO4FILE *f4memo, long memo_id, char **ptr_ptr, unsigned *len_ptr, unsigned long offset, unsigned read_max )
{
  unsigned long pos, avail ;
  MEMO4BLOCK memo_block ;

  if ( memo_id <= 0L )
  {
    *len_ptr = 0 ;
    return 0 ;
  }

  pos = memo_id * f4memo->block_size ;

  if ( file4read_all( &f4memo->file, pos, &memo_block, sizeof( MEMO4BLOCK ) ) < 0)
    return -1 ;

#ifdef S4BYTE_SWAP
#ifdef S4MFOX
  memo_block.type = x4reverse_long( (void *)&memo_block.type ) ;
#else
  memo_block.start_pos = x4reverse_short( (void *)&memo_block.start_pos ) ;
#endif
  memo_block.num_chars = x4reverse_long( (void *)&memo_block.num_chars ) ;
#endif

  memo_block.num_chars = x4reverse_long( (void *)&memo_block.num_chars ) ;

  avail = memo_block.num_chars - offset ;
  if ( avail > (unsigned long)read_max )
    avail = read_max ;

  if ( avail > (unsigned long)*len_ptr )
  {
    if ( *len_ptr > 0 )
      u4free( *ptr_ptr ) ;
    *ptr_ptr = (char *)u4alloc_er( f4memo->file.code_base, memo_block.num_chars + 1 ) ;
    if ( *ptr_ptr == 0 )
      return e4memory ;
  }

  *len_ptr = (unsigned)avail ;

  return (int)file4read_all( &f4memo->file, offset + pos + (long)(2*sizeof(S4LONG)), *ptr_ptr, *len_ptr ) ;
}
#endif

int memo4file_read( MEMO4FILE *f4memo, long memo_id, char **ptr_ptr, unsigned *len_ptr )
{
#ifdef S4MFOX
  return memo4file_read_part( f4memo, memo_id, ptr_ptr, len_ptr, 0L, UINT_MAX ) ;
#else
  long  pos ;
#ifdef S4MNDX
  long amt_read, read_max, len_read ;
  char *t_ptr ;
#else
  MEMO4BLOCK  memo_block ;
  unsigned final_len ;
#endif

  if ( memo_id <= 0L )
  {
    *len_ptr = 0 ;
    return 0 ;
  }

  pos = memo_id * f4memo->block_size ;

#ifdef S4MNDX
  amt_read = 0 ;
  read_max = *len_ptr ;

  for(;;)
  {
    if ( MEMO4SIZE > read_max )  /* must increase the memo buffer size */
    {
      t_ptr = (char *)u4alloc_er( f4memo->file.code_base, amt_read + MEMO4SIZE + 1 ) ;
      if ( t_ptr == 0 )
        return e4memory ;
      if ( *len_ptr > 0 )
      {
        memcpy( t_ptr, *ptr_ptr, *len_ptr ) ;
        u4free( *ptr_ptr ) ;
      }
      *ptr_ptr = t_ptr ;
      *len_ptr = (unsigned)( amt_read + MEMO4SIZE + 1 ) ;
      read_max = MEMO4SIZE ;
    }

    len_read = file4read( &f4memo->file, pos + amt_read, *ptr_ptr + amt_read, read_max ) ;
    if ( len_read <= 0 )
      return -1 ;

    for ( ; len_read > 0 ; amt_read++, len_read-- )
      if ( (*ptr_ptr)[amt_read] == 0x1A ) /* if done */
      {
        if ( amt_read > 0 )
        {
          t_ptr = (char *)u4alloc_free( f4memo->file.code_base, amt_read + 1 ) ;
          if ( t_ptr == 0 )
          {
            /* this will result in a correct return, but extra */
            /* space is still allocated at end of *ptr_ptr */
            *ptr_ptr[amt_read] = 0 ;
            *len_ptr = (unsigned)amt_read ;
            return 0 ;
          }
          memcpy( t_ptr, *ptr_ptr, (int)amt_read ) ;
          t_ptr[amt_read] = 0 ;
        }
        else
          t_ptr = 0 ;

        u4free( *ptr_ptr ) ;
        *ptr_ptr = t_ptr ;
        *len_ptr = (unsigned)amt_read ;
        return 0 ;
      }
    read_max = 0 ;
  }
#else
  if ( file4read_all( &f4memo->file, pos, &memo_block, sizeof( MEMO4BLOCK ) ) < 0)
    return -1 ;

#ifdef S4BYTE_SWAP
  memo_block.start_pos = x4reverse_short( (void *)&memo_block.start_pos ) ;
  memo_block.num_chars = x4reverse_long( (void *)&memo_block.num_chars ) ;
#endif

  if ( memo_block.minus_one != -1 )  /* must be an invalid entry, so return an empty entry */
  {
    *len_ptr = 0 ;
    return 0 ;
  }
  else
  {
    if ( memo_block.num_chars >= USHRT_MAX )
      return e4( f4memo->file.code_base, e4info, E4_MEMO4FILE_RD ) ;

    final_len = (unsigned)memo_block.num_chars - 2 * ( sizeof(short) ) - ( sizeof(S4LONG) ) ;
    if ( final_len > *len_ptr )
    {
      if ( *len_ptr > 0 )
        u4free( *ptr_ptr ) ;
      *ptr_ptr = (char *)u4alloc_er( f4memo->file.code_base, final_len + 1 ) ;
      if ( *ptr_ptr == 0 )
        return e4memory ;
    }
    *len_ptr = final_len ;

    return file4read_all( &f4memo->file, pos+ memo_block.start_pos, *ptr_ptr, final_len ) ;
  }
#endif
#endif
}

#ifdef S4MFOX
/* Writes partial data to a memo record.
   Usage rules:
   Must call this function with an offset == 0 to write 1st part of block
   before any additional writing.  In addition, the memo_len must be
   accurately set during the first call in order to reserve the correct
   amount of memo space ahead of time.  Later calls just fill in data to
   the reserved disk space.
   len_write is the amount of data to write, offset is the number of
   bytes from the beginning of the memo in which to write the data
   Secondary calls to this function assume that everything has been
   previously set up, and merely performs a file write to the reserved
   space.  The space is not checked to see whether or not it actually
   is in the bounds specified, so use with care.
   */
int memo4file_write_part( MEMO4FILE *f4memo, long *memo_id_ptr, char *ptr, long memo_len, long offset, unsigned len_write )
{
  int str_num_blocks ;
  long pos ;
#ifndef S4SINGLE
#ifndef N4OTHER
  int rc, lock_cond ;
#endif
#endif
  MEMO4BLOCK old_memo_block ;
  MEMO4HEADER mh ;
  unsigned len_read ;
  long block_no ;
  unsigned n_entry_blks = 0 ;


#ifdef S4DEBUG
  if ( memo_id_ptr == 0 )
    e4severe( e4parm, E4_MEMO4FILE_WR ) ;
#endif

  if ( offset == 0 )   /* must do the set-up work */
  {
    if ( memo_len == 0 )
    {
      *memo_id_ptr = 0L ;
      return 0 ;
    }

#ifdef S4DEBUG
    if ( f4memo->block_size <= 1 )
      e4severe( e4info, E4_INFO_IMS ) ;
#endif

    str_num_blocks = (int) ((memo_len + sizeof(MEMO4BLOCK) + f4memo->block_size-1) / f4memo->block_size) ;
    if ( *memo_id_ptr <= 0L )
      block_no = 0L ;
    else
    {
      block_no = *memo_id_ptr ;
      pos = block_no * f4memo->block_size ;

      file4read_all( &f4memo->file, pos, (char *)&old_memo_block, sizeof(old_memo_block) ) ;

#ifdef S4BYTE_SWAP
#ifdef S4MFOX
      old_memo_block.type = x4reverse_long( (void *)&old_memo_block.type ) ;
#else
      old_memo_block.start_pos = x4reverse_short( (void *)&old_memo_block.start_pos ) ;
#endif
      old_memo_block.num_chars = x4reverse_long( (void *)&old_memo_block.num_chars ) ;
#endif

      old_memo_block.num_chars = x4reverse_long( (void *)&old_memo_block.num_chars ) ;
      n_entry_blks = (unsigned) ((old_memo_block.num_chars + f4memo->block_size-1)/ f4memo->block_size ) ;
    }
    if ( n_entry_blks >= ((unsigned)str_num_blocks) && block_no )  /* write to existing position */
      *memo_id_ptr = block_no ;
    else  /* read in header record */
    {
#ifndef S4SINGLE
#ifndef N4OTHER
      lock_cond = f4memo->data->memo_file.file_lock ;
      rc = memo4file_lock( &f4memo->data->memo_file ) ;
      if ( rc )
        return rc ;
#endif
#endif

      len_read = file4read( &f4memo->file, 0, &mh, sizeof( mh ) ) ;
#ifdef S4BYTE_SWAP
      mh.next_block = x4reverse_long( (void *)&mh.next_block ) ;
      mh.block_size = x4reverse_short( (void *)&mh.block_size ) ;
#endif

      if ( f4memo->data->code_base->error_code < 0 )
      {
#ifndef S4SINGLE
#ifndef N4OTHER
        if ( !lock_cond )
          memo4file_unlock( &f4memo->data->memo_file ) ;
#endif
#endif
        return -1 ;
      }

      if ( len_read != sizeof( mh ) )
      {
#ifndef S4SINGLE
#ifndef N4OTHER
        if ( !lock_cond )
          memo4file_unlock( &f4memo->data->memo_file ) ;
#endif
#endif
        return file4read_error( &f4memo->file ) ;
      }

      *memo_id_ptr = x4reverse_long( (void *)&mh.next_block ) ;
      mh.next_block = *memo_id_ptr + str_num_blocks ;
      mh.next_block = x4reverse_long( (void *)&mh.next_block ) ;

#ifdef S4BYTE_SWAP
      mh.next_block = x4reverse_long( (void *)&mh.next_block ) ;
      mh.block_size = x4reverse_short( (void *)&mh.block_size ) ;
#endif

      file4write( &f4memo->file, 0, &mh, sizeof( mh ) ) ;

#ifndef S4SINGLE
#ifndef N4OTHER
      if ( !lock_cond )
        memo4file_unlock( &f4memo->data->memo_file ) ;
#endif
#endif
    }
    if ( memo4file_dump( f4memo, *memo_id_ptr, ptr, len_write ) < 0 )
      return -1 ;
  }
  else
    return file4write( &f4memo->file, *memo_id_ptr * f4memo->block_size + offset, ptr, len_write ) ;

  return 0 ;
}
#endif

int memo4file_write( MEMO4FILE *f4memo, long *memo_id_ptr, char *ptr, unsigned ptr_len )
{
#ifdef S4MFOX
  return memo4file_write_part( f4memo, memo_id_ptr, ptr, ptr_len, 0L, ptr_len ) ;
#else
  int str_num_blocks ;
  long pos ;
#ifndef S4MDX
#ifndef S4SINGLE
#ifndef N4OTHER
  int rc, lock_cond ;
#endif
#endif
#endif
#ifdef S4MNDX
  MEMO4HEADER mh ;
  long len_read ;
  char buf[MEMO4SIZE] ;
  int read_size, i ;
#else
  MEMO4BLOCK old_memo_block ;
  MEMO4CHAIN_ENTRY new_entry, cur, prev ;
  int  str_written ;
  long prev_prev_entry, prev_prev_num ;
  long file_len, extra_len ;
#endif

#ifdef S4DEBUG
  if ( memo_id_ptr == 0 )
    e4severe( e4parm, E4_MEMO4FILE_WR ) ;
#endif

#ifdef S4MNDX
  if ( ptr_len == 0 )
  {
    *memo_id_ptr = 0L ;
    return 0 ;
  }

  str_num_blocks = (ptr_len + f4memo->block_size-1) / f4memo->block_size ;

  if ( *memo_id_ptr <= 0L )
    *memo_id_ptr = 0L ;
  else    /* read in old record to see if new entry can fit */
  {
    read_size = 0 ;
    pos = *memo_id_ptr * f4memo->block_size ;

    do
    {
      read_size += MEMO4SIZE ;

      len_read = file4read( &f4memo->file, pos, buf, MEMO4SIZE ) ;
      if ( len_read <= 0 )
        return file4read_error( &f4memo->file ) ;

      for ( i=0 ; ((unsigned) i) < len_read ; i++ )
        if ( buf[i] == (char)0x1A )  break ;

#ifdef S4DEBUG
      if ( buf[i] != (char)0x1A && len_read != MEMO4SIZE )
        return e4( f4memo->file.code_base, e4info, E4_INFO_CMF ) ;
#endif

      pos += MEMO4SIZE ;
    } while ( i >= MEMO4SIZE && buf[i] != (char) 0x1A ) ;  /* Continue if Esc is not located */

    if ( ((unsigned)read_size) <= ptr_len )   /* there is not room */
      *memo_id_ptr = 0 ;
  }

  if ( *memo_id_ptr == 0 )   /* add entry at eof */
  {
#ifndef S4SINGLE
#ifndef N4OTHER
    lock_cond = f4memo->data->memo_file.file_lock ;
    rc = memo4file_lock( &f4memo->data->memo_file ) ;
    if ( rc )
      return rc ;
#endif
#endif

    len_read = file4read( &f4memo->file, 0, &mh, sizeof( mh ) ) ;
#ifdef S4BYTE_SWAP
    mh.next_block = x4reverse_long( (void *)&mh.next_block ) ;
#endif
    if ( f4memo->data->code_base->error_code < 0 )
    {
#ifndef S4SINGLE
#ifndef N4OTHER
      if ( !lock_cond )
        memo4file_unlock( &f4memo->data->memo_file ) ;
#endif
#endif
      return -1 ;
    }

    if ( len_read != sizeof( mh ) )
    {
#ifndef S4SINGLE
#ifndef N4OTHER
      if ( !lock_cond )
        memo4file_unlock( &f4memo->data->memo_file ) ;
#endif
#endif
      return file4read_error( &f4memo->file ) ;
    }

    *memo_id_ptr = mh.next_block ;
    mh.next_block = *memo_id_ptr + str_num_blocks ;
#ifdef S4BYTE_SWAP
    mh.next_block = x4reverse_long( (void *)&mh.next_block ) ;
#endif

    file4write( &f4memo->file, 0, &mh, sizeof( mh ) ) ;

#ifndef S4SINGLE
#ifndef N4OTHER
    if ( !lock_cond )
      memo4file_unlock( &f4memo->data->memo_file ) ;
#endif
#endif

#ifdef S4BYTE_SWAP
    mh.next_block = x4reverse_long( (void *)&mh.next_block ) ;
#endif
  }


  if ( memo4file_dump( f4memo, *memo_id_ptr, ptr, ptr_len ) < 0 )
    return -1 ;

  return 0 ;
#else
  /* S4MMDX */
  memset( (void *)&new_entry, 0, sizeof(new_entry) ) ;
  new_entry.block_no = *memo_id_ptr ;

  str_written = 0 ;
  if ( ptr_len == 0 )
  {
    str_written = 1 ;
    *memo_id_ptr = 0 ;
  }

  /* Initialize information about the old memo entry */
  if ( new_entry.block_no <= 0L )
  {
    if ( str_written )
    {
      *memo_id_ptr = 0L ;
      return 0 ;
    }
    new_entry.num = 0 ;
  }
  else
  {
    pos = new_entry.block_no * f4memo->block_size ;

    file4read_all( &f4memo->file, pos, (char *)&old_memo_block, sizeof(old_memo_block) ) ;
#ifdef S4BYTE_SWAP
    old_memo_block.start_pos = x4reverse_short( (void *)&old_memo_block.start_pos ) ;
    old_memo_block.num_chars = x4reverse_long( (void *)&old_memo_block.num_chars ) ;
#endif

    new_entry.num = ((unsigned)old_memo_block.num_chars + f4memo->block_size-1)/ f4memo->block_size ;
  }

  str_num_blocks = (ptr_len+2*(sizeof(short))+(sizeof(S4LONG))+ f4memo->block_size-1) / f4memo->block_size ;

  if ( new_entry.num >= str_num_blocks  &&  !str_written )
  {
    *memo_id_ptr = new_entry.block_no + new_entry.num - str_num_blocks ;
    if ( memo4file_dump( f4memo, *memo_id_ptr, ptr, ptr_len ) < 0 )
      return -1 ;

    str_written = 1 ;
    if ( new_entry.num == str_num_blocks )
      return 0 ;

    new_entry.num -= str_num_blocks ;
  }

  /* Initialize 'chain' */
  memset( (void *)&cur, 0, sizeof(cur) ) ;
  memset( (void *)&prev, 0, sizeof(prev) ) ;

  for(;;)
  {
    if ( f4memo->data->code_base->error_code < 0 )
      return -1 ;

    memo4file_chain_flush( f4memo, &prev ) ;
    prev_prev_entry = prev.block_no ;
    prev_prev_num = prev.num ;

    memcpy( (void *)&prev, (void *)&cur, sizeof(prev) ) ;

    if ( new_entry.block_no > 0  &&  prev.next > new_entry.block_no )
    {
      /* See if the new entry fits in here */
      memcpy( (void *)&cur, (void *)&new_entry, sizeof(cur) ) ;
      new_entry.block_no = 0 ;
      cur.next  = prev.next ;
      prev.next = cur.block_no ;
      cur.to_disk = prev.to_disk = 1 ;
    }
    else
      memo4file_chain_skip( f4memo, &cur ) ;

    /* See if the entries can be combined. */
    if ( prev.block_no + prev.num == cur.block_no && prev.num )
    {
      /* 'cur' becomes the combined groups. */
      prev.to_disk = 0 ;
      cur.to_disk  = 1 ;

      cur.block_no = prev.block_no ;
      if ( cur.num >= 0 )
        cur.num  += prev.num ;
      prev.block_no = prev_prev_entry ;
      prev.num = prev_prev_num ;
    }

    if ( str_written )
    {
      if ( new_entry.block_no == 0 )
      {
        memo4file_chain_flush( f4memo, &prev ) ;
        memo4file_chain_flush( f4memo, &cur ) ;
        return 0 ;
      }
    }
    else  /* 'str' is not yet written, try the current entry */
    {
      if ( cur.next == -1 )  /* End of file */
        cur.num = str_num_blocks ;

      if ( cur.num >= str_num_blocks )
      {
        cur.num -= str_num_blocks ;
        *memo_id_ptr = cur.block_no + cur.num ;
        memo4file_dump( f4memo, *memo_id_ptr, ptr, ptr_len ) ;
        if ( cur.next == -1 ) /* if end of file */
        {
          /* For dBASE IV compatibility */
          file_len  = file4len( &f4memo->file ) ;
          extra_len = f4memo->block_size -  file_len % f4memo->block_size ;
          if ( extra_len != f4memo->block_size )
            file4len_set( &f4memo->file, file_len+extra_len ) ;
        }

        str_written = 1 ;

        if ( cur.num == 0 )
        {
          if ( cur.next == -1 ) /* End of file */
            prev.next = cur.block_no + str_num_blocks ;
          else
            prev.next = cur.next ;
          prev.to_disk = 1 ;
          cur.to_disk = 0 ;
        }
        else
          cur.to_disk = 1 ;
      }
    }
  }
#endif
#endif
}
#endif