/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-  */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// This code is a port to NS Mork from public domain Mithril C++ sources.
// Note many code comments here come verbatim from cut-and-pasted Mithril.
// In many places, code is identical; Mithril versions stay public domain.
// Changes in porting are mainly class type and scalar type name changes.

#include "nscore.h"

#ifndef _MDB_
#include "mdb.h"
#endif

#ifndef _MORK_
#include "mork.h"
#endif

#ifndef _MORKNODE_
#include "morkNode.h"
#endif

#ifndef _MORKPROBEMAP_
#include "morkProbeMap.h"
#endif

#ifndef _MORKENV_
#include "morkEnv.h"
#endif

/*============================================================================*/
/* morkMapScratch */

void morkMapScratch::halt_map_scratch(morkEnv* ev)
{
  nsIMdbHeap* heap = sMapScratch_Heap;
  
  if ( heap )
  {
    if ( sMapScratch_Keys )
      heap->Free(ev->AsMdbEnv(), sMapScratch_Keys);
    if ( sMapScratch_Vals )
      heap->Free(ev->AsMdbEnv(), sMapScratch_Vals);
  }
}

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789


/*============================================================================*/
/* morkProbeMap */

void morkProbeMap::ProbeMapBadTagError(morkEnv* ev) const
{
  ev->NewError("bad sProbeMap_Tag");
}

void morkProbeMap::WrapWithNoVoidSlotError(morkEnv* ev) const
{
  ev->NewError("wrap without void morkProbeMap slot");
}

void morkProbeMap::GrowFailsMaxFillError(morkEnv* ev) const
{
  ev->NewError("grow fails morkEnv > sMap_Fill");
}

void morkProbeMap::MapKeyIsNotIPError(morkEnv* ev) const
{
  ev->NewError("not sMap_KeyIsIP");
}

void morkProbeMap::MapValIsNotIPError(morkEnv* ev) const
{
  ev->NewError("not sMap_ValIsIP");
}

void morkProbeMap::rehash_old_map(morkEnv* ev, morkMapScratch* ioScratch)
{
  mork_size keySize = sMap_KeySize; // size of every key bucket
  mork_size valSize = sMap_ValSize; // size of every associated value
  
  mork_count slots = sMap_Slots; // number of new buckets
  mork_u1* keys = sMap_Keys; // destination for rehashed keys
  mork_u1* vals = sMap_Vals; // destination for any copied values
  
  mork_bool keyIsIP = ( keys && keySize == sizeof(mork_ip) && sMap_KeyIsIP );
  mork_bool valIsIP = ( vals && valSize == sizeof(mork_ip) && sMap_ValIsIP );

  mork_count oldSlots = ioScratch->sMapScratch_Slots; // sMap_Slots
  mork_u1* oldKeys = ioScratch->sMapScratch_Keys; // sMap_Keys
  mork_u1* oldVals = ioScratch->sMapScratch_Vals; // sMap_Vals
  mork_u1* end = oldKeys + (keySize * oldSlots); // one byte past last key
  
  mork_fill fill = 0; // let's count the actual fill for a double check
  
  while ( oldKeys < end ) // another old key bucket to rehash if non-nil?
  {
    if ( !this->ProbeMapIsKeyNil(ev, oldKeys) ) // need to rehash?
    {
      ++fill; // this had better match sMap_Fill when we are all done
      mork_u4 hash = this->ProbeMapHashMapKey(ev, oldKeys);

      mork_pos i = hash % slots;   // target hash bucket
      mork_pos startPos = i;       // remember start to detect
      
      mork_u1* k = keys + (i * keySize);
      while ( !this->ProbeMapIsKeyNil(ev, k) )
      {
        if ( ++i >= (mork_pos)slots ) // advanced past end? need to wrap around now?
          i = 0; // wrap around to first slot in map's hash table
          
        if ( i == startPos ) // no void slots were found anywhere in map?
        {
          this->WrapWithNoVoidSlotError(ev); // should never happen
          return; // this is bad, and we can't go on with the rehash
        }
        k = keys + (i * keySize);
      }
      if ( keyIsIP ) // int special case?
        *((mork_ip*) k) = *((const mork_ip*) oldKeys); // fast bitwise copy
      else
        MORK_MEMCPY(k, oldKeys, keySize); // slow bitwise copy

      if ( oldVals ) // need to copy values as well?
      {
        mork_size valOffset = (i * valSize);
        mork_u1* v = vals + valOffset;
        mork_u1* ov = oldVals + valOffset;
        if ( valIsIP ) // int special case?
          *((mork_ip*) v) = *((const mork_ip*) ov); // fast bitwise copy
        else
          MORK_MEMCPY(v, ov, valSize); // slow bitwise copy
      }
    }
    oldKeys += keySize; // advance to next key bucket in old map
  }
  if ( fill != sMap_Fill ) // is the recorded value of sMap_Fill wrong?
  {
    ev->NewWarning("fill != sMap_Fill");
    sMap_Fill = fill;
  }
}

