import java.util.LinkedList;

public class HashMap
{
   public Entry[] bucket;
   public int capacity;
   public int prime;
   public int NItems;

   public int index_for_key;
   public int scale;
   public int shift;

   public Entry AVAILABLE = new Entry(null, null);  // Special entry to
                                                    // mark deleted entries

   /* ===============================================================
      General Constructor:  initialize prime number p
                            Create an array (bucket) of given size
      =============================================================== */
   public HashMap(int p, int MapArraySize)
   {
      prime = p;
      capacity = MapArraySize;
      bucket = new Entry[ capacity ];
      NItems = 0;

      java.util.Random rand = new java.util.Random();

      scale = rand.nextInt(prime-1) + 1;      // Multiplier in MAD
      shift = rand.nextInt(prime);            // Shift amount in MAD
   }

   /* ===============================================================
      Common Constructor:  pick the default prime number 109345121
                           Create an array (bucket) of given size
      =============================================================== */
   public HashMap(int MapArraySize)
   {
      this( 109345121, MapArraySize );   // Calls general constructor
   }

   public HashMap()
   {
      this( 109345121, 1000 );   // Default array size = 1000
   }


   /* ******************************************************************
      Map methods
      ****************************************************************** */
   public int size()
   {
      return( NItems );
   }

   public boolean isEmpty()
   {
      return( NItems == 0 );
   }


   /* ===============================================================
      Hash function

         Hash function applying MAD method to default hash code. 
      =============================================================== */
  public int hashValue(String key) 
  {
    int x = key.hashCode();      // String has a built-in hashCode() !!!

    return (int) ((Math.abs(x*scale + shift) % prime) % capacity);
  }

   /* ====================================================================
      fintEntry(key): find index of the array for given key

      Return value:

	 1 ==> key is found in the hash table
               Set index_for_key such that: Entry[index_for_key].key == key

         0 ==> key is NOT found 
               Entry[index_for_key] is where you can STORE the key
      ==================================================================== */
    public int findEntry(String key) 
    {
       int i;
       int start;

       if ( key == null )
       {
          index_for_key = -1;              // Error
          return ( -1 );                   // Not found
       }

       i = hashValue(key);   // Start location to look for key

       start = i;	     // Remember the start location (to detect end)

       index_for_key = -1;   // Flag "no hole found" condition.

       do 
       {
          Entry e;

	  e = bucket[i];

          if ( e == null ) 
	  {  /* --------------------------------------------------
	        We arrive at a hole: key is not in hash table and 
		we found a location where you can insert the key
		-------------------------------------------------- */
	     if ( index_for_key < 0 )
                index_for_key = i;
             return(0);
          }

          if (key.equals(e.getKey()))    // we have found our key
          {
	     index_for_key = i;
             return 1;                   // key found
          }

          if (e == AVAILABLE) 
	  {  // entry in bucket was deleted 

             if ( index_for_key < 0 )
                index_for_key = i;    // remember the FIRST available slot
          }

          i = (i + 1) % capacity;     // Check the next slot... if any

       } while (i != start);     // Stop when you wrap around

       return 0;                 // Not found
   }

   public Integer get(String k)
   {
      int found = findEntry(k);     // Find the array index for the key k
                                    // If found > 0, index_for_key is the index

      if ( found > 0 )
      {
         return bucket[index_for_key].getValue();  // return value 
      }
      else
      {
         return null;                      // Key not found
      }
  }


  public Integer put (String key, int value) 
  {
    int found = findEntry(key); //find the appropriate spot for this entry

    if ( found > 0 ) //  key found
    {
       Integer oldValue = bucket[index_for_key].setValue(value);// set new value

       return ( oldValue );  // Return old value
    }

    /* ===================================================
       Keep occupance (load factor) of array at 50%
       =================================================== */
    if (NItems >= capacity/2) 
    {
      rehash();               // rehash to keep the load factor <= 0.5
      found = findEntry(key); // find the appropriate spot for this entry
    }

    /* ===================================================
       Insert (key, value) in bucket[index_for_key]
       =================================================== */
    bucket[index_for_key] = new Entry(key, value); // convert to proper index
    NItems++;
    return null;        // there was no previous value
  }

  public Integer remove (String key) 
  {
    int found = findEntry(key);     // find this key first

    if ( found == 0 ) 
        return null;     // nothing to remove

    Integer toReturn = bucket[index_for_key].getValue();

    bucket[index_for_key] = AVAILABLE;   // mark this slot as deactivated
    NItems--;
    return toReturn;
  }


  public void rehash() 
  {
      Entry[] old = bucket;              // Old contains the original entries

      capacity = 2*capacity;
      bucket = new Entry[capacity];      // new bucket is twice as big

      java.util.Random rand = new java.util.Random();

      scale = rand.nextInt(prime-1) + 1;          // new hash scaling factor
      shift = rand.nextInt(prime);                // new hash shifting factor

      for (int i=0; i<old.length; i++) 
      {
         Entry e;

         e = old[i];
         if ((e != null) && (e != AVAILABLE)) 
         {  // e is a valid entry

            findEntry(e.getKey());          // Find index for the key
            bucket[index_for_key] = e;      // Insert in new bucket
        }
      }
  }

  /* =======================================================
     Value-added methods... 
     ======================================================= */

   public Iterable<String> keySet()
   {
      LinkedList<String> r = new LinkedList<String>();
                                     // One of the classes that implements
                                     // "Iterable" is LinkedList

      for (int i = 0; i < bucket.length; i++)
      {
         Entry e;

         e = bucket[i];
         if ((e != null) && (e != AVAILABLE))
         {  // e is a valid entry

            r.add( e.key );
         }
      }

      return(r);
   }


  public String toString()
  {
      String output = "{";

      for (int i = 0; i < bucket.length; i++)
      {
         Entry e;

         e = bucket[i];
         if ((e != null) && (e != AVAILABLE))
         {  // e is a valid entry

            output += "(" + e.getKey() + "," + e.getValue() + ") ";
         }
      }
      output += "}";
      return output;
   }

}
