
/*---------------------------------------------------------------------------
 * 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 java.lang.InterruptedException;
import com.dalsemi.onewire.*;
import com.dalsemi.onewire.container.*;
import com.dalsemi.onewire.adapter.*;
import com.dalsemi.onewire.utils.*;
import java.util.*;


/**
 * 1-Wire Complex Network Monitor.  Currently only supports scanning
 * networks utilizing the DS2409 1-Wire Coupler.
 *
 * @version    0.00, 18 September 2000
 * @author     DS
 */
public class NetworkMonitor
   extends Thread
{

   //--------
   //-------- Variables 
   //--------

   /** Flag for overall thread running state */
   private boolean keepRunning = true;

   /** Flag to indicate thread has begin to run */
   private boolean startRunning = true;

   /** Flag to indicate thread is running now */
   private boolean isRunning = false;

   /** Adapter where device is */
   private DSPortAdapter adapter;

   /** Paths to search */
   private Vector paths;

   /** Devices to track */
   private Hashtable list;

   /** Listeners to notify */
   private Vector listeners;

   //--------
   //-------- Constructors
   //--------
   private NetworkMonitor ()
   {
   }

   /**
    * Create a new Network monitor on the provided
    * adapter port.
    *
    * @param  adapter where the path is based
    */
   public NetworkMonitor (DSPortAdapter adapter)
   {
      this.adapter = adapter;
      paths        = new Vector(1, 2);

      paths.addElement(new OWPath(adapter));

      list      = new Hashtable();
      listeners = new Vector(3);
   }

   /**
    * Add a network event listener
    *
    * @param  owmel network event listener
    */
   public void addEventListener (NetworkMonitorEventListener owmel)
      throws TooManyListenersException
   {
      if (owmel == null)
         throw new IllegalArgumentException();

      listeners.addElement(owmel);
   }

   /**
    * Remove a network event listener
    *
    * @param  owmel network event listener
    */
   public void removeEventListener (NetworkMonitorEventListener owmel)
   {
      listeners.removeElement(owmel);
   }

   /**
    * Pause this network monitor
    */
   public void pauseMonitor ()
   {
      if (!keepRunning)
         return;

      // clear the start flag
      synchronized (this)
      {
         startRunning = false;
      }

      // wait until it is paused
      while (isRunning)
      {
         msSleep(10);
      }
   }

   /**
    * Resume this network monitor
    */
   public void resumeMonitor ()
   {
      if (!keepRunning)
         return;

      // set the start flag
      synchronized (this)
      {
         startRunning = true;
      }

      // wait until it is running
      while (!isRunning)
      {
         msSleep(10);
      }
   }

   /**
    * Check to see if network monitor is running
    *
    * @return true if monitor is running
    */
   public boolean isMonitorRunning ()
   {
      return (keepRunning && startRunning && isRunning);
   }

   /**
    * Kill this network monitor
    */
   public void killMonitor ()
   {

      // clear the running flags
      synchronized (this)
      {
         keepRunning  = false;
         startRunning = false;
      }

      // wait until the thread is no longer running
      while (isAlive())
      {
         msSleep(20);
      }
   }

   /**
    * Network monitor work
    */
   public void run ()
   {
      int                p_num;
      OWPath             path;
      Long               address;
      NetworkDeviceState state;

      while (keepRunning)
      {
         if (startRunning)
         {

            // set is now running
            synchronized (this)
            {
               isRunning = true;
            }

            try
            {

               // aquire the adapter
               adapter.beginExclusive(true);

               // search through all of the paths
               for (p_num = 0; p_num < paths.size(); p_num++)
               {
                  search(( OWPath ) paths.elementAt(p_num));
               }
            }
            catch (InterruptedException e)
            {

               // DRAIN
               continue;
            }
            catch (OneWireIOException e)
            {

               // notify all listeners
               notifyOfException(e);
            }
            catch (OneWireException e)
            {

               // notify all listeners
               notifyOfException(e);
            }
            finally
            {
               adapter.endExclusive();
            }

            // remove any devices that have not been seen
            for (Enumeration list_enum = list.keys();
                    list_enum.hasMoreElements(); )
            {
               address = ( Long ) list_enum.nextElement();
               state   = ( NetworkDeviceState ) list.get(address);

               // check for removal
               if (state.getCount() <= 1)
               {

                  // if remove is a 2409 then remove from paths vector
                  // NOT DONE
                  // device gone so remove
                  list.remove(address);

                  // send remove event
                  notifyOfDeparture(adapter, address.longValue(),
                                    state.getOWPath());
               }
               else
               {

                  // debit count of all devices not removed
                  state.setCount(state.getCount() - 1);
               }
            }

            // sleep to give other threads a chance at this network
            msSleep(100);
         }
         else
         {

            // not running so clear flag
            synchronized (this)
            {
               isRunning = false;
            }

            msSleep(100);
         }
      }

      // not running so clear flag
      synchronized (this)
      {
         isRunning = false;
      }
   }

   //--------
   //-------- Private methods
   //--------

   /**
    * Close all paths, DS2409 specific
    */
   private void closeAllPaths ()
      throws OneWireException, OneWireIOException
   {

      // DS2409 specific - skip, all lines off
      adapter.reset();
      adapter.putByte(0x00CC);
      adapter.putByte(0x0066);
      adapter.getByte();
   }

   /**
    * Sleep for the specified number of milliseconds
    */
   private void msSleep (long msTime)
   {
      try
      {
         Thread.sleep(msTime);
      }
      catch (InterruptedException e){}
   }

   /**
    * Notify the listeners of the arrival event
    */
   private void notifyOfArrival (DSPortAdapter adapter, long address,
                                 OWPath path)
   {
      for (int i = 0; i < listeners.size(); i++)
      {
         (( NetworkMonitorEventListener ) listeners.elementAt(
            i)).networkArrival(
               new NetworkMonitorEvent(this, adapter, address, path));
      }
   }

   /**
    * Notify the listeners of the departure event
    */
   private void notifyOfDeparture (DSPortAdapter adapter, long address,
                                   OWPath path)
   {
      for (int i = 0; i < listeners.size(); i++)
      {
         (( NetworkMonitorEventListener ) listeners.elementAt(
            i)).networkDeparture(
               new NetworkMonitorEvent(this, adapter, address, path));
      }
   }

   /**
    * Notify the listeners of the exception
    */
   private void notifyOfException (Exception ex)
   {
      for (int i = 0; i < listeners.size(); i++)
      {
         (( NetworkMonitorEventListener ) listeners.elementAt(
            i)).networkException(ex);
      }
   }

   /**
    * Search the specified path for new devices
    */
   private void search (OWPath path)
      throws InterruptedException, OneWireException, OneWireIOException
   {
      OneWireContainer   owc;
      Long               address;
      OWPath             last_path;
      NetworkDeviceState state;
      byte[]             address_bytes;

      // close any opened branches
      closeAllPaths();

      // set searches to not use reset
      adapter.setNoResetSearch();

      // find the first device on this branch
      path.open();

      boolean search_result = adapter.findFirstDevice();

      // loop while devices found 
      while (search_result)
      {

         // check if still running
         if (!startRunning)
            throw new InterruptedException("search interrupted");

         // check if already in hash
         address = new Long(adapter.getAddressAsLong());
         state   = ( NetworkDeviceState ) list.get(address);

         if (state == null)
         {

            // new entry so add to hash
            list.put(address, (new NetworkDeviceState(adapter, path)));

            // send add event
            notifyOfArrival(adapter, address.longValue(), path);

            // check to see if this is a smart switch
            // ************************************************
            // Could create a container and check for a smart
            // but since DS2409 is the only one currently then
            // this short cut is in here.  This saves creating 
            // a container for every device.
            // ************************************************
            address_bytes = Address.toByteArray(address.longValue());

            if (( byte ) address_bytes [0] == ( byte ) 0x1F)
            {

               // create a container for this switch
               owc = adapter.getDeviceContainer();

               // create a new path FOR MAIN
               last_path = new OWPath(adapter, path);

               last_path.add(owc, 0);

               // add this new path to the paths vector
               paths.addElement(last_path);

               // create a new path FOR AUX
               last_path = new OWPath(adapter, path);

               last_path.add(owc, 1);

               // add this new path to the paths vector
               paths.addElement(last_path);
            }
         }
         else
         {

            // it is present so get the last path
            last_path = state.getOWPath();

            // check if device moved
            if (!path.equals(last_path))
            {

               // path changed so update
               list.remove(address);

               // send remove event
               notifyOfDeparture(adapter, address.longValue(), last_path);

               // add new version to hash
               list.put(address, (new NetworkDeviceState(adapter, path)));

               // send add event
               notifyOfArrival(adapter, address.longValue(), path);
            }
            else
            {

               // device did not move so update count
               state.setCount(NetworkDeviceState.MAX_COUNT);
            }
         }

         // find the next device on this branch
         path.open();

         search_result = adapter.findNextDevice();
      }

      closeAllPaths();
   }
}