mork_bool morkProbeMap::grow_probe_map(morkEnv* ev)
{
  if ( sMap_Heap ) // can we grow the map?
  {
    mork_num newSlots = ((sMap_Slots * 4) / 3) + 1; // +25%
    morkMapScratch old; // a place to temporarily hold all the old arrays
    if ( this->new_slots(ev, &old, newSlots) ) // have more?
    {      
      ++sMap_Seed; // note the map has changed
      this->rehash_old_map(ev, &old);
      
      if ( ev->Good() ) 
      {
        mork_count slots = sMap_Slots;
        mork_num emptyReserve = (slots / 7) + 1; // keep this many empty
        mork_fill maxFill = slots - emptyReserve; // new max occupancy
        if ( maxFill > sMap_Fill ) // new max is bigger than old occupancy?
          sProbeMap_MaxFill = maxFill; // we can install new max for fill
        else
          this->GrowFailsMaxFillError(ev); // we have invariant failure
      }
      
      if ( ev->Bad() ) // rehash failed? need to revert map to last state?
        this->revert_map(ev, &old); // swap the vectors back again

      old.halt_map_scratch(ev); // remember to free the old arrays
    }
  }
  else ev->OutOfMemoryError();
  
  return ev->Good();
}

void morkProbeMap::revert_map(morkEnv* ev, morkMapScratch* ioScratch)
{
  mork_count tempSlots = ioScratch->sMapScratch_Slots; // sMap_Slots  
  mork_u1* tempKeys = ioScratch->sMapScratch_Keys;     // sMap_Keys
  mork_u1* tempVals = ioScratch->sMapScratch_Vals;     // sMap_Vals
  
  ioScratch->sMapScratch_Slots = sMap_Slots;
  ioScratch->sMapScratch_Keys = sMap_Keys;
  ioScratch->sMapScratch_Vals = sMap_Vals;
  
  sMap_Slots = tempSlots;
  sMap_Keys = tempKeys;
  sMap_Vals = tempVals;
}

void morkProbeMap::put_probe_kv(morkEnv* ev,
  const void* inAppKey, const void* inAppVal, mork_pos inPos)
{
  mork_u1* mapVal = 0;
  mork_u1* mapKey = 0;

  mork_num valSize = sMap_ValSize;
  if ( valSize && inAppVal ) // map holds values? caller sends value?
  {
    mork_u1* val = sMap_Vals + (valSize * inPos);
    if ( valSize == sizeof(mork_ip) && sMap_ValIsIP ) // int special case? 
      *((mork_ip*) val) = *((const mork_ip*) inAppVal);
    else
      mapVal = val; // show possible need to call ProbeMapPushIn()
  }
  if ( inAppKey ) // caller sends the key? 
  {
    mork_num keySize = sMap_KeySize;
    mork_u1* key = sMap_Keys + (keySize * inPos);
    if ( keySize == sizeof(mork_ip) && sMap_KeyIsIP ) // int special case? 
      *((mork_ip*) key) = *((const mork_ip*) inAppKey);
    else
      mapKey = key; // show possible need to call ProbeMapPushIn()
  }
  else
    ev->NilPointerError();

  if ( (  inAppVal && mapVal ) || ( inAppKey && mapKey ) )
    this->ProbeMapPushIn(ev, inAppKey, inAppVal, mapKey, mapVal);

  if ( sMap_Fill > sProbeMap_MaxFill )
    this->grow_probe_map(ev);
}

void morkProbeMap::get_probe_kv(morkEnv* ev,
  void* outAppKey, void* outAppVal, mork_pos inPos) const
{
  const mork_u1* mapVal = 0;
  const mork_u1* mapKey = 0;

  mork_num valSize = sMap_ValSize;
  if ( valSize && outAppVal ) // map holds values? caller wants value?
  {
    const mork_u1* val = sMap_Vals + (valSize * inPos);
    if ( valSize == sizeof(mork_ip) && sMap_ValIsIP ) // int special case? 
      *((mork_ip*) outAppVal) = *((const mork_ip*) val);
    else
      mapVal = val; // show possible need to call ProbeMapPullOut()
  }
  if ( outAppKey ) // caller wants the key? 
  {
    mork_num keySize = sMap_KeySize;
    const mork_u1* key = sMap_Keys + (keySize * inPos);
    if ( keySize == sizeof(mork_ip) && sMap_KeyIsIP ) // int special case? 
      *((mork_ip*) outAppKey) = *((const mork_ip*) key);
    else
      mapKey = key; // show possible need to call ProbeMapPullOut()
  }
  if ( ( outAppVal && mapVal ) || ( outAppKey && mapKey ) )
    this->ProbeMapPullOut(ev, mapKey, mapVal, outAppKey, outAppVal);
}

mork_test
morkProbeMap::find_key_pos(morkEnv* ev, const void* inAppKey,
  mork_u4 inHash, mork_pos* outPos) const
{
  mork_u1* k = sMap_Keys;        // array of keys, each of size sMap_KeySize
  mork_num size = sMap_KeySize;  // number of bytes in each key
  mork_count slots = sMap_Slots; // total number of key buckets
  mork_pos i = inHash % slots;   // target hash bucket
  mork_pos startPos = i;         // remember start to detect
  
  mork_test outTest = this->MapTest(ev, k + (i * size), inAppKey);
  while ( outTest == morkTest_kMiss )
  {
    if ( ++i >= (mork_pos)slots ) // advancing goes beyond end? need to wrap around now?
      i = 0; // wrap around to first slot in map's hash table
      
    if ( i == startPos ) // no void slots were found anywhere in map?
    {
      this->WrapWithNoVoidSlotError(ev); // should never happen
      break; // end loop on kMiss; note caller expects either kVoid or kHit
    }
    outTest = this->MapTest(ev, k + (i * size), inAppKey);
  }
  *outPos = i;
  
  return outTest;
}
 
