namespace mimas {

template< typename T >
image_dc1394input< T >::image_dc1394input( const char *device,
                                           int node,
                                           int channel,
                                           int preferredFormat,
                                           int preferredMode,
                                           int preferredFormat7Colour,
                                           int speed )
  throw (mimasexception):
  m_device(device), m_cameraNode(NULL), m_node(-1), m_handle(NULL),
  m_format(-1), m_mode(-1), m_format7Colour(-1),
  m_colourSpace(Unknown), m_frameRate(-1), m_bufferUsed( false )
{
  try {
    m_handle = dc1394_create_handle( 0 );
    MMERROR( m_handle != NULL, mimasexception, ,
             "Unable to acquire raw1394 handle. Please check, wether "
             "the kernel modules 'ieee1394','raw1394' and 'ohci1394' "
             "are loaded and wether you have read/write permission on "
             "\"/dev/raw1394\"." );

    int numNodes = raw1394_get_nodecount(m_handle);
    int numCameras;
#ifndef NDEBUG
    m_cameraNode =
      dc1394_get_camera_nodes( m_handle, &numCameras, 1 );
#else
    m_cameraNode =
      dc1394_get_camera_nodes( m_handle, &numCameras, 0 );
#endif
    MMERROR( numCameras >= 0, mimasexception, ,
             "Failed to acquire list of digital camera nodes." );
    MMERROR( numCameras >= 1, mimasexception, , "Did not find a single digital "
             "camera on the firewire bus." );

    // Check for iso-transfer bug of old raw1394 system
    // (before kernel 2.4.21-pre2).
    // Compare http://sourceforge.net/tracker/index.php?func=detail&aid=435107&group_id=8157&atid=108157
    for ( int i=0; i<numCameras; i++ ) {
      MMERROR( m_cameraNode[i] != numNodes - 1, mimasexception, ,
               "A digital camera has become the root-node of the "
               "firewire bus. Try to fix this problem by reloading the "
               "'ohci1394'-module with option 'attempt_root=1'." );
    };

    MMERROR( node >= 0 && node < numCameras, mimasexception, ,
             "Camera-node number " << node << " out of range. The "
             "range is [ 0; " << numCameras << " )." );

    MMERROR( dc1394_camera_on( m_handle, node ), mimasexception, ,
             "Could not switch on camera" );
    m_node = node;
    selectFormat( preferredFormat );
    selectMode( preferredMode );

    if ( m_mode >= MODE_FORMAT7_MIN && m_mode <= MODE_FORMAT7_MAX ) {
      selectFormat7Colour( preferredFormat7Colour );
      // Setup DMA capture (allocates ringbuffer).
      MMERROR( dc1394_dma_setup_format7_capture
               ( m_handle, m_cameraNode[ m_node ], channel,
                 m_mode, speed, QUERY_FROM_CAMERA,
                 (unsigned int)QUERY_FROM_CAMERA,
                 (unsigned int)QUERY_FROM_CAMERA,
                 (unsigned int)QUERY_FROM_CAMERA,
                 (unsigned int)QUERY_FROM_CAMERA,
                 4,// Number of dma-buffers.
                 1,// Drop frames.
                 m_device.empty() ? (const char *)NULL : device,
                 &m_camera ) == DC1394_SUCCESS,
               mimasexception, ,
               "Error setting up format7 DMA capture. Please check, wether "
               "the kernel modules 'video1394' is loaded and wether "
               "you have read/write permission on \"/dev/video1394/*\"" );
    } else {
      selectFrameRate( -1 );
      // Setup DMA capture (allocates ringbuffer).
      MMERROR( dc1394_dma_setup_capture
               ( m_handle, m_cameraNode[ m_node ], channel,
                 m_format, m_mode, speed, m_frameRate,
                 4,// Number of dma-buffers.
#ifdef LIBDC1394_OLD
                 0,// Extra-buffering (?)
#endif
                 1,// Drop frames.
                 m_device.empty() ? (const char *)NULL : device,
                 &m_camera ) == DC1394_SUCCESS,
               mimasexception, ,
               "Error setting up DMA capture. Please check, wether "
               "the kernel modules 'video1394' is loaded and wether "
               "you have read/write permission on \"/dev/video1394/*\"" );
    };
        
    // Start capture.
    MMERROR( dc1394_start_iso_transmission( m_handle, m_camera.node )
             == DC1394_SUCCESS, mimasexception, ,
             "Error starting camera iso transmission." );
        
  } catch ( mimasexception &e ) {
      
    if ( m_node != -1 ) dc1394_camera_off( m_handle, m_node );  
    if ( m_cameraNode != NULL ) dc1394_free_camera_nodes( m_cameraNode );
    if ( m_handle != NULL ) dc1394_destroy_handle( m_handle );
    throw e;
        
  };
}

template< typename T >
image_dc1394input< T >::~image_dc1394input(void)
{
  if ( m_handle != NULL ) close();
}

template< typename T >
void image_dc1394input< T >::close(void)
{
  if ( m_bufferUsed ) {
    dc1394_dma_done_with_buffer( &m_camera );
    m_bufferUsed = false;
  };
  if ( m_handle != NULL ) {
    // Halt iso reception.
    dc1394_dma_unlisten( m_handle, &m_camera );
    // Stop iso transmission.
    dc1394_stop_iso_transmission( m_handle, m_camera.node );
    // Free mapped memory.
    dc1394_dma_release_camera( m_handle, &m_camera );
    // Free list of nodes.
    dc1394_free_camera_nodes( m_cameraNode ); m_cameraNode = NULL;
    // Switch off camera.
    dc1394_camera_off( m_handle, m_node );
    // Free handle.
    dc1394_destroy_handle( m_handle ); m_handle = NULL;
  };
}

template< typename T >
dc1394_feature_info image_dc1394input< T >::get_feature( int _id )
  throw (mimasexception)
{
  dc1394_feature_info retval; retval.feature_id = _id;
  MMERROR( dc1394_get_camera_feature( m_handle, m_node, &retval ) != 0,
           mimasexception, ,
           "Error requesting feature " << _id << " from firewire "
           "digital camera." );
  return retval;
}

template< typename T >
unsigned int image_dc1394input< T >::get_feature_value( int id )
  throw (mimasexception)
{
  MMERROR( m_handle != NULL, mimasexception, ,
           "Camera device was closed already." );
  unsigned int retval;
  MMERROR( dc1394_get_feature_value( m_handle, m_node, id, &retval ) != 0,
           mimasexception, ,
           "Error requesting value of feature " << id
           << " from firewire digital camera." );
  return retval;
}

template< typename T >
void image_dc1394input< T >::set_feature_value( int id,
                                                unsigned int value )
  throw (mimasexception)
{
  MMERROR( m_handle != NULL, mimasexception, ,
           "Camera device was closed already." );
  MMERROR( dc1394_set_feature_value( m_handle, m_node, id, value ) != 0,
           mimasexception, ,
           "Error setting feature " << id << " from firewire "
           "digital camera." );
}

template< typename T >
void image_dc1394input< T >::selectFormat( int preferredFormat ) throw (mimasexception)
{
  typedef struct {
    int format;
    const char *name;
  } FormatEntry;
  FormatEntry format[] = {
    { FORMAT_SCALABLE_IMAGE_SIZE , "FORMAT_SCALABLE_IMAGE_SIZE"  },
    { FORMAT_SVGA_NONCOMPRESSED_2, "FORMAT_SVGA_NONCOMPRESSED_2" },
    { FORMAT_SVGA_NONCOMPRESSED_1, "FORMAT_SVGA_NONCOMPRESSED_1" },
    { FORMAT_VGA_NONCOMPRESSED   , "FORMAT_VGA_NONCOMPRESSED"    },
    { FORMAT_STILL_IMAGE         , "FORMAT_STILL_IMAGE"          }
  };
  const char *namePreferred = "invalid format number";

  // Get list of supported formats.
  quadlet_t formats = 0;
  MMERROR( dc1394_query_supported_formats
           ( m_handle, m_cameraNode[ m_node ], &formats )
           == DC1394_SUCCESS, mimasexception, ,
           "Error requesting supported video formats." );
      
  int selected = 0;
  std::ostringstream s;

  while ( true ) {

    if ( format[ selected ].format == preferredFormat )
      namePreferred = format[ selected ].name;

    // Check wether format is supported.
    if ( ( formats & ( 1U << ( 31 + FORMAT_MIN -
                               format[ selected ].format ) ) ) != 0 ) {
      if ( preferredFormat == format[ selected ].format ||
           preferredFormat == -1 ) {
        m_format = format[ selected ].format;
        break;
      };
      s << " " << format[ selected ].name;
    };
    selected++;
    if ( selected >= (signed)(sizeof(format)/sizeof(FormatEntry)) ) {
      MMERROR( preferredFormat == -1, mimasexception, ,
               "Preferred format \"" << namePreferred
               <<  "\" not supported by camera (supported:"
               << s.str() << ")." );
      MMERROR( false, mimasexception, ,
               "Camera-driver doesn't offer a known video-format." );
    };

  };
  assert( m_format != -1 );
}

template< typename T >
void image_dc1394input< T >::selectMode( int preferredMode ) throw (mimasexception)
{
  typedef struct {
    ColourSpace colourSpace;
    int mode;
    const char *name;
  } ModeEntry;
  ModeEntry mode[] = {
    { UYVY   , MODE_640x480_YUV422  , "MODE_640x480_YUV422"   },
    { UYVY   , MODE_320x240_YUV422  , "MODE_320x200_YUV422"   },
    { Grey8  , MODE_640x480_MONO    , "MODE_640x480_MONO"     },
    { RGB24  , MODE_640x480_RGB     , "MODE_640x480_RGB"      },
    { Unknown, MODE_640x480_YUV411  , "MODE_640x480_YUV411"   },
    { Unknown, MODE_640x480_MONO16  , "MODE_640x480_MONO16"   },
    { Unknown, MODE_160x120_YUV444  , "MODE_160x120_YUV444"   },

    { UYVY   , MODE_1024x768_YUV422 , "MODE_1024x768_YUV422"  },
    { Grey8  , MODE_1024x768_MONO   , "MODE_1024x768_MONO"    },
    { RGB24  , MODE_1024x768_RGB    , "MODE_1024x768_RGB"     },
    { UYVY   , MODE_800x600_YUV422  , "MODE_800x600_YUV422"   },
    { Grey8  , MODE_800x600_MONO    , "MODE_800x600_MONO"     },
    { RGB24  , MODE_800x600_RGB     , "MODE_800x600_RGB"      },
    { Unknown, MODE_1024x768_MONO16 , "MODE_1024x768_MONO16"  },
    { Unknown, MODE_800x600_MONO16  , "MODE_800x600_MONO16"   },

    { UYVY   , MODE_1600x1200_YUV422, "MODE_1600x1200_YUV422" },
    { Grey8  , MODE_1600x1200_MONO  , "MODE_1600x1200_MONO"   },
    { RGB24  , MODE_1600x1200_RGB   , "MODE_1600x1200_RGB"    },
    { UYVY   , MODE_1280x960_YUV422 , "MODE_1280x960_YUV422"  },
    { Grey8  , MODE_1280x960_MONO   , "MODE_1280x960_MONO"    },
    { RGB24  , MODE_1280x960_RGB    , "MODE_1280x960_RGB"     },
    { Unknown, MODE_1600x1200_MONO16, "MODE_1600x1200_MONO16" },
    { Unknown, MODE_1280x960_MONO16 , "MODE_1280x960_MONO16"  },

    { Unknown, MODE_EXIF            , "MODE_EXIF"             },

    { Unknown, MODE_FORMAT7_7       , "MODE_FORMAT7_7"        },
    { Unknown, MODE_FORMAT7_6       , "MODE_FORMAT7_6"        },
    { Unknown, MODE_FORMAT7_5       , "MODE_FORMAT7_5"        },
    { Unknown, MODE_FORMAT7_4       , "MODE_FORMAT7_4"        },
    { Unknown, MODE_FORMAT7_3       , "MODE_FORMAT7_3"        },
    { Unknown, MODE_FORMAT7_2       , "MODE_FORMAT7_2"        },
    { Unknown, MODE_FORMAT7_1       , "MODE_FORMAT7_1"        },
    { Unknown, MODE_FORMAT7_0       , "MODE_FORMAT7_0"        }
  };
  const char *namePreferred = "invalid mode number";

  // Get list of supported video modes.
  quadlet_t videoModes = 0;
  MMERROR( dc1394_query_supported_modes
           ( m_handle, m_cameraNode[ m_node ], m_format,
             &videoModes )
           == DC1394_SUCCESS, mimasexception, ,
           "Error requesting supported video modes." );
  int
    modesMin = 0,
    modesMax = -1;
  switch ( m_format ) {
  case FORMAT_VGA_NONCOMPRESSED:
    modesMin = MODE_FORMAT0_MIN;
    modesMax = MODE_FORMAT0_MAX;
    break;
  case FORMAT_SVGA_NONCOMPRESSED_1:
    modesMin = MODE_FORMAT1_MIN;
    modesMax = MODE_FORMAT1_MAX;
    break;
  case FORMAT_SVGA_NONCOMPRESSED_2:
    modesMin = MODE_FORMAT2_MIN;
    modesMax = MODE_FORMAT2_MAX;
    break;
  case FORMAT_STILL_IMAGE:
    modesMin = MODE_FORMAT6_MIN;
    modesMax = MODE_FORMAT6_MAX;
    break;
  case FORMAT_SCALABLE_IMAGE_SIZE:
    modesMin = MODE_FORMAT7_MIN;
    modesMax = MODE_FORMAT7_MAX;
    break;
  default:
    assert( false );
  };

  int selected = 0;
  std::ostringstream s;

  while ( true ) {

    if ( mode[ selected ].mode == preferredMode )
      namePreferred = mode[ selected ].name;

    if ( mode[ selected ].mode >= modesMin &&
         mode[ selected ].mode <= modesMax ) {
      if ( ( videoModes & ( 1U << ( 31 + modesMin -
                                    mode[ selected ].mode ) ) ) != 0 ) {
        if ( preferredMode == mode[ selected ].mode ||
             preferredMode == -1 ) {
          m_mode = mode[ selected ].mode;
          m_colourSpace = mode[ selected ].colourSpace;
          break;
        };
        s << " " << mode[ selected ].name;
      };
    };

    selected++;
    if ( selected >= (signed)(sizeof(mode)/sizeof(ModeEntry)) ) {
      MMERROR( preferredMode == -1 ||
               ( preferredMode >= modesMin &&
                 preferredMode <= modesMax ),
               mimasexception, ,
               "Mode \"" << namePreferred << "\" not allowed with "
               "current format (allowed and supported:"
               << s.str() << ")." );
      MMERROR( preferredMode == -1, mimasexception, ,
               "Preferred mode \"" << namePreferred
               <<  "\" not supported in this format (supported:"
               << s.str() << ")." );
      MMERROR( false, mimasexception, ,
               "The current video format doesn't offer a known mode." );
    };

  };
  assert( m_mode != -1 );
  MMERROR( m_colourSpace != Unknown ||
           m_format == FORMAT_SCALABLE_IMAGE_SIZE,
           mimasexception, , "Colourspace transformation for "
           << mode[ selected ].name
           << "-images not implemented." );
}
      
template< typename T >
void image_dc1394input< T >::selectFrameRate( int preferredFrameRate ) throw (mimasexception)
{
  typedef struct {
    int rate;
    const char *name;
  } RateEntry;
  RateEntry rate[] = {
    { FRAMERATE_240  , "FRAMERATE_240"   },
    { FRAMERATE_120  , "FRAMERATE_120"   },
    { FRAMERATE_60   , "FRAMERATE_60"    },
    { FRAMERATE_30   , "FRAMERATE_30"    },
    { FRAMERATE_15   , "FRAMERATE_15"    },
    { FRAMERATE_7_5  , "FRAMERATE_7_5"   },
    { FRAMERATE_3_75 , "FRAMERATE_3_75"  },
    { FRAMERATE_1_875, "FRAMERATE_1_875" }
  };
  const char *namePreferred = "invalid frame rate index";
  // Get list of possible framerates.
  quadlet_t frameRates = 0;
  MMERROR( dc1394_query_supported_framerates
           ( m_handle, m_cameraNode[ m_node ], m_format,
             m_mode, &frameRates )
           == DC1394_SUCCESS, mimasexception, ,
           "Error requesting supported framerates." );
    
  int selected = 0;
  std::ostringstream s;

  while ( true ) {

    if ( rate[ selected ].rate == preferredFrameRate )
      namePreferred = rate[ selected ].name;

    // Check wether frameRate is supported.
    if ( ( frameRates & ( 1U << ( 31 + FRAMERATE_MIN -
                                  rate[ selected ].rate ) ) ) != 0 ) {
      if ( preferredFrameRate == rate[ selected ].rate ||
           preferredFrameRate == -1 ) {
        m_frameRate = rate[ selected ].rate;
        break;
      };
      s << " " << rate[ selected ].name;
    };
    selected++;
    if ( selected >= (signed)(sizeof(rate)/sizeof(RateEntry)) ) {
      MMERROR( preferredFrameRate == -1, mimasexception, ,
               "Preferred frameRate \"" << namePreferred
               <<  "\" not supported in this mode (supported:"
               << s.str() << ")." );
      MMERROR( false, mimasexception, ,
               "Camera-driver doesn't offer a known frame-rate." );
    };

  };
  assert( m_frameRate != -1 );
  
}

template< typename T >
void image_dc1394input< T >::selectFormat7Colour( int preferredFormat7Colour )
  throw (mimasexception)
{
  assert( m_format == FORMAT_SCALABLE_IMAGE_SIZE );
  typedef struct {
    ColourSpace colourSpace;
    int colour;
    const char *name;
  } ColourEntry;
  ColourEntry colour[] = {
    { UYVY   , COLOR_FORMAT7_YUV422 , "COLOR_FORMAT7_YUV422"  },
    { Grey8  , COLOR_FORMAT7_MONO8  , "COLOR_FORMAT7_MONO8"   },
    { RGB24  , COLOR_FORMAT7_RGB8   , "COLOR_FORMAT7_RGB8"    },
    { Unknown, COLOR_FORMAT7_YUV411 , "COLOR_FORMAT7_YUV411"  },
    { Unknown, COLOR_FORMAT7_YUV444 , "COLOR_FORMAT7_YUV444"  },
    { Unknown, COLOR_FORMAT7_MONO16 , "COLOR_FORMAT7_MONO16"  },
    { Unknown, COLOR_FORMAT7_RGB16  , "COLOR_FORMAT7_RGB16"   },
    { Unknown, COLOR_FORMAT7_MONO16S, "COLOR_FORMAT7_MONO16S" },
    { Unknown, COLOR_FORMAT7_RGB16S , "COLOR_FORMAT7_RGB16S"  },
    { Unknown, COLOR_FORMAT7_RAW8   , "COLOR_FORMAT7_RAW8"    },
    { Unknown, COLOR_FORMAT7_RAW16  , "COLOR_FORMAT7_RAW16"   }
  };
  const char *namePreferred = "invalid format7 color index";
  // Get list of possible format7 colours.
  quadlet_t colours = 0;
  MMERROR( dc1394_query_format7_color_coding
           ( m_handle, m_cameraNode[ m_node ],
             m_mode, &colours ) == DC1394_SUCCESS, mimasexception, ,
           "Error requesting format7 colourspaces." );

  int selected = 0;
  std::ostringstream s;

  while ( true ) {
        
    if ( colour[ selected ].colour == preferredFormat7Colour )
      namePreferred = colour[ selected ].name;

    // Check wether colour is supported.
    if ( ( colours & ( 1U << ( 31 + COLOR_FORMAT7_MIN -
                               colour[ selected ].colour ) ) ) != 0 ) {
      if ( preferredFormat7Colour == colour[ selected ].colour || 
           preferredFormat7Colour == -1 ) {
        m_format7Colour = colour[ selected ].colour;
        m_colourSpace = colour[ selected ].colourSpace;
        break;
      };
      s << " " << colour[ selected ].name;
    };
    selected++;
    if ( selected >= (signed)(sizeof(colour)/sizeof(ColourEntry)) ) {
      MMERROR( preferredFormat7Colour == -1, mimasexception, ,
               "Preferred format7 colour \"" << namePreferred
               << "\" not supported in this mode (supported:"
               << s.str() << ")." );
      MMERROR( false, mimasexception, ,
               "Camera-driver doesn't offer a known format7 colour in "
               "this mode." );
    };

  }
  assert( m_format7Colour != -1 );
  MMERROR( m_colourSpace != Unknown,
           mimasexception, , "Colourspace transformation for "
           << colour[ selected ].name << "-images not implemented." );
}

};