//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library 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
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public
//  License along with this program; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307, USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Tag number types and set class.
//  ------------------------------------------------------------------

#include <cstdlib>
#include <cstring>
#include <gmemdbg.h>
#include <gutltag.h>


//  ------------------------------------------------------------------
//  Constructor.

GTag::GTag() {

  granularity = 10;

  tag = NULL;
  allocated = tags = count = 0;
}


//  ------------------------------------------------------------------
//  Destructor.

GTag::~GTag() {

  Reset();
}


//  ------------------------------------------------------------------
//  Deallocate and reset internal data.

void GTag::Reset() {

  throw_xrelease(tag);
  allocated = tags = 0;
  // NOTE: Does and must NOT reset the count!
}


//  ------------------------------------------------------------------
//  Resize tag array.
//  Returns NULL if realloc failed.

uint32_t* GTag::Resize(uint __tags) {

  register uint newsize = 0;

  if(__tags >= allocated)
    newsize = __tags + granularity;
  else if(__tags < allocated) {
    if((allocated-__tags) > granularity)
      newsize = __tags + granularity;
  }

  if(newsize) {
    tag = (uint32_t*)throw_realloc(tag, newsize*sizeof(uint32_t));
    allocated = newsize;
  }

  count = tags = __tags;
  return tag;
}


//  ------------------------------------------------------------------
//  Appends a new tag number to the end of the tag array.
//  NOTE - Does not check for duplicates or correct sequence!

uint32_t* GTag::Append(uint32_t __tagn) {

  Resize(tags+1);
  tag[tags-1] = __tagn;
  return tag;
}


//  ------------------------------------------------------------------
//  Add a new tag number at the correct position of the tag array.
//
//  Algorithm dry test situations:
//
//  []        0123456
//  tag       345789x  tags = 7 (after resize)
//  _reln     1234567
//
//  Case 1 - New tag is smaller than first tag in array:
//    __tagn = 2
//    _reln = 1 (changed to 0)
//    tags-_reln-1 = 7-0-1 = 6
//
//  Case 2 - Normal insert in middle:
//    __tagn = 6
//    _reln = 3
//    tags-_reln-1 = 7-3-1 = 3
//
//  Case 3 - New tag is larger than last tag in array:
//    __tagn = 10
//    _reln = 6
//    tags-_reln-1 = 7-6-1 = 0
//
//  Dry test result: Works!

uint32_t* GTag::Add(uint32_t __tagn) {

  // Find closest tag number
  uint _reln = ToReln(__tagn, TAGN_CLOSEST);

  // Do we have it already?
  if((_reln == RELN_INVALID) or (tag[_reln-1] != __tagn)) {

    // Resize tag array to make room for the new number
    Resize(tags+1);

    //  General rule:
    //    - tag[_reln-1] is smaller than __tagn
    //
    //  The exceptions to the rule:
    //    - no tags in array (if _reln == RELN_INVALID)
    //    - tag[_reln-1] is larger than __tagn

    if(_reln and (tag[_reln-1] > __tagn))
      _reln--;

    // Move data to make room for the new tag number
    memmove(tag+_reln+1, tag+_reln, (tags-_reln-1)*sizeof(uint32_t));

    // Copy the new tag number to the insert position
    tag[_reln] = __tagn;
  }

  return tag;
}


//  ------------------------------------------------------------------
//  Delete a tag number from the tag array.
//  Returns the relative tag number or RELN_INVALID if missing.
//  NOTE - Does not resize the array.

uint GTag::Del(uint32_t __tagn) {

  return DelReln(ToReln(__tagn));
}


//  ------------------------------------------------------------------

uint GTag::DelReln(uint __reln) {

  if(__reln) {
    memmove(tag+__reln-1, tag+__reln, (tags-__reln)*sizeof(uint32_t));
    count--;
    tags--;
  }
  return __reln;
}


//  ------------------------------------------------------------------

uint GTag::DelResize(uint32_t __tagn) {

  uint _reln = Del(__tagn);
  Resize(tags);
  return _reln;
}


//  ------------------------------------------------------------------

static int TagnCmp(const uint32_t* __a, const uint32_t* __b) {

  return CmpV(*__a, *__b);
}


//  ------------------------------------------------------------------

void GTag::Sort() {

  qsort(tag, tags, sizeof(uint32_t), (StdCmpCP)TagnCmp);
}

//  ------------------------------------------------------------------

void GTag::ElimDups() {

  if(tags > 1) {
    uint _before = tags;
    uint32_t _last = tag[0];
    for(uint n=1; n<tags; n++) {
      if(_last == tag[n]) {
        memmove(&tag[n-1], &tag[n], (tags-n)*sizeof(uint32_t));
        tags--;
        n--;
      }
      _last = tag[n];
    }
    if(_before != tags)
      Resize(tags);
  }
}


//  ------------------------------------------------------------------
//  Return the tag number corresponding to a relative tag number.
//  Returns TAGN_INVALID, if the requested number did not exist.

uint32_t GTag::CvtReln(uint __reln) {

  if(tag and __reln and (__reln <= tags))
    return tag[__reln-1];
  else
    return TAGN_INVALID;
}


//  ------------------------------------------------------------------
//  Return the relative tag number corresponding to a tag number.
//  Returns RELN_INVALID if the requested number did not exist.
//  If __closest is true, the closest smaller tag number is returned.
//  If no tags exist at all, RELN_INVALID is returned.

uint GTag::ToReln(uint32_t __tagn, int __closest) {

  uint _lastreln = RELN_INVALID;

  if(tag) {

    if(__tagn and tags)
      if(__tagn > tag[tags-1])
        return __closest ? tags : RELN_INVALID;

    if(tags and __tagn) {

      int _mid;
      int _left = 0;
      int _right = tags;

      do {
        _mid = (_left+_right)/2;
        if(__tagn < tag[(uint)_mid])
          _right = _mid - 1;
        else if(__tagn > tag[(uint)_mid])
          _left = _mid + 1;
        else
          return (uint)(_mid + 1);
      } while(_left < _right);

      _lastreln = (uint)(_left + 1);
      if(__tagn == tag[(uint)_left])
        return _lastreln;
    }
  }

  // Not found
  return __closest ? _lastreln : RELN_INVALID;
}


//  ------------------------------------------------------------------

uint GTag::ToReln(uint32_t __tagn) {

  return ToReln(__tagn, TAGN_EXACT);
}


//  ------------------------------------------------------------------

void GTag::Load(gfile& fp)
{
  dword val;

  fp.Fread(&val, sizeof(dword));
  count = (uint) val;
  if (count)
  {
    Resize(count);
    fp.Fread(tag, sizeof(uint32_t), count);
  }
}


//  ------------------------------------------------------------------

void GTag::Save(gfile& fp)
{
  dword val = (dword) count;
  fp.Fwrite(&val, sizeof(dword));

  if (tag and count)
    fp.Fwrite(tag, sizeof(uint32_t), count);
}


//  ------------------------------------------------------------------