void morkProbeMap::probe_map_lazy_init(morkEnv* ev)
{
  if ( this->need_lazy_init() && sMap_Fill == 0 ) // pending lazy action?
  {
    // The constructor cannot successfully call virtual ProbeMapClearKey(),
    // so we lazily do so now, when we add the first member to the map.
    
    mork_u1* keys = sMap_Keys;
    if ( keys ) // okay to call lazy virtual clear method on new map keys?
    {
      if ( sProbeMap_ZeroIsClearKey ) // zero is good enough to clear keys?
      {
        mork_num keyVolume = sMap_Slots * sMap_KeySize;
        if ( keyVolume )
          MORK_MEMSET(keys, 0, keyVolume);
      }
      else
        this->ProbeMapClearKey(ev, keys, sMap_Slots);
    }
    else
      this->MapNilKeysError(ev);
  }
  sProbeMap_LazyClearOnAdd = 0; // don't do this ever again
}

mork_bool
morkProbeMap::MapAtPut(morkEnv* ev,
  const void* inAppKey, const void* inAppVal,
  void* outAppKey, void* outAppVal)
{
  mork_bool outPut = morkBool_kFalse;
  
  if ( this->GoodProbeMap() ) /* looks good? */
  {
    if ( this->need_lazy_init() && sMap_Fill == 0 ) // pending lazy action?
      this->probe_map_lazy_init(ev);
          
    if ( ev->Good() )
    {
      mork_pos slotPos = 0;
      mork_u4 hash = this->MapHash(ev, inAppKey);
      mork_test test = this->find_key_pos(ev, inAppKey, hash, &slotPos);
      outPut = ( test == morkTest_kHit );

      if ( outPut ) // replacing an old assoc? no change in member count?
      {
        if ( outAppKey || outAppVal ) /* copy old before cobber? */
          this->get_probe_kv(ev, outAppKey, outAppVal, slotPos);
      }
      else // adding a new assoc increases membership by one
      {
        ++sMap_Fill; /* one more member in the collection */
      }
      
      if ( test != morkTest_kMiss ) /* found slot to hold new assoc? */
      {
        ++sMap_Seed; /* note the map has changed */
        this->put_probe_kv(ev, inAppKey, inAppVal, slotPos);
      }
    }
  }
  else this->ProbeMapBadTagError(ev);
  
  return outPut;
}
    
mork_bool
morkProbeMap::MapAt(morkEnv* ev, const void* inAppKey,
    void* outAppKey, void* outAppVal)
{
  if ( this->GoodProbeMap() ) /* looks good? */
  {
    if ( this->need_lazy_init() && sMap_Fill == 0 ) // pending lazy action?
      this->probe_map_lazy_init(ev);
          
    mork_pos slotPos = 0;
    mork_u4 hash = this->MapHash(ev, inAppKey);
    mork_test test = this->find_key_pos(ev, inAppKey, hash, &slotPos);
    if ( test == morkTest_kHit ) /* found an assoc pair for inAppKey? */
    {
      this->get_probe_kv(ev, outAppKey, outAppVal, slotPos);
      return morkBool_kTrue;
    }
  }
  else this->ProbeMapBadTagError(ev);
  
  return morkBool_kFalse;
}
    
mork_num
morkProbeMap::MapCutAll(morkEnv* ev)
{
  mork_num outCutAll = 0;
  
  if ( this->GoodProbeMap() ) /* looks good? */
  {
    outCutAll = sMap_Fill; /* number of members cut, which is all of them */
    
    if ( sMap_Keys && !sProbeMap_ZeroIsClearKey )
      this->ProbeMapClearKey(ev, sMap_Keys, sMap_Slots);

    sMap_Fill = 0; /* map now has no members */
  }
  else this->ProbeMapBadTagError(ev);
  
  return outCutAll;
}
    
// { ===== node interface =====

/*virtual*/
morkProbeMap::~morkProbeMap() // assert NodeStop() finished earlier
{
  MORK_ASSERT(sMap_Keys==0);
  MORK_ASSERT(sProbeMap_Tag==0);
}

/*public virtual*/ void
morkProbeMap::CloseMorkNode(morkEnv* ev) // CloseMap() only if open
{
  if ( this->IsOpenNode() )
  {
    this->MarkClosing();
    this->CloseProbeMap(ev);
    this->MarkShut();
  }
}

void morkProbeMap::CloseProbeMap(morkEnv* ev)
{
    if ( this->IsNode() )
    {
      nsIMdbHeap* heap = sMap_Heap;
      if ( heap ) // able to free map arrays?
      {
        void* block = sMap_Keys;
        if ( block )
        {
          heap->Free(ev->AsMdbEnv(), block);
          sMap_Keys = 0;
        }
          
        block = sMap_Vals;
        if ( block )
        {
          heap->Free(ev->AsMdbEnv(), block);
          sMap_Vals = 0;
        }
      }
      sMap_Keys = 0;
      sMap_Vals = 0;
      
      this->CloseNode(ev);
      sProbeMap_Tag = 0;
      sProbeMap_MaxFill = 0;
      
      this->MarkShut();
    }
    else
      this->NonNodeError(ev);
}

void*
morkProbeMap::clear_alloc(morkEnv* ev, mork_size inSize)
{
  void* p = 0;
  nsIMdbHeap* heap = sMap_Heap;
  if ( heap )
  {
    if (NS_SUCCEEDED(heap->Alloc(ev->AsMdbEnv(), inSize, (void**) &p)) && p )
    {
      MORK_MEMSET(p, 0, inSize);
      return p;
    }
  }
  else
    ev->NilPointerError();
    
  return (void*) 0;
}

