/* Network-Integrated Multimedia Middleware (NMM)
 * http://www.motama.com/nmm.html
 *  
 * Copyright (C) 2006-2007
 * Motama GmbH, Saarbruecken, Germany
 * http://www.motama.com
 *  
 * This file is part of NMM. 
 * 
 * NMM is free software; you can redistribute it and/or modify it    
 * under the terms of the GNU General Public License as published by 
 * the Free Software Foundation; only version 2 of the License.      
 * 
 * NMM 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. See the GNU General Public 
 * License for more details.  
 * 
 * You should have received a copy of the GNU General Public License 
 * along with NMM; if not, write to the Free Software Foundation,    
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#ifndef NMM_DEVICE_PLUGIN_HPP
#define NMM_DEVICE_PLUGIN_HPP NMM_DEVICE_PLUGIN_HPP

#include "nmm/nmm_prefix.hpp"

#include "TPlugin.hpp"

#include "IDevice.hpp"

namespace NMM {

  /** A plug-in which represents a device.
   * This is a template class for plug-ins that represent a device. It is used to implement device detection and management.
   * The TDevicePlugin class creates a dummy node and attempts to call IDevice::setDevice for a set of device names.
   * The IDevice::setDevice event is added to the configuration of the node.
   * An instance of the node is registered for each device that was found, with a maximum instance count of 1.
   *
   * This class implements a certain strategy for detecting devices. Subclasses for specific plug-ins only need to
   * implement some protected methods to perform the actual device probing. The strategy is as follows:
   *
   * The set of device names is the set of all filenames of the form @a device_name + @a i where @a i
   * is a positive number, possibly including zero. The set may also include the standard device name
   * which is simply @c device_name. For example, the set of devices may be "/dev/dsp", "/dev/dsp1",
   * "/dev/dsp2", ... . If the standard device is checked, then @a i starts with 1, otherwise @a i starts
   * with zero. If device @a n is not found, no check is done for any device with a higher number.
   * The standard device is always checked first. If it is not available, no checks for other devices
   * are made.
   *
   * @see IDevice
   */
  template<typename NODE>
  class TDevicePlugin
    : public TPlugin<NODE>
  {
  public:
    /** Create a device plug-in.
     *
     * @param device_name The device base name, e.g. "/dev/dsp"
     * @param check_standard_device Check for the standard device
     * @param node_name The name of the node
     * @param max_instances Maximum number of instances per device. A value of -1 means no limitation.
     */
    TDevicePlugin(const string& device_name, bool check_standard_device, const string& node_name, int max_instances = 1);

    /** Virtual destructor.
     */
    virtual ~TDevicePlugin();

    /** Plug-in registration.
     * This method is called by the PluginManager when the plug-in is registered.
     * It can be implemented by subclasses to perform specific actions required for a particular plug-in.
     *
     * This implementation checks for devices using the default strategy for device detection.
     *
     * @exception SerializedException if an error occurred
     * @exception Exception if an error occurred
     */
    virtual void nmmRegistryInit()
      throw(const Exception&, const SerializedException&);
    
  protected:
    /** Check for standard auxiliary device(s)
     * This method can be reimplemented to check for auxiliary devices (such as a mixer device for
     * a soundcard). Auxiliary devices are optional, so this method must not fail.
     * The default implementation does nothing.
     *
     * The standard auxiliary device is the auxiliary device associated with the standard main device
     * (for example /dev/mixer for /dev/dsp).
     *
     * @param i_device Interface for setting the device
     */
    virtual void checkForStandardAuxDevice(IDevice& i_device);
    
    /** Check for auxiliary device(s).
     * This method can be reimplemented to check for auxiliary devices (such as a mixer device for
     * a soundcard). Auxiliary devices are optional, so this method must not fail.
     * The default implementation does nothing.
     *
     * The device number @c device_num is the number of the main device. This method checks
     * for auxiliary devices associated with this main device (for example /dev/mixer1 for /dev/dsp1).
     *
     * @param i_device Interface for setting the device
     * @param device_num Device number
     */
    virtual void checkForAuxDevice(IDevice& i_device, unsigned device_num);
    
  private:
    string __device_name;             ///< Base device name
    bool __check_standard_device;     ///< Check the standard device
  };
  
  template<typename NODE>
  TDevicePlugin<NODE>::TDevicePlugin(const string& device_name, bool check_standard_device, const string& node_name, int max_instance /*= -1*/)
    : TPlugin<NODE>(node_name, max_instance),
      __device_name(device_name),
      __check_standard_device(check_standard_device)
  {}
  
  template<typename NODE>
  TDevicePlugin<NODE>::~TDevicePlugin() 
  {}
  
  template<typename NODE>
  void TDevicePlugin<NODE>::nmmRegistryInit()
    throw(const Exception&, const SerializedException&)
  {
    /* we create a NODE and get the INode interface we need */
    NODE node;
    
    /* prepare the INode for registration*/
    INode_var i_node(prepareRegistration(node));
    
    /* we get the IDevice interface to specify the node capabilities on the system */
    IDevice_var i_device;
    i_device.reset(node.template getCheckedInterface<IDevice>());
    
    /* first we try to set the standard device using the IDevice interface */
    /* If the standard device is not available, we assume that no other devices are available */
    if(__check_standard_device) {
      GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << ">: Checking for standard device " << __device_name << std::endl);
      try {
        i_device->setDevice(__device_name);
	
	/* check for standard auxiliary device */
	checkForStandardAuxDevice(*i_device);
	
	/* if the node is configured, then we create the NodeDescription */
	NodeDescription description;
	this->createNodeDescription(*i_node.get(), description);
	
	/* we register the node */
	registerNode(description, this->getMaxInstanceCount());
      }
      catch (const DeviceNotFoundException& e) {
        /* The standard device does not exist or can not be accessed.
           Assume that no devices are available at all. */
        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> " << e << endl);
        throw Exception("No devices available");
      }
      catch (const WrongDeviceException& e) {
        /* The device exists but can not be used. The next device may be usable, though. */
        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> " << e << endl);
      }
      catch (const SerializedException &e){
        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> " << e << endl);
      }
      catch (const Exception &e){
        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> " << e << endl);
      }
    }
    
    /* if we have found a default device we try to scan for other devices in the same manner*/
    unsigned i = __check_standard_device ? 1 : 0;
    while ( 1 ) {
      GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> Checking for device " << __device_name << i << std::endl);
      
      /* we assume that the next devices are __device_name + 1, __device_name + 2, ... */
      string next_device_name = __device_name + i;
      
      try {
        i_device->setDevice(next_device_name);
	
	/* check for auxiliary device */
	checkForAuxDevice(*i_device, i);
	
	/* if the node is configured we create the NodeDescription */
	NodeDescription description;
	this->createNodeDescription(*i_node.get(), description);
	
	/* we register the node */
	registerNode(description, this->getMaxInstanceCount());
      }
      catch (const DeviceNotFoundException& e) {
        /* The device does not exist or can not be accessed. Stop checking for more devices. */
        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> " << e << endl);

        /* Check if at least one device is available */
        if(this->getNodeDescriptionList().size() == 0) {
          throw Exception("No devices available");
        }

        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> Stop checking for devices" << endl);
        return;
      }
      catch (const WrongDeviceException& e) {
        /* The device exists but can not be used. The next device may be usable, though. */
        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> " << e << endl);
      }
      catch (const SerializedException &e){
        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> " << e << endl);
      }
      catch (const Exception &e){
        GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> " << e << endl);
      }

      GLOBAL_DEBUG_STREAM("DevicePlugin<" << this->getNodeName() << "> Checking next device" << endl);
      ++i;
    }
    /*End< */
  }
  
  template<typename NODE>
  void TDevicePlugin<NODE>::checkForStandardAuxDevice(IDevice&)
  {}
  
  template<typename NODE>
  void TDevicePlugin<NODE>::checkForAuxDevice(IDevice&, unsigned)
  {}
  
}

#include "nmm/nmm_suffix.hpp"

#endif
