/*
 * Distributed as part of mchange-commons-java 0.2.11
 *
 * Copyright (C) 2015 Machinery For Change, Inc.
 *
 * Author: Steve Waldman <swaldman@mchange.com>
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of EITHER:
 *
 *     1) The GNU Lesser General Public License (LGPL), version 2.1, as 
 *        published by the Free Software Foundation
 *
 * OR
 *
 *     2) The Eclipse Public License (EPL), version 1.0
 *
 * You may choose which license to accept if you wish to redistribute
 * or modify this work. You may offer derivatives of this work
 * under the license you have chosen, or you may provide the same
 * choice of license which you have been offered here.
 *
 * This software 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.
 *
 * You should have received copies of both LGPL v2.1 and EPL v1.0
 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
 * If not, the text of these licenses are currently available at
 *
 * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *  EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php 
 * 
 */

package com.mchange.util.impl;

import com.mchange.util.*;

public class LongObjectHash implements LongObjectMap
{
  LOHRecord[] records;
  float       load_factor;
  long        threshold;
  long        size;

  public LongObjectHash(int init_capacity, float load_factor)
    {
      this.records     = new LOHRecord[init_capacity];
      this.load_factor = load_factor;
      this.threshold   = (long) (load_factor * init_capacity);
    }

  public LongObjectHash()
    {this(101, 0.75f);} //defaults from java.util.Hashtable

  public synchronized Object get(long num)
    {
      int index  = (int) (num % records.length);
      Object out = null;
      if (records[index] != null)
	out = records[index].get(num);
      return out;
    }
  
  public synchronized void put(long num, Object obj)
    {
      int index = (int) (num % records.length);
      if (records[index] == null)
	records[index] = new LOHRecord(index);
      boolean replaced = records[index].add(num, obj, true);
      if (!replaced) ++size;
      if (size > threshold) rehash();
    }

  public synchronized boolean putNoReplace(long num, Object obj)
    {
      int index = (int)(num % records.length);
      if (records[index] == null)
	records[index] = new LOHRecord(index);
      boolean needed_replace = records[index].add(num, obj, false);
      if (needed_replace)
	{return false;}
      else
	{
	  ++size;
	  if (size > threshold) rehash();
	  return true;
	}
    }

  public long getSize()
    {return size;}

  public synchronized boolean containsLong(long num)
    {
      int index = (int) (num % records.length);
      return (records[index] != null && records[index].findLong(num) != null);
    }

  public synchronized Object remove(long num)
    {
      LOHRecord rec = records[(int) (num % records.length)];
      Object out = (rec == null ? null : rec.remove(num));
      if (out != null) size--;
      return out;
    }

  //should only be called from a sync'ed method
  protected void rehash()
    {
      if ((records.length * 2L) > Integer.MAX_VALUE)
	throw new Error("Implementation of LongObjectHash allows a capacity of only " + Integer.MAX_VALUE);

      LOHRecord[] newRecords = new LOHRecord[records.length * 2];
      for (int i = 0; i < records.length; ++i)
	{
	  if (records[i] != null)
	    {
	      newRecords[i]     = records[i];
	      newRecords[i * 2] = records[i].split(newRecords.length);
	    }
	}
      records = newRecords;
      threshold = (long) (load_factor * records.length);
    }
}

class LOHRecord extends LOHRecElem
{
  LongObjectHash parent;
  int size = 0;
  
  LOHRecord(long index)
    {super(index, null, null);}

  LOHRecElem findLong(long num) //retuns the RecElem previous to the one containing num
    {
      for (LOHRecElem finger = this; finger.next != null; finger = finger.next)
	if (finger.next.num == num) return finger;
      return null;
    }

  boolean add(long num, Object obj, boolean replace) //returns whether or not we would have had to replace
    {                                               //whether we did depends on the value of replace
      LOHRecElem prev = findLong(num);
      if (prev != null)
	{
	  if (replace)
	    prev.next = new LOHRecElem(num, obj, prev.next.next);
	  return true;
	}
      else
	{
	  this.next = new LOHRecElem(num, obj, this.next);
	  ++size;
	  return false; 
	}
    }

  Object remove(long num)
    {
      LOHRecElem prev = findLong(num);
      if (prev == null) return null;
      else
	{
	  Object out = prev.next.obj;
	  prev.next = prev.next.next;
	  --size;
 	  if (size == 0)
 	    parent.records[(int) this.num] = null; //kamikaze!!!
	  return out;
	}
    }

  Object get(long num)
    {
      LOHRecElem prev = findLong(num);
      if (prev != null)
	return prev.next.obj;
      else return null;
    }

  LOHRecord split(int new_cap)
    {
      LOHRecord  out       = null;
      LOHRecElem outFinger = null;
      for (LOHRecElem finger = this; finger.next != null; finger = finger.next)
	{
	  if ((finger.next.num % new_cap) != this.num)
	    {
	      if (out == null)
		{
		  out       = new LOHRecord(num * 2);
		  outFinger = out;
		}
	      outFinger.next = finger.next;
	      finger.next    = finger.next.next;
	      outFinger      = outFinger.next;
	      outFinger.next = null;
	    }
	}
      return out;
    }
}

class LOHRecElem
{
  long       num;
  Object     obj;
  LOHRecElem next;

  LOHRecElem(long num, Object obj, LOHRecElem next)
    {
      this.num  = num;
      this.obj  = obj;
      this.next = next;
    }
}