/*| map_new_keys: allocate an array of inSlots new keys filled with zero.
**| (cf IronDoc's FeHashTable_new_keys())
|*/
mork_u1*
morkProbeMap::map_new_keys(morkEnv* ev, mork_num inSlots)
{
  mork_num size = inSlots * sMap_KeySize;
  return (mork_u1*) this->clear_alloc(ev, size);
}

/*| map_new_vals: allocate an array of inSlots new values filled with zero.
**| When values are zero sized, we just return a null pointer.
**|
**| (cf IronDoc's FeHashTable_new_values())
|*/
mork_u1*
morkProbeMap::map_new_vals(morkEnv* ev, mork_num inSlots)
{
  mork_u1* values = 0;
  mork_num size = inSlots * sMap_ValSize;
  if ( size )
    values = (mork_u1*) this->clear_alloc(ev, size);
  return values;
}


void morkProbeMap::MapSeedOutOfSyncError(morkEnv* ev)
{
  ev->NewError("sMap_Seed out of sync");
}

void morkProbeMap::MapFillUnderflowWarning(morkEnv* ev)
{
  ev->NewWarning("sMap_Fill underflow");
}

void morkProbeMap::MapNilKeysError(morkEnv* ev)
{
  ev->NewError("nil sMap_Keys");
}

void morkProbeMap::MapZeroKeySizeError(morkEnv* ev)
{
  ev->NewError("zero sMap_KeySize");
}

/*static*/
void morkProbeMap::ProbeMapCutError(morkEnv* ev)
{
  ev->NewError("morkProbeMap cannot cut");
}


void morkProbeMap::init_probe_map(morkEnv* ev, mork_size inSlots)
{
  // Note we cannot successfully call virtual ProbeMapClearKey() when we
  // call init_probe_map() inside the constructor; so we leave this problem
  // to the caller.  (The constructor will call ProbeMapClearKey() later
  // after setting a suitable lazy flag to show this action is pending.)

  if ( ev->Good() )
  {
    morkMapScratch old;

    if ( inSlots < 7 ) // capacity too small?
      inSlots = 7; // increase to reasonable minimum
    else if ( inSlots > (128 * 1024) ) // requested capacity too big?
      inSlots = (128 * 1024); // decrease to reasonable maximum
      
    if ( this->new_slots(ev, &old, inSlots) )
      sProbeMap_Tag = morkProbeMap_kTag;
      
    mork_count slots = sMap_Slots;
    mork_num emptyReserve = (slots / 7) + 1; // keep this many empty
    sProbeMap_MaxFill = slots - emptyReserve;

    MORK_MEMSET(&old, 0, sizeof(morkMapScratch)); // don't bother halting
  }
}

mork_bool
morkProbeMap::new_slots(morkEnv* ev, morkMapScratch* old, mork_num inSlots)
{
  mork_bool outNew = morkBool_kFalse;
  
  // Note we cannot successfully call virtual ProbeMapClearKey() when we
  // call new_slots() inside the constructor; so we leave this problem
  // to the caller.  (The constructor will call ProbeMapClearKey() later
  // after setting a suitable lazy flag to show this action is pending.)
    
  // allocate every new array before we continue:
  mork_u1* newKeys = this->map_new_keys(ev, inSlots);
  mork_u1* newVals = this->map_new_vals(ev, inSlots);
  
  // okay for newVals to be null when values are zero sized?
  mork_bool okayValues = ( newVals || !sMap_ValSize );
  
  if ( newKeys && okayValues )
  {
    outNew = morkBool_kTrue; // we created every array needed

    // init mapScratch using slots from current map:
    old->sMapScratch_Heap = sMap_Heap;
    
    old->sMapScratch_Slots = sMap_Slots;
    old->sMapScratch_Keys = sMap_Keys;
    old->sMapScratch_Vals = sMap_Vals;
    
    // replace all map array slots using the newly allocated members:
    ++sMap_Seed; // the map has changed
    sMap_Keys = newKeys;
    sMap_Vals = newVals;
    sMap_Slots = inSlots;
  }
  else // free any allocations if only partially successful
  {
    nsIMdbHeap* heap = sMap_Heap;
    if ( newKeys )
      heap->Free(ev->AsMdbEnv(), newKeys);
    if ( newVals )
      heap->Free(ev->AsMdbEnv(), newVals);
    
    MORK_MEMSET(old, 0, sizeof(morkMapScratch)); // zap scratch space
  }
  
  return outNew;
}

void
morkProbeMap::clear_probe_map(morkEnv* ev, nsIMdbHeap* ioMapHeap)
{
  sProbeMap_Tag = 0;
  sMap_Seed = 0;
  sMap_Slots = 0;
  sMap_Fill = 0;
  sMap_Keys = 0;
  sMap_Vals = 0;
  sProbeMap_MaxFill = 0;
  
  sMap_Heap = ioMapHeap;
  if ( !ioMapHeap )
    ev->NilPointerError();
}

morkProbeMap::morkProbeMap(morkEnv* ev, const morkUsage& inUsage,
  nsIMdbHeap* ioNodeHeap,
  mork_size inKeySize, mork_size inValSize,
  nsIMdbHeap* ioMapHeap, mork_size inSlots,
  mork_bool inZeroIsClearKey)
  
: morkNode(ev, inUsage, ioNodeHeap)
, sMap_Heap( ioMapHeap )
    
