
/*---------------------------------------------------------------------------
 * Copyright (C) 1999,2000 Dallas Semiconductor Corporation, All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Dallas Semiconductor
 * shall not be used except as stated in the Dallas Semiconductor
 * Branding Policy.
 *---------------------------------------------------------------------------
 */

package com.dalsemi.onewire.utils;

import com.dalsemi.onewire.*;
import com.dalsemi.onewire.adapter.*;
import java.util.*;


/**
 * This class allows an application to be notified of arriving
 * and departing 1-Wire devices on a 1-Wire network.
 */
public class OneWireMonitor
   extends Thread
{
   private boolean       keepRunning = true;
   private Vector        listeners   = new Vector(3);
   private DSPortAdapter adapter;

   // The initial hash size MUST be a power of 2 for this to work.
   private static final int INITIAL_HASH_SIZE = 8;

   // The max hash size MUST be a power of 2 for this to work.
   private static final int MAX_HASH_SIZE = 256;

   // Total number of elements in the hashtable.
   private int hashElementCount;

   // The hashtable array.
   private long[] addressHash;

   // This stores the mask used to index into the hash table
   // Always equals (addressHash.length-1)
   private int HASH_MASK;

   // Number of elements in the array to linearly search after
   // a collision before giving up.
   private int HASH_MAX_COLLISION_SKIP;

   // Enumeration variables.
   private int enumIndex = 0;
   private int enumCount = 0;

   private OneWireMonitor ()
   {
   }

   /**
    * Create a 1-Wire monitoring object attached to the specified
    * adapter.
    *
    * @param adapter 1-Wire adapter the monitor should use to scan
    *        for arrivals and departures of 1-Wire devices.
    */
   public OneWireMonitor (DSPortAdapter adapter)
   {
      this.adapter            = adapter;
      hashElementCount        = 0;
      addressHash             = new long [INITIAL_HASH_SIZE];
      HASH_MASK               = addressHash.length - 1;
      HASH_MAX_COLLISION_SKIP = addressHash.length / 2;
   }

   /**
    *
    */
   public void killMonitor()
   {
     keepRunning = false;
   }

   /**
    * Add an event listener that should be notified in case of 1-Wire
    * event.
    *
    * @param owmel Event listener to add
    */
   public void addEventListener (OneWireMonitorEventListener owmel)
      throws TooManyListenersException
   {
      if (owmel == null)
         throw new IllegalArgumentException();

      listeners.addElement(owmel);
   }

   /**
    * Remove an event listener.
    * NOTE: If all event listeners are removed, 1-Wire network scanning
    * will cease until
    *
    * @param owmel Event listener to remove
    */
   public void removeEventListener (OneWireMonitorEventListener owmel)
   {
      listeners.removeElement(owmel);
   }

   /**
    * Loop through all listeners and notify them of a 1-Wire device arriving.
    */
   private void notifyOfArrival (DSPortAdapter adapter, long address)
   {
      for (int i = 0; i < listeners.size(); i++)
      {
         (( OneWireMonitorEventListener ) listeners.elementAt(
            i)).oneWireArrival(
               new OneWireMonitorEvent(this, adapter, address));
      }
   }

   /**
    * Loop through all listeners and notify them of a 1-Wire device departing.
    */
   private void notifyOfDeparture (DSPortAdapter adapter, long address)
   {
      for (int i = 0; i < listeners.size(); i++)
      {
         (( OneWireMonitorEventListener ) listeners.elementAt(
            i)).oneWireDeparture(
               new OneWireMonitorEvent(this, adapter, address));
      }
   }

   /**
    * Start up the show.
    */
   public void run ()
   {
      while (keepRunning)
      {

         // If we have no listeners, sleep so we don't burn CPU.
         if (listeners.size() == 0)
         {
            try
            {
               Thread.sleep(500);
            }
            catch (InterruptedException e)
            {

               // DRAIN
            }
         }
         else
         {

            // Do polling here.
            pollDevices();
            // Give some CPU to other threads.  Could be nicer in the future.
            try {Thread.sleep(100);} catch (InterruptedException e) {}
         }
      }
   }

   /**
    * 1-Wire Address hashtable get function
    *
    * @return address if address is found in hashtable, or zero if
    *         address is not found.
    */
   private long get (long address)
   {
      int index = ( int ) ((address >>> 56) & HASH_MASK);

      for (int i = 0; i < HASH_MAX_COLLISION_SKIP; i++)
      {
         if (addressHash [index & HASH_MASK] == address)
            return address;

         index++;
      }

      return 0;
   }

   /**
    * 1-Wire Address hashtable put function
    */
   private void put (long address)
   {
      int index = ( int ) ((address >>> 56) & HASH_MASK);

      for (int i = 0; i < HASH_MAX_COLLISION_SKIP; i++)
      {
         if (addressHash [index & HASH_MASK] == 0)
         {
            addressHash [index & HASH_MASK] = address;

            hashElementCount++;

            return;
         }

         index++;
      }

      // If we get here, we have a collision that can not be avoided.
      // Make a new hashtable twice the size of the current.
      int newLength = addressHash.length << 1;

      // Make sure we don't go over our maximum size.
      if (newLength > MAX_HASH_SIZE)
         throw new IllegalArgumentException();

      // Create and initialize the new hashtable.
      long[] oldAddressHash = addressHash;

      addressHash             = new long [newLength];
      hashElementCount        = 0;
      HASH_MASK               = addressHash.length - 1;
      HASH_MAX_COLLISION_SKIP = addressHash.length / 2;

      // Put the current address into the table.
      put(address);

      // Put all the old table addresses into the new table.
      for (int i = 0; i < oldAddressHash.length; i++)
      {
         if (oldAddressHash [i] != 0)
         {
            put(oldAddressHash [i]);
         }
      }
   }

   /**
    * 1-Wire Address hashtable remove function
    */
   private void remove (long address)
   {
      int index = ( int ) ((address >>> 56) & HASH_MASK);

      for (int i = 0; i < HASH_MAX_COLLISION_SKIP; i++)
      {
         if (addressHash [index & HASH_MASK] == address)
         {
            addressHash [index & HASH_MASK] = 0;

            hashElementCount--;

            return;
         }

         index++;
      }

      // If we get here, bad things have happened to our hashtable
   }

   /**
    * Return first 1-Wire address in the hashtable.
    *
    * @return zero for no address found.
    */
   private long first ()
   {
      enumIndex = 0;
      enumCount = hashElementCount;

      return next();
   }

   /**
    * Return next 1-Wire address in the hashtable.
    *
    * @return zero for no address found.
    */
   private long next ()
   {
      if (enumCount == 0)
         return 0;

      for (; enumIndex < addressHash.length; enumIndex++)
      {
         if (addressHash [enumIndex] != 0)
         {
            enumCount--;

            // Note the post increment on enumIndex!
            // We must increment the index after we grab the correct value.
            return addressHash [enumIndex++];
         }
      }

      return 0;
   }

   /**
    * Scan the bus for 1-Wire device arrival/departure.
    */
   private void pollDevices ()
   {
      try
      {
         adapter.beginExclusive(true);

         // First find all devices on the 1-wire.
         if (adapter.findFirstDevice())
         {
            do
            {
               long address = adapter.getAddressAsLong();

               if (get(address) == 0)
               {
                  put(address);
                  notifyOfArrival(this.adapter, address);
               }
            }
            while (adapter.findNextDevice());
         }

         // Next, find all the devices that have dropped off the 1-wire.
         long address;

         if ((address = first()) != 0)
         {
            do
            {
               if (!adapter.isPresent(address))
               {
                  remove(address);
                  notifyOfDeparture(this.adapter, address);
               }
            }
            while ((address = next()) != 0);
         }
      }
      catch (OneWireException e){}
      finally
      {
         adapter.endExclusive();
      }
   }
}