, sMap_Keys( 0 )
, sMap_Vals( 0 )
  
, sMap_Seed( 0 )   // change count of members or structure
    
, sMap_Slots( 0 )  // count of slots in the hash table
, sMap_Fill( 0 )   // number of used slots in the hash table

, sMap_KeySize( 0 ) // size of each key (cannot be zero)
, sMap_ValSize( 0 ) // size of each val (zero allowed)
  
, sMap_KeyIsIP( morkBool_kFalse ) // sMap_KeySize == sizeof(mork_ip)
, sMap_ValIsIP( morkBool_kFalse ) // sMap_ValSize == sizeof(mork_ip)

, sProbeMap_MaxFill( 0 )
, sProbeMap_LazyClearOnAdd( 0 )
, sProbeMap_ZeroIsClearKey( inZeroIsClearKey )
, sProbeMap_Tag( 0 )
{
  // Note we cannot successfully call virtual ProbeMapClearKey() when we
  // call init_probe_map() inside the constructor; so we leave this problem
  // to the caller.  (The constructor will call ProbeMapClearKey() later
  // after setting a suitable lazy flag to show this action is pending.)

  if ( ev->Good() )
  {
    this->clear_probe_map(ev, ioMapHeap);
    if ( ev->Good() )
    {      
      sMap_KeySize = inKeySize;
      sMap_ValSize = inValSize;
      sMap_KeyIsIP = ( inKeySize == sizeof(mork_ip) );
      sMap_ValIsIP = ( inValSize == sizeof(mork_ip) );
      
      this->init_probe_map(ev, inSlots);
      if ( ev->Good() )
      {
        if ( !inZeroIsClearKey ) // must lazy clear later with virtual method?
          sProbeMap_LazyClearOnAdd = morkProbeMap_kLazyClearOnAdd;
          
        mNode_Derived = morkDerived_kProbeMap;
      }
    }
  }
}

/*============================================================================*/

/*virtual*/ mork_test // hit(a,b) implies hash(a) == hash(b)
morkProbeMap::MapTest(morkEnv* ev,
  const void* inMapKey, const void* inAppKey) const
  // Note inMapKey is always a key already stored in the map, while inAppKey
  //   is always a method argument parameter from a client method call.
  //   This matters the most in morkProbeMap subclasses, which have the
  //   responsibility of putting 'app' keys into slots for 'map' keys, and
  //   the bit pattern representation might be different in such cases.
  // morkTest_kHit means that inMapKey equals inAppKey (and this had better
  //   also imply that hash(inMapKey) == hash(inAppKey)).
  // morkTest_kMiss means that inMapKey does NOT equal inAppKey (but this
  //   implies nothing at all about hash(inMapKey) and hash(inAppKey)).
  // morkTest_kVoid means that inMapKey is not a valid key bit pattern,
  //   which means that key slot in the map is not being used.  Note that
  //   kVoid is only expected as a return value in morkProbeMap subclasses,
  //   because morkProbeMap must ask whether a key slot is used or not. 
  //   morkChainMap however, always knows when a key slot is used, so only
  //   key slots expected to have valid bit patterns will be presented to
  //   the MapTest() methods for morkChainMap subclasses.
  //
  // NOTE: it is very important that subclasses correctly return the value
  // morkTest_kVoid whenever the slot for inMapKey contains a bit pattern
  // that means the slot is not being used, because this is the only way a
  // probe map can terminate an unsuccessful search for a key in the map.
{
  mork_size keySize = sMap_KeySize;
  if ( keySize == sizeof(mork_ip) && sMap_KeyIsIP )
  {
    mork_ip mapKey = *((const mork_ip*) inMapKey);
    if ( mapKey == *((const mork_ip*) inAppKey) )
      return morkTest_kHit;
    else
    {
      return ( mapKey )? morkTest_kMiss : morkTest_kVoid;
    }
  }
  else
  {
    mork_bool allSame = morkBool_kTrue;
    mork_bool allZero = morkBool_kTrue;
    const mork_u1* ak = (const mork_u1*) inAppKey;
    const mork_u1* mk = (const mork_u1*) inMapKey;
    const mork_u1* end = mk + keySize;
    --mk; // prepare for preincrement:
    while ( ++mk < end )
    {
      mork_u1 byte = *mk;
      if ( byte ) // any nonzero byte in map key means slot is not nil?
        allZero = morkBool_kFalse;
      if ( byte != *ak++ ) // bytes differ in map and app keys?
        allSame = morkBool_kFalse;
    }
    if ( allSame )
      return morkTest_kHit;
    else
      return ( allZero )? morkTest_kVoid : morkTest_kMiss;
  }
}

/*virtual*/ mork_u4 // hit(a,b) implies hash(a) == hash(b)
morkProbeMap::MapHash(morkEnv* ev, const void* inAppKey) const
{
  mork_size keySize = sMap_KeySize;
  if ( keySize == sizeof(mork_ip) && sMap_KeyIsIP )
  {
    return *((const mork_ip*) inAppKey);
  }
  else
  {
    const mork_u1* key = (const mork_u1*) inAppKey;
    const mork_u1* end = key + keySize;
    --key; // prepare for preincrement:
    while ( ++key < end )
    {
      if ( *key ) // any nonzero byte in map key means slot is not nil?
        return morkBool_kFalse;
    }
    return morkBool_kTrue;
  }
  return (mork_u4) NS_PTR_TO_INT32(inAppKey);
}


/*============================================================================*/

/*virtual*/ mork_u4
morkProbeMap::ProbeMapHashMapKey(morkEnv* ev, const void* inMapKey) const
  // ProbeMapHashMapKey() does logically the same thing as MapHash(), and
  // the default implementation actually calls virtual MapHash().  However,
  // Subclasses must override this method whenever the formats of keys in
  // the map differ from app keys outside the map, because MapHash() only
  // works on keys in 'app' format, while ProbeMapHashMapKey() only works
  // on keys in 'map' format.  This method is called in order to rehash all
  // map keys when a map is grown, and this causes all old map members to
  // move into new slot locations.
  //
  // Note it is absolutely imperative that a hash for a key in 'map' format
  // be exactly the same the hash of the same key in 'app' format, or else
  // maps will seem corrupt later when keys in 'app' format cannot be found.
{
  return this->MapHash(ev, inMapKey);
}

/*virtual*/ mork_bool
morkProbeMap::ProbeMapIsKeyNil(morkEnv* ev, void* ioMapKey)
  // ProbeMapIsKeyNil() must say whether the representation of logical 'nil'
  // is currently found inside the key at ioMapKey, for a key found within
  // the map.  The the map iterator uses this method to find map keys that
  // are actually being used for valid map associations; otherwise the
  // iterator cannot determine which map slots actually denote used keys.
  // The default method version returns true if all the bits equal zero.
{
  if ( sMap_KeySize == sizeof(mork_ip) && sMap_KeyIsIP )
  {
    return !*((const mork_ip*) ioMapKey);
  }
  else
  {
    const mork_u1* key = (const mork_u1*) ioMapKey;
    const mork_u1* end = key + sMap_KeySize;
    --key; // prepare for preincrement:
    while ( ++key < end )
    {
      if ( *key ) // any nonzero byte in map key means slot is not nil?
        return morkBool_kFalse;
    }
    return morkBool_kTrue;
  }
}

/*virtual*/ void
morkProbeMap::ProbeMapClearKey(morkEnv* ev, // put 'nil' alls keys inside map
  void* ioMapKey, mork_count inKeyCount) // array of keys inside map
  // ProbeMapClearKey() must put some representation of logical 'nil' into
  // every key slot in the map, such that MapTest() will later recognize
  // that this bit pattern shows each key slot is not actually being used.
  //
  // This method is typically called whenever the map is either created or
  // grown into a larger size, where ioMapKey is a pointer to an array of
  // inKeyCount keys, where each key is this->MapKeySize() bytes in size.
  // Note that keys are assumed immediately adjacent with no padding, so
  // if any alignment requirements must be met, then subclasses should have
  // already accounted for this when specifying a key size in the map.
  //
  // Since this method will be called when a map is being grown in size,
  // nothing should be assumed about the state slots of the map, since the
  // ioMapKey array might not yet live in sMap_Keys, and the array length
  // inKeyCount might not yet live in sMap_Slots.  However, the value kept
  // in sMap_KeySize never changes, so this->MapKeySize() is always correct.
{
  if ( ioMapKey && inKeyCount )
  {
    MORK_MEMSET(ioMapKey, 0, (inKeyCount * sMap_KeySize));
  }
  else
    ev->NilPointerWarning();
}

/*virtual*/ void
morkProbeMap::ProbeMapPushIn(morkEnv* ev, // move (key,val) into the map
  const void* inAppKey, const void* inAppVal, // (key,val) outside map
  void* outMapKey, void* outMapVal)      // (key,val) inside map
  // This method actually puts keys and vals in the map in suitable format.
  //
  // ProbeMapPushIn() must copy a caller key and value in 'app' format
  // into the map slots provided, which are in 'map' format.  When the
  // 'app' and 'map' formats are identical, then this is just a bitwise
  // copy of this->MapKeySize() key bytes and this->MapValSize() val bytes,
  // and this is exactly what the default implementation performs.  However,
  // if 'app' and 'map' formats are different, and MapTest() depends on this
  // difference in format, then subclasses must override this method to do
  // whatever is necessary to store the input app key in output map format.
  //
  // Do NOT write more than this->MapKeySize() bytes of a map key, or more
  // than this->MapValSize() bytes of a map val, or corruption might ensue.
  //
  // The inAppKey and inAppVal parameters are the same ones passed into a
  // call to MapAtPut(), and the outMapKey and outMapVal parameters are ones
  // determined by how the map currently positions key inAppKey in the map.
  //
  // Note any key or val parameter can be a null pointer, in which case
  // this method must do nothing with those parameters.  In particular, do
  // no key move at all when either inAppKey or outMapKey is nil, and do
  // no val move at all when either inAppVal or outMapVal is nil.  Note that
  // outMapVal should always be nil when this->MapValSize() is nil.
{
}

/*virtual*/ void
morkProbeMap::ProbeMapPullOut(morkEnv* ev, // move (key,val) out from the map
  const void* inMapKey, const void* inMapVal, // (key,val) inside map
  void* outAppKey, void* outAppVal) const    // (key,val) outside map
  // This method actually gets keys and vals from the map in suitable format.
  //
  // ProbeMapPullOut() must copy a key and val in 'map' format into the
  // caller key and val slots provided, which are in 'app' format.  When the
  // 'app' and 'map' formats are identical, then this is just a bitwise
  // copy of this->MapKeySize() key bytes and this->MapValSize() val bytes,
  // and this is exactly what the default implementation performs.  However,
  // if 'app' and 'map' formats are different, and MapTest() depends on this
  // difference in format, then subclasses must override this method to do
  // whatever is necessary to store the input map key in output app format.
  //
  // The outAppKey and outAppVal parameters are the same ones passed into a
  // call to either MapAtPut() or MapAt(), while inMapKey and inMapVal are
  // determined by how the map currently positions the target key in the map.
  //
  // Note any key or val parameter can be a null pointer, in which case
  // this method must do nothing with those parameters.  In particular, do
  // no key move at all when either inMapKey or outAppKey is nil, and do
  // no val move at all when either inMapVal or outAppVal is nil.  Note that
  // inMapVal should always be nil when this->MapValSize() is nil.
{
}


/*============================================================================*/
/* morkProbeMapIter */

morkProbeMapIter::morkProbeMapIter(morkEnv* ev, morkProbeMap* ioMap)
: sProbeMapIter_Map( 0 )
, sProbeMapIter_Seed( 0 )
, sProbeMapIter_HereIx( morkProbeMapIter_kBeforeIx )
{
  if ( ioMap )
  {
    if ( ioMap->GoodProbeMap() )
    {
      if ( ioMap->need_lazy_init() ) // pending lazy action?
        ioMap->probe_map_lazy_init(ev);
        
      sProbeMapIter_Map = ioMap;
      sProbeMapIter_Seed = ioMap->sMap_Seed;
    }
    else ioMap->ProbeMapBadTagError(ev);
  }
  else ev->NilPointerError();
}

void morkProbeMapIter::CloseMapIter(morkEnv* ev)
{
  MORK_USED_1(ev);
  sProbeMapIter_Map = 0;
  sProbeMapIter_Seed = 0;

  sProbeMapIter_HereIx = morkProbeMapIter_kAfterIx;
}

morkProbeMapIter::morkProbeMapIter( )
// zero most slots; caller must call InitProbeMapIter()
{
  sProbeMapIter_Map = 0;
  sProbeMapIter_Seed = 0;

  sProbeMapIter_HereIx = morkProbeMapIter_kBeforeIx;
}

void morkProbeMapIter::InitProbeMapIter(morkEnv* ev, morkProbeMap* ioMap)
{
  sProbeMapIter_Map = 0;
  sProbeMapIter_Seed = 0;

  sProbeMapIter_HereIx = morkProbeMapIter_kBeforeIx;

  if ( ioMap )
  {
    if ( ioMap->GoodProbeMap() )
    {
      if ( ioMap->need_lazy_init() ) // pending lazy action?
        ioMap->probe_map_lazy_init(ev);
        
      sProbeMapIter_Map = ioMap;
      sProbeMapIter_Seed = ioMap->sMap_Seed;
    }
    else ioMap->ProbeMapBadTagError(ev);
  }
  else ev->NilPointerError();
}
 
mork_bool morkProbeMapIter::IterFirst(morkEnv* ev,
  void* outAppKey, void* outAppVal)
{
  sProbeMapIter_HereIx = morkProbeMapIter_kAfterIx; // default to done
  morkProbeMap* map = sProbeMapIter_Map;
  
  if ( map && map->GoodProbeMap() ) /* looks good? */
  {
    sProbeMapIter_Seed = map->sMap_Seed; /* sync the seeds */
    
    mork_u1* k = map->sMap_Keys;  // array of keys, each of size sMap_KeySize
    mork_num size = map->sMap_KeySize;  // number of bytes in each key
    mork_count slots = map->sMap_Slots; // total number of key buckets
    mork_pos here = 0;  // first hash bucket
    
    while ( here < (mork_pos)slots )
    {
      if ( !map->ProbeMapIsKeyNil(ev, k + (here * size)) )
      {
        map->get_probe_kv(ev, outAppKey, outAppVal, here);
        
        sProbeMapIter_HereIx = (mork_i4) here;
        return morkBool_kTrue;
      }
      ++here; // next bucket
    } 
  }
  else map->ProbeMapBadTagError(ev);

  return morkBool_kFalse;
}

mork_bool morkProbeMapIter::IterNext(morkEnv* ev,
  void* outAppKey, void* outAppVal)
{
  morkProbeMap* map = sProbeMapIter_Map;
  
  if ( map && map->GoodProbeMap() ) /* looks good? */
  {    
    if ( sProbeMapIter_Seed == map->sMap_Seed ) /* in sync? */
    {
      if ( sProbeMapIter_HereIx != morkProbeMapIter_kAfterIx )
      {
        mork_pos here = (mork_pos) sProbeMapIter_HereIx;
        if ( sProbeMapIter_HereIx < 0 )
          here = 0;
        else
          ++here;
          
        sProbeMapIter_HereIx = morkProbeMapIter_kAfterIx; // default to done

        mork_u1* k = map->sMap_Keys;  // key array, each of size sMap_KeySize
        mork_num size = map->sMap_KeySize;  // number of bytes in each key
        mork_count slots = map->sMap_Slots; // total number of key buckets
        
        while ( here < (mork_pos)slots )
        {
          if ( !map->ProbeMapIsKeyNil(ev, k + (here * size)) )
          {
            map->get_probe_kv(ev, outAppKey, outAppVal, here);
            
            sProbeMapIter_HereIx = (mork_i4) here;
            return morkBool_kTrue;
          }
          ++here; // next bucket
        } 
      }
    }
    else map->MapSeedOutOfSyncError(ev);
  }
  else map->ProbeMapBadTagError(ev);

  return morkBool_kFalse;
}

mork_bool morkProbeMapIter::IterHere(morkEnv* ev,
  void* outAppKey, void* outAppVal)
{
  morkProbeMap* map = sProbeMapIter_Map;
  
  if ( map && map->GoodProbeMap() ) /* looks good? */
  {    
    if ( sProbeMapIter_Seed == map->sMap_Seed ) /* in sync? */
    {
      mork_pos here = (mork_pos) sProbeMapIter_HereIx;
      mork_count slots = map->sMap_Slots; // total number of key buckets
      if ( sProbeMapIter_HereIx >= 0 && (here < (mork_pos)slots))
      {
        mork_u1* k = map->sMap_Keys;  // key array, each of size sMap_KeySize
        mork_num size = map->sMap_KeySize;  // number of bytes in each key

        if ( !map->ProbeMapIsKeyNil(ev, k + (here * size)) )
        {
          map->get_probe_kv(ev, outAppKey, outAppVal, here);
          return morkBool_kTrue;
        }
      }
    }
    else map->MapSeedOutOfSyncError(ev);
  }
  else map->ProbeMapBadTagError(ev);

  return morkBool_kFalse;
}

mork_change*
morkProbeMapIter::First(morkEnv* ev, void* outKey, void* outVal)
{
  if ( this->IterFirst(ev, outKey, outVal) )
    return &sProbeMapIter_Change;
  
  return (mork_change*) 0;
}

mork_change*
morkProbeMapIter::Next(morkEnv* ev, void* outKey, void* outVal)
{
  if ( this->IterNext(ev, outKey, outVal) )
    return &sProbeMapIter_Change;
  
  return (mork_change*) 0;
}

mork_change*
morkProbeMapIter::Here(morkEnv* ev, void* outKey, void* outVal)
{
  if ( this->IterHere(ev, outKey, outVal) )
    return &sProbeMapIter_Change;
  
  return (mork_change*) 0;
}

mork_change*
morkProbeMapIter::CutHere(morkEnv* ev, void* outKey, void* outVal)
{
  morkProbeMap::ProbeMapCutError(ev);
  
  return (mork_change*) 0;
}

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

// NOTE: the following methods ONLY work for sMap_ValIsIP pointer values.
// (Note the implied assumption that zero is never a good value pattern.)

void* morkProbeMapIter::IterFirstVal(morkEnv* ev, void* outKey)
// equivalent to { void* v=0; this->IterFirst(ev, outKey, &v); return v; }
{
  morkProbeMap* map = sProbeMapIter_Map;
  if ( map )
  {
    if ( map->sMap_ValIsIP )
    {
      void* v = 0;
      this->IterFirst(ev, outKey, &v);
      return v;
    }
    else
      map->MapValIsNotIPError(ev);
  }
  return (void*) 0;
}

void* morkProbeMapIter::IterNextVal(morkEnv* ev, void* outKey)
// equivalent to { void* v=0; this->IterNext(ev, outKey, &v); return v; }
{
  morkProbeMap* map = sProbeMapIter_Map;
  if ( map )
  {
    if ( map->sMap_ValIsIP )
    {
      void* v = 0;
      this->IterNext(ev, outKey, &v);
      return v;
    }
    else
      map->MapValIsNotIPError(ev);
  }
  return (void*) 0;
}

void* morkProbeMapIter::IterHereVal(morkEnv* ev, void* outKey)
// equivalent to { void* v=0; this->IterHere(ev, outKey, &v); return v; }
{
  morkProbeMap* map = sProbeMapIter_Map;
  if ( map )
  {
    if ( map->sMap_ValIsIP )
    {
      void* v = 0;
      this->IterHere(ev, outKey, &v);
      return v;
    }
    else
      map->MapValIsNotIPError(ev);
  }
  return (void*) 0;
}

// NOTE: the following methods ONLY work for sMap_KeyIsIP pointer values.
// (Note the implied assumption that zero is never a good key pattern.)

void* morkProbeMapIter::IterFirstKey(morkEnv* ev)
// equivalent to { void* k=0; this->IterFirst(ev, &k, 0); return k; }
{
  morkProbeMap* map = sProbeMapIter_Map;
  if ( map )
  {
    if ( map->sMap_KeyIsIP )
    {
      void* k = 0;
      this->IterFirst(ev, &k, (void*) 0);
      return k;
    }
    else
      map->MapKeyIsNotIPError(ev);
  }
  return (void*) 0;
}

void* morkProbeMapIter::IterNextKey(morkEnv* ev)
// equivalent to { void* k=0; this->IterNext(ev, &k, 0); return k; }
{
  morkProbeMap* map = sProbeMapIter_Map;
  if ( map )
  {
    if ( map->sMap_KeyIsIP )
    {
      void* k = 0;
      this->IterNext(ev, &k, (void*) 0);
      return k;
    }
    else
      map->MapKeyIsNotIPError(ev);
  }
  return (void*) 0;
}

void* morkProbeMapIter::IterHereKey(morkEnv* ev)
// equivalent to { void* k=0; this->IterHere(ev, &k, 0); return k; }
{
  morkProbeMap* map = sProbeMapIter_Map;
  if ( map )
  {
    if ( map->sMap_KeyIsIP )
    {
      void* k = 0;
      this->IterHere(ev, &k, (void*) 0);
      return k;
    }
    else
      map->MapKeyIsNotIPError(ev);
  }
  return (void*) 0;
}

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789