//
// ZoneMinder Monitor Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
// 
// This program 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; either version 2
// of the License, or (at your option) any later version.
// 
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// 

#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <glob.h>

#include "zm.h"
#include "zm_db.h"
#include "zm_time.h"
#include "zm_mpeg.h"
#include "zm_signal.h"
#include "zm_monitor.h"
#if ZM_HAS_V4L
#include "zm_local_camera.h"
#endif // ZM_HAS_V4L
#include "zm_remote_camera.h"
#include "zm_remote_camera_http.h"
#if HAVE_LIBAVFORMAT
#include "zm_remote_camera_rtsp.h"
#endif // HAVE_LIBAVFORMAT
#include "zm_file_camera.h"
#if HAVE_LIBAVFORMAT
#include "zm_ffmpeg_camera.h"
#endif // HAVE_LIBAVFORMAT
#if HAVE_LIBVLC
#include "zm_libvlc_camera.h"
#endif // HAVE_LIBVLC
#if HAVE_LIBCURL
#include "zm_curl_camera.h"
#endif // HAVE_LIBCURL

#if ZM_MEM_MAPPED
#include <sys/mman.h>
#include <fcntl.h>
#else // ZM_MEM_MAPPED
#include <sys/ipc.h>
#include <sys/shm.h>
#endif // ZM_MEM_MAPPED

// SOLARIS - we don't have MAP_LOCKED on openSolaris/illumos
#ifndef MAP_LOCKED
#define MAP_LOCKED 0
#endif

//=============================================================================
std::vector<std::string> split(const std::string &s, char delim) {
  std::vector<std::string> elems;
  std::stringstream ss(s);
  std::string item;
  while(std::getline(ss, item, delim)) {
    elems.push_back(trimSpaces(item));
  }
  return elems;
}
//=============================================================================



Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id )
{
  strncpy( name, p_name, sizeof(name) );

#if ZM_MEM_MAPPED
  map_fd = -1;
  snprintf( mem_file, sizeof(mem_file), "%s/zm.mmap.%d", config.path_map, id );
#else // ZM_MEM_MAPPED
  shm_id = 0;
#endif // ZM_MEM_MAPPED
  mem_size = 0;
  mem_ptr = 0;

  last_event = 0;
  last_state = IDLE;

  last_connect_time = 0;
  connected = false;
}

Monitor::MonitorLink::~MonitorLink()
{
  disconnect();
}

bool Monitor::MonitorLink::connect()
{
  if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 )
  {
    last_connect_time = time( 0 );

    mem_size = sizeof(SharedData) + sizeof(TriggerData);

    Debug( 1, "link.mem.size=%d", mem_size );
#if ZM_MEM_MAPPED
    map_fd = open( mem_file, O_RDWR, (mode_t)0600 );
    if ( map_fd < 0 )
    {
      Debug( 3, "Can't open linked memory map file %s: %s", mem_file, strerror(errno) );
      disconnect();
      return( false );
    }
    while ( map_fd <= 2 ) {
      int new_map_fd = dup(map_fd);
      Warning( "Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd );
      close(map_fd);
      map_fd = new_map_fd;
    }

    struct stat map_stat;
    if ( fstat( map_fd, &map_stat ) < 0 )
    {
      Error( "Can't stat linked memory map file %s: %s", mem_file, strerror(errno) );
      disconnect();
      return( false );
    }

    if ( map_stat.st_size == 0 )
    {
      Error( "Linked memory map file %s is empty: %s", mem_file, strerror(errno) );
      disconnect();
      return( false );
    }
    else if ( map_stat.st_size < mem_size )
    {
      Error( "Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size );
      disconnect();
      return( false );
    }

    mem_ptr = (unsigned char *)mmap( NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0 );
    if ( mem_ptr == MAP_FAILED )
    {
      Error( "Can't map file %s (%d bytes) to memory: %s", mem_file, mem_size, strerror(errno) );
      disconnect();
      return( false );
    }
#else // ZM_MEM_MAPPED
    shm_id = shmget( (config.shm_key&0xffff0000)|id, mem_size, 0700 );
    if ( shm_id < 0 )
    {
      Debug( 3, "Can't shmget link memory: %s", strerror(errno) );
      connected = false;
      return( false );
    }
    mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 );
    if ( mem_ptr < 0 )
    {
      Debug( 3, "Can't shmat link memory: %s", strerror(errno) );
      connected = false;
      return( false );
    }
#endif // ZM_MEM_MAPPED

    shared_data = (SharedData *)mem_ptr;
    trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));

    if ( !shared_data->valid )
    {
      Debug( 3, "Linked memory not initialised by capture daemon" );
      disconnect();
      return( false );
    }

    last_state = shared_data->state;
    last_event = shared_data->last_event;
    connected = true;

    return( true );
  }
  return( false );
}

bool Monitor::MonitorLink::disconnect()
{
  if ( connected )
  {
    connected = false;

#if ZM_MEM_MAPPED
    if ( mem_ptr > 0 )
    {
      msync( mem_ptr, mem_size, MS_ASYNC );
      munmap( mem_ptr, mem_size );
    }
    if ( map_fd >= 0 )
      close( map_fd );

    map_fd = -1;
#else // ZM_MEM_MAPPED
    struct shmid_ds shm_data;
    if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 )
    {
      Debug( 3, "Can't shmctl: %s", strerror(errno) );
      return( false );
    }

    shm_id = 0;

    if ( shm_data.shm_nattch <= 1 )
    {
      if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 )
      {
        Debug( 3, "Can't shmctl: %s", strerror(errno) );
        return( false );
      }
    }

    if ( shmdt( mem_ptr ) < 0 )
    {
      Debug( 3, "Can't shmdt: %s", strerror(errno) );
      return( false );
    }

#endif // ZM_MEM_MAPPED
    mem_size = 0;
    mem_ptr = 0;
  }
  return( true );
}

bool Monitor::MonitorLink::isAlarmed()
{
  if ( !connected )
  {
    return( false );
  }
  return( shared_data->state == ALARM );
}

bool Monitor::MonitorLink::inAlarm()
{
  if ( !connected )
  {
    return( false );
  }
  return( shared_data->state == ALARM || shared_data->state == ALERT );
}

bool Monitor::MonitorLink::hasAlarmed()
{
  if ( shared_data->state == ALARM )
  {
    return( true );
  }
  else if( shared_data->last_event != (unsigned int)last_event )
  {
    last_event = shared_data->last_event;
  }
  return( false );
}

Monitor::Monitor(
  int p_id,
  const char *p_name,
  const unsigned int p_server_id,
  int p_function,
  bool p_enabled,
  const char *p_linked_monitors,
  Camera *p_camera,
  int p_orientation,
  unsigned int p_deinterlacing,
  const char *p_event_prefix,
  const char *p_label_format,
  const Coord &p_label_coord,
  int p_label_size,
  int p_image_buffer_count,
  int p_warmup_count,
  int p_pre_event_count,
  int p_post_event_count,
  int p_stream_replay_buffer,
  int p_alarm_frame_count,
  int p_section_length,
  int p_frame_skip,
  int p_motion_frame_skip,
  double p_analysis_fps,
  unsigned int p_analysis_update_delay,
  int p_capture_delay,
  int p_alarm_capture_delay,
  int p_fps_report_interval,
  int p_ref_blend_perc,
  int p_alarm_ref_blend_perc,
  bool p_track_motion,
  Rgb p_signal_check_colour,
  bool p_embed_exif,
  Purpose p_purpose,
  int p_n_zones,
  Zone *p_zones[]
) : id( p_id ),
  server_id( p_server_id ),
  function( (Function)p_function ),
  enabled( p_enabled ),
  width( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Height():p_camera->Width() ),
  height( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Width():p_camera->Height() ),
  orientation( (Orientation)p_orientation ),
  deinterlacing( p_deinterlacing ),
  label_coord( p_label_coord ),
  label_size( p_label_size ),
  image_buffer_count( p_image_buffer_count ),
  warmup_count( p_warmup_count ),
  pre_event_count( p_pre_event_count ),
  post_event_count( p_post_event_count ),
  stream_replay_buffer( p_stream_replay_buffer ),
  section_length( p_section_length ),
  frame_skip( p_frame_skip ),
  motion_frame_skip( p_motion_frame_skip ),
  analysis_fps( p_analysis_fps ),
  analysis_update_delay( p_analysis_update_delay ),
  capture_delay( p_capture_delay ),
  alarm_capture_delay( p_alarm_capture_delay ),
  alarm_frame_count( p_alarm_frame_count ),
  fps_report_interval( p_fps_report_interval ),
  ref_blend_perc( p_ref_blend_perc ),
  alarm_ref_blend_perc( p_alarm_ref_blend_perc ),
  track_motion( p_track_motion ),
  signal_check_colour( p_signal_check_colour ),
  embed_exif( p_embed_exif ),
  delta_image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ),
  ref_image( width, height, p_camera->Colours(), p_camera->SubpixelOrder() ),
  purpose( p_purpose ),
  last_motion_score(0),
  camera( p_camera ),
  n_zones( p_n_zones ),
  zones( p_zones ),
  timestamps( 0 ),
  images( 0 ),
  privacy_bitmask( NULL )
{
  strncpy( name, p_name, sizeof(name)-1 );

  strncpy( event_prefix, p_event_prefix, sizeof(event_prefix)-1 );
  strncpy( label_format, p_label_format, sizeof(label_format)-1 );

  // Change \n to actual line feeds
  char *token_ptr = label_format;
  const char *token_string = "\n";
  while( ( token_ptr = strstr( token_ptr, token_string ) ) )
  {
    if ( *(token_ptr+1) )
    {
      *token_ptr = '\n';
      token_ptr++;
      strcpy( token_ptr, token_ptr+1 );
    }
    else
    {
      *token_ptr = '\0';
      break;
    }
  }

  fps = 0.0;
  event_count = 0;
  image_count = 0;
  ready_count = warmup_count;
  first_alarm_count = 0;
  last_alarm_count = 0;
  state = IDLE;

  if ( alarm_frame_count < 1 )
    alarm_frame_count = 1;
  else if ( alarm_frame_count > MAX_PRE_ALARM_FRAMES )
    alarm_frame_count = MAX_PRE_ALARM_FRAMES;

  auto_resume_time = 0;

  if ( strcmp( config.event_close_mode, "time" ) == 0 )
    event_close_mode = CLOSE_TIME;
  else if ( strcmp( config.event_close_mode, "alarm" ) == 0 )
    event_close_mode = CLOSE_ALARM;
  else
    event_close_mode = CLOSE_IDLE;

  Debug( 1, "monitor purpose=%d", purpose );

  mem_size = sizeof(SharedData)
       + sizeof(TriggerData)
       + (image_buffer_count*sizeof(struct timeval))
       + (image_buffer_count*camera->ImageSize())
       + 64; /* Padding used to permit aligning the images buffer to 16 byte boundary */

  Debug( 1, "mem.size=%d", mem_size );
  mem_ptr = NULL;

  if ( purpose == CAPTURE ) {
    this->connect();
    if ( ! mem_ptr ) exit(-1);
    memset( mem_ptr, 0, mem_size );
    shared_data->size = sizeof(SharedData);
    shared_data->active = enabled;
    shared_data->signal = false;
    shared_data->state = IDLE;
    shared_data->last_write_index = image_buffer_count;
    shared_data->last_read_index = image_buffer_count;
    shared_data->last_write_time = 0;
    shared_data->last_event = 0;
    shared_data->action = (Action)0;
    shared_data->brightness = -1;
    shared_data->hue = -1;
    shared_data->colour = -1;
    shared_data->contrast = -1;
    shared_data->alarm_x = -1;
    shared_data->alarm_y = -1;
    shared_data->format = camera->SubpixelOrder();
    shared_data->imagesize = camera->ImageSize();
    trigger_data->size = sizeof(TriggerData);
    trigger_data->trigger_state = TRIGGER_CANCEL;
    trigger_data->trigger_score = 0;
    trigger_data->trigger_cause[0] = 0;
    trigger_data->trigger_text[0] = 0;
    trigger_data->trigger_showtext[0] = 0;
    shared_data->valid = true;
  } else if ( purpose == ANALYSIS ) {
    this->connect();
    if ( ! mem_ptr ) exit(-1);
    shared_data->state = IDLE;
    shared_data->last_read_time = 0;
    shared_data->alarm_x = -1;
    shared_data->alarm_y = -1;
  }

  if ( ( ! mem_ptr ) || ! shared_data->valid )
  {
    if ( purpose != QUERY )
    {
      Error( "Shared data not initialised by capture daemon for monitor %s", name );
      exit( -1 );
    }
  }

  // Will this not happen every time a monitor is instantiated?  Seems like all the calls to the Monitor constructor pass a zero for n_zones, then load zones after..
  if ( !n_zones ) {
    Debug( 1, "Monitor %s has no zones, adding one.", name );
    n_zones = 1;
    zones = new Zone *[1];
    Coord coords[4] = { Coord( 0, 0 ), Coord( width-1, 0 ), Coord( width-1, height-1 ), Coord( 0, height-1 ) };
    zones[0] = new Zone( this, 0, "All", Zone::ACTIVE, Polygon( sizeof(coords)/sizeof(*coords), coords ), RGB_RED, Zone::BLOBS );
  }
  start_time = last_fps_time = time( 0 );

  event = 0;

  Debug( 1, "Monitor %s has function %d", name, function );
  Debug( 1, "Monitor %s LBF = '%s', LBX = %d, LBY = %d, LBS = %d", name, label_format, label_coord.X(), label_coord.Y(), label_size );
  Debug( 1, "Monitor %s IBC = %d, WUC = %d, pEC = %d, PEC = %d, EAF = %d, FRI = %d, RBP = %d, ARBP = %d, FM = %d", name, image_buffer_count, warmup_count, pre_event_count, post_event_count, alarm_frame_count, fps_report_interval, ref_blend_perc, alarm_ref_blend_perc, track_motion );

  if ( purpose == ANALYSIS )
  {
    static char path[PATH_MAX];

    strncpy( path, config.dir_events, sizeof(path) );

    struct stat statbuf;
    errno = 0;
    stat( path, &statbuf );
    if ( errno == ENOENT || errno == ENOTDIR )
    {
      if ( mkdir( path, 0755 ) )
      {
        Error( "Can't make %s: %s", path, strerror(errno));
      }
    }

    snprintf( path, sizeof(path), "%s/%d", config.dir_events, id );

    errno = 0;
    stat( path, &statbuf );
    if ( errno == ENOENT || errno == ENOTDIR )
    {
      if ( mkdir( path, 0755 ) )
      {
        Error( "Can't make %s: %s", path, strerror(errno));
      }
      char temp_path[PATH_MAX];
      snprintf( temp_path, sizeof(temp_path), "%d", id );
      if ( chdir( config.dir_events ) < 0 )
        Fatal( "Can't change directory to '%s': %s", config.dir_events, strerror(errno) );
      if ( symlink( temp_path, name ) < 0 )
        Fatal( "Can't symlink '%s' to '%s': %s", temp_path, name, strerror(errno) );
      if ( chdir( ".." ) < 0 )
        Fatal( "Can't change to parent directory: %s", strerror(errno) );
    }

    while( shared_data->last_write_index == (unsigned int)image_buffer_count 
         && shared_data->last_write_time == 0)
    {
      Warning( "Waiting for capture daemon" );
      sleep( 1 );
    }
    ref_image.Assign( width, height, camera->Colours(), camera->SubpixelOrder(), image_buffer[shared_data->last_write_index].image->Buffer(), camera->ImageSize());

    n_linked_monitors = 0;
    linked_monitors = 0;

    adaptive_skip = true;

    ReloadLinkedMonitors( p_linked_monitors );
  }
}

bool Monitor::connect() {
#if ZM_MEM_MAPPED
  snprintf( mem_file, sizeof(mem_file), "%s/zm.mmap.%d", config.path_map, id );
  map_fd = open( mem_file, O_RDWR|O_CREAT, (mode_t)0600 );
  if ( map_fd < 0 )
    Fatal( "Can't open memory map file %s, probably not enough space free: %s", mem_file, strerror(errno) );

  struct stat map_stat;
  if ( fstat( map_fd, &map_stat ) < 0 )
    Fatal( "Can't stat memory map file %s: %s, is the zmc process for this monitor running?", mem_file, strerror(errno) );
  if ( map_stat.st_size != mem_size && purpose == CAPTURE ) {
    // Allocate the size
    if ( ftruncate( map_fd, mem_size ) < 0 ) {
      Fatal( "Can't extend memory map file %s to %d bytes: %s", mem_file, mem_size, strerror(errno) );
    }
  } else if ( map_stat.st_size == 0 ) {
    Error( "Got empty memory map file size %ld, is the zmc process for this monitor running?", map_stat.st_size, mem_size );
    return false;
  } else if ( map_stat.st_size != mem_size ) {
    Error( "Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size );
    return false;
  } else {
#ifdef MAP_LOCKED
    mem_ptr = (unsigned char *)mmap( NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0 );
    if ( mem_ptr == MAP_FAILED ) {
      if ( errno == EAGAIN ) {
        Debug( 1, "Unable to map file %s (%d bytes) to locked memory, trying unlocked", mem_file, mem_size );
#endif
        mem_ptr = (unsigned char *)mmap( NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0 );
        Debug( 1, "Mapped file %s (%d bytes) to locked memory, unlocked", mem_file, mem_size );
#ifdef MAP_LOCKED
      }
    }
#endif
    if ( mem_ptr == MAP_FAILED )
      Fatal( "Can't map file %s (%d bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno );
  }
#else // ZM_MEM_MAPPED
  shm_id = shmget( (config.shm_key&0xffff0000)|id, mem_size, IPC_CREAT|0700 );
  if ( shm_id < 0 ) {
    Error( "Can't shmget, probably not enough shared memory space free: %s", strerror(errno));
    exit( -1 );
  }
  mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 );
  if ( mem_ptr < 0 )
  {
    Error( "Can't shmat: %s", strerror(errno));
    exit( -1 );
  }
#endif // ZM_MEM_MAPPED
  shared_data = (SharedData *)mem_ptr;
  trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
  struct timeval *shared_timestamps = (struct timeval *)((char *)trigger_data + sizeof(TriggerData));
  unsigned char *shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval)));
  
  if(((unsigned long)shared_images % 16) != 0) {
    /* Align images buffer to nearest 16 byte boundary */
    Debug(3,"Aligning shared memory images to the next 16 byte boundary");
    shared_images = (uint8_t*)((unsigned long)shared_images + (16 - ((unsigned long)shared_images % 16)));
  }
  image_buffer = new Snapshot[image_buffer_count];
  for ( int i = 0; i < image_buffer_count; i++ )
  {
    image_buffer[i].timestamp = &(shared_timestamps[i]);
    image_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()]) );
    image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */
  }
  if ( (deinterlacing & 0xff) == 4)
  {
    /* Four field motion adaptive deinterlacing in use */
    /* Allocate a buffer for the next image */
    next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder());
    next_buffer.timestamp = new struct timeval;
  }

  if ( ( purpose == ANALYSIS ) && analysis_fps )
  {
    // Size of pre event buffer must be greater than pre_event_count
    // if alarm_frame_count > 1, because in this case the buffer contains
    // alarmed images that must be discarded when event is created
    pre_event_buffer_count = pre_event_count + alarm_frame_count - 1;
    pre_event_buffer = new Snapshot[pre_event_buffer_count];
    for ( int i = 0; i < pre_event_buffer_count; i++ )
    {
      pre_event_buffer[i].timestamp = new struct timeval;
      pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder());
    }
  }

  return true;
}

Monitor::~Monitor()
{
  if ( timestamps ) {
    delete[] timestamps;
    timestamps = 0;
  }
  if ( images ) {
    delete[] images;
    images = 0;
  }
    if ( privacy_bitmask ) {
      delete[] privacy_bitmask;
      privacy_bitmask = NULL;
    }
  if ( mem_ptr ) {
    if ( event )
      Info( "%s: %03d - Closing event %d, shutting down", name, image_count, event->Id() );
    closeEvent();

    if ( (deinterlacing & 0xff) == 4)
    {
      delete next_buffer.image;
      delete next_buffer.timestamp;
    }
    for ( int i = 0; i < image_buffer_count; i++ )
    {
      delete image_buffer[i].image;
    }
    delete[] image_buffer;
  } // end if mem_ptr

  for ( int i = 0; i < n_zones; i++ )
  {
    delete zones[i];
  }
  delete[] zones;

  delete camera;

  if ( mem_ptr ) {
    if ( purpose == ANALYSIS )
    {
      shared_data->state = state = IDLE;
      shared_data->last_read_index = image_buffer_count;
      shared_data->last_read_time = 0;

      if ( analysis_fps )
      {
        for ( int i = 0; i < pre_event_buffer_count; i++ )
        {
          delete pre_event_buffer[i].image;
          delete pre_event_buffer[i].timestamp;
        }
        delete[] pre_event_buffer;
      }
    }
    else if ( purpose == CAPTURE )
    {
      shared_data->valid = false;
      memset( mem_ptr, 0, mem_size );
    }

#if ZM_MEM_MAPPED
    if ( msync( mem_ptr, mem_size, MS_SYNC ) < 0 )
      Error( "Can't msync: %s", strerror(errno) );
    if ( munmap( mem_ptr, mem_size ) < 0 )
      Fatal( "Can't munmap: %s", strerror(errno) );
    close( map_fd );

    if ( purpose == CAPTURE ) {
        char mmap_path[PATH_MAX] = "";
        snprintf( mmap_path, sizeof(mmap_path), "%s/zm.mmap.%d", config.path_map, id );

        if ( unlink( mmap_path ) < 0 ) {
            Warning( "Can't unlink '%s': %s", mmap_path, strerror(errno) );
        }
    }
#else // ZM_MEM_MAPPED
    struct shmid_ds shm_data;
    if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) {
      Error( "Can't shmctl: %s", strerror(errno) );
      exit( -1 );
    }
    if ( shm_data.shm_nattch <= 1 ) {
      if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) {
        Error( "Can't shmctl: %s", strerror(errno) );
        exit( -1 );
      }
    }
#endif // ZM_MEM_MAPPED
  } // end if mem_ptr
}

void Monitor::AddZones( int p_n_zones, Zone *p_zones[] )
{
  for ( int i = 0; i < n_zones; i++ )
    delete zones[i];
  delete[] zones;
  n_zones = p_n_zones;
  zones = p_zones;
}

void Monitor::AddPrivacyBitmask( Zone *p_zones[] )
{
  if ( privacy_bitmask )
    delete[] privacy_bitmask;
  privacy_bitmask = NULL;
  Image *privacy_image = NULL;

  for ( int i = 0; i < n_zones; i++ )
  {
    if ( p_zones[i]->IsPrivacy() )
    {
      if ( !privacy_image )
      {
        privacy_image = new Image( width, height, 1, ZM_SUBPIX_ORDER_NONE);
        privacy_image->Clear();
      }
      privacy_image->Fill( 0xff, p_zones[i]->GetPolygon() );
      privacy_image->Outline( 0xff, p_zones[i]->GetPolygon() );
    }
  } // end foreach zone
  if ( privacy_image )
    privacy_bitmask = privacy_image->Buffer();
}

Monitor::State Monitor::GetState() const
{
  return( (State)shared_data->state );
}

int Monitor::GetImage( int index, int scale )
{
  if ( index < 0 || index > image_buffer_count )
  {
    index = shared_data->last_write_index;
  }

  if ( index != image_buffer_count )
  {
    Image *image;
    // If we are going to be modifying the snapshot before writing, then we need to copy it
    if ( ( scale != ZM_SCALE_BASE ) || ( !config.timestamp_on_capture ) ) {
      Snapshot *snap = &image_buffer[index];
      Image *snap_image = snap->image;

      alarm_image.Assign( *snap_image );


      //write_image.Assign( *snap_image );

      if ( scale != ZM_SCALE_BASE ) {
        alarm_image.Scale( scale );
      }

      if ( !config.timestamp_on_capture ) {
        TimestampImage( &alarm_image, snap->timestamp );
      }
      image = &alarm_image;
    } else {
      image = image_buffer[index].image;
    }

    static char filename[PATH_MAX];
    snprintf( filename, sizeof(filename), "Monitor%d.jpg", id );
    image->WriteJpeg( filename );
  }
  else
  {
    Error( "Unable to generate image, no images in buffer" );
  }
  return( 0 );
}

struct timeval Monitor::GetTimestamp( int index ) const
{
  if ( index < 0 || index > image_buffer_count )
  {
    index = shared_data->last_write_index;
  }

  if ( index != image_buffer_count )
  {
    Snapshot *snap = &image_buffer[index];

    return( *(snap->timestamp) );
  }
  else
  {
    static struct timeval null_tv = { 0, 0 };

    return( null_tv );
  }
}

unsigned int Monitor::GetLastReadIndex() const
{
  return( shared_data->last_read_index!=(unsigned int)image_buffer_count?shared_data->last_read_index:-1 );
}

unsigned int Monitor::GetLastWriteIndex() const
{
  return( shared_data->last_write_index!=(unsigned int)image_buffer_count?shared_data->last_write_index:-1 );
}

unsigned int Monitor::GetLastEvent() const
{
  return( shared_data->last_event );
}

double Monitor::GetFPS() const
{
  int index1 = shared_data->last_write_index;
  if ( index1 == image_buffer_count )
  {
    return( 0.0 );
  }
  Snapshot *snap1 = &image_buffer[index1];
  if ( !snap1->timestamp || !snap1->timestamp->tv_sec )
  {
    return( 0.0 );
  }
  struct timeval time1 = *snap1->timestamp;

  int image_count = image_buffer_count;
  int index2 = (index1+1)%image_buffer_count;
  if ( index2 == image_buffer_count )
  {
    return( 0.0 );
  }
  Snapshot *snap2 = &image_buffer[index2];
  while ( !snap2->timestamp || !snap2->timestamp->tv_sec )
  {
    if ( index1 == index2 )
    {
      return( 0.0 );
    }
    index2 = (index2+1)%image_buffer_count;
    snap2 = &image_buffer[index2];
    image_count--;
  }
  struct timeval time2 = *snap2->timestamp;

  double time_diff = tvDiffSec( time2, time1 );

  double curr_fps = image_count/time_diff;

  if ( curr_fps < 0.0 )
  {
    //Error( "Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count );
    return( 0.0 );
  }
  return( curr_fps );
}

useconds_t Monitor::GetAnalysisRate()
{
  double capturing_fps = GetFPS();
  if ( !analysis_fps )
  {
    return( 0 );
  }
  else if ( analysis_fps > capturing_fps )
  {
    Warning( "Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps, capturing_fps );
    return( 0 );
  }
  else
  {
    return( ( 1000000 / analysis_fps ) - ( 1000000 / capturing_fps ) );
  }
}

void Monitor::UpdateAdaptiveSkip()
{
  if ( config.opt_adaptive_skip )
  {
    double capturing_fps = GetFPS();
    if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) )
    {
      Info( "Analysis fps (%.2f) is lower than capturing fps (%.2f), disabling adaptive skip feature", analysis_fps, capturing_fps );
      adaptive_skip = false;
    }
    else if ( !adaptive_skip && ( !analysis_fps || ( analysis_fps >= capturing_fps ) ) )
    {
      Info( "Enabling adaptive skip feature" );
      adaptive_skip = true;
    }
  }
  else
  {
    adaptive_skip = false;
  }
}

void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text )
{
  trigger_data->trigger_state = TRIGGER_ON;
  trigger_data->trigger_score = force_score;
  strncpy( trigger_data->trigger_cause, force_cause, sizeof(trigger_data->trigger_cause) );
  strncpy( trigger_data->trigger_text, force_text, sizeof(trigger_data->trigger_text) );
}

void Monitor::ForceAlarmOff()
{
  trigger_data->trigger_state = TRIGGER_OFF;
}

void Monitor::CancelForced()
{
  trigger_data->trigger_state = TRIGGER_CANCEL;
}

void Monitor::actionReload()
{
  shared_data->action |= RELOAD;
}

void Monitor::actionEnable()
{
  shared_data->action |= RELOAD;

  static char sql[ZM_SQL_SML_BUFSIZ];
  snprintf( sql, sizeof(sql), "update Monitors set Enabled = 1 where Id = '%d'", id );
  if ( mysql_query( &dbconn, sql ) )
  {
    Error( "Can't run query: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
}

void Monitor::actionDisable()
{
  shared_data->action |= RELOAD;

  static char sql[ZM_SQL_SML_BUFSIZ];
  snprintf( sql, sizeof(sql), "update Monitors set Enabled = 0 where Id = '%d'", id );
  if ( mysql_query( &dbconn, sql ) )
  {
    Error( "Can't run query: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
}

void Monitor::actionSuspend()
{
  shared_data->action |= SUSPEND;
}

void Monitor::actionResume()
{
  shared_data->action |= RESUME;
}

int Monitor::actionBrightness( int p_brightness )
{
  if ( purpose != CAPTURE )
  {
    if ( p_brightness >= 0 )
    {
      shared_data->brightness = p_brightness;
      shared_data->action |= SET_SETTINGS;
      int wait_loops = 10;
      while ( shared_data->action & SET_SETTINGS )
      {
        if ( wait_loops-- )
          usleep( 100000 );
        else
        {
          Warning( "Timed out waiting to set brightness" );
          return( -1 );
        }
      }
    }
    else
    {
      shared_data->action |= GET_SETTINGS;
      int wait_loops = 10;
      while ( shared_data->action & GET_SETTINGS )
      {
        if ( wait_loops-- )
          usleep( 100000 );
        else
        {
          Warning( "Timed out waiting to get brightness" );
          return( -1 );
        }
      }
    }
    return( shared_data->brightness );
  }
  return( camera->Brightness( p_brightness ) );
}

int Monitor::actionContrast( int p_contrast )
{
  if ( purpose != CAPTURE )
  {
    if ( p_contrast >= 0 )
    {
      shared_data->contrast = p_contrast;
      shared_data->action |= SET_SETTINGS;
      int wait_loops = 10;
      while ( shared_data->action & SET_SETTINGS )
      {
        if ( wait_loops-- )
          usleep( 100000 );
        else
        {
          Warning( "Timed out waiting to set contrast" );
          return( -1 );
        }
      }
    }
    else
    {
      shared_data->action |= GET_SETTINGS;
      int wait_loops = 10;
      while ( shared_data->action & GET_SETTINGS )
      {
        if ( wait_loops-- )
          usleep( 100000 );
        else
        {
          Warning( "Timed out waiting to get contrast" );
          return( -1 );
        }
      }
    }
    return( shared_data->contrast );
  }
  return( camera->Contrast( p_contrast ) );
}

int Monitor::actionHue( int p_hue )
{
  if ( purpose != CAPTURE )
  {
    if ( p_hue >= 0 )
    {
      shared_data->hue = p_hue;
      shared_data->action |= SET_SETTINGS;
      int wait_loops = 10;
      while ( shared_data->action & SET_SETTINGS )
      {
        if ( wait_loops-- )
          usleep( 100000 );
        else
        {
          Warning( "Timed out waiting to set hue" );
          return( -1 );
        }
      }
    }
    else
    {
      shared_data->action |= GET_SETTINGS;
      int wait_loops = 10;
      while ( shared_data->action & GET_SETTINGS )
      {
        if ( wait_loops-- )
          usleep( 100000 );
        else
        {
          Warning( "Timed out waiting to get hue" );
          return( -1 );
        }
      }
    }
    return( shared_data->hue );
  }
  return( camera->Hue( p_hue ) );
}

int Monitor::actionColour( int p_colour )
{
  if ( purpose != CAPTURE )
  {
    if ( p_colour >= 0 )
    {
      shared_data->colour = p_colour;
      shared_data->action |= SET_SETTINGS;
      int wait_loops = 10;
      while ( shared_data->action & SET_SETTINGS )
      {
        if ( wait_loops-- )
          usleep( 100000 );
        else
        {
          Warning( "Timed out waiting to set colour" );
          return( -1 );
        }
      }
    }
    else
    {
      shared_data->action |= GET_SETTINGS;
      int wait_loops = 10;
      while ( shared_data->action & GET_SETTINGS )
      {
        if ( wait_loops-- )
          usleep( 100000 );
        else
        {
          Warning( "Timed out waiting to get colour" );
          return( -1 );
        }
      }
    }
    return( shared_data->colour );
  }
  return( camera->Colour( p_colour ) );
}

void Monitor::DumpZoneImage( const char *zone_string )
{
  int exclude_id = 0;
  int extra_colour = 0;
  Polygon extra_zone;

  if ( zone_string )
  {
    if ( !Zone::ParseZoneString( zone_string, exclude_id, extra_colour, extra_zone ) )
    {
      Error( "Failed to parse zone string, ignoring" );
    }
  }

  int index = shared_data->last_write_index;
  Snapshot *snap = &image_buffer[index];
  Image *snap_image = snap->image;

  Image zone_image( *snap_image );
  if(zone_image.Colours() == ZM_COLOUR_GRAY8) {
    zone_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB );
  }
  
  for( int i = 0; i < n_zones; i++ )
  {
    if ( exclude_id && (!extra_colour || extra_zone.getNumCoords()) && zones[i]->Id() == exclude_id )
      continue;

    Rgb colour;
    if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id )
    {
      colour = extra_colour;
    }
    else
    {
      if ( zones[i]->IsActive() )
      {
        colour = RGB_RED;
      }
      else if ( zones[i]->IsInclusive() )
      {
        colour = RGB_ORANGE;
      }
      else if ( zones[i]->IsExclusive() )
      {
        colour = RGB_PURPLE;
      }
      else if ( zones[i]->IsPreclusive() )
      {
        colour = RGB_BLUE;
      }
      else
      {
        colour = RGB_WHITE;
      }
    }
    zone_image.Fill( colour, 2, zones[i]->GetPolygon() );
    zone_image.Outline( colour, zones[i]->GetPolygon() );
  }

  if ( extra_zone.getNumCoords() )
  {
    zone_image.Fill( extra_colour, 2, extra_zone );
    zone_image.Outline( extra_colour, extra_zone );
  }

  static char filename[PATH_MAX];
  snprintf( filename, sizeof(filename), "Zones%d.jpg", id );
  zone_image.WriteJpeg( filename );
}

void Monitor::DumpImage( Image *dump_image ) const
{
  if ( image_count && !(image_count%10) )
  {
    static char filename[PATH_MAX];
    static char new_filename[PATH_MAX];
    snprintf( filename, sizeof(filename), "Monitor%d.jpg", id );
    snprintf( new_filename, sizeof(new_filename), "Monitor%d-new.jpg", id );
    dump_image->WriteJpeg( new_filename );
    rename( new_filename, filename );
  }
}

bool Monitor::CheckSignal( const Image *image )
{
  static bool static_undef = true;
  /* RGB24 colors */
  static uint8_t red_val;
  static uint8_t green_val;
  static uint8_t blue_val;
  static uint8_t grayscale_val; /* 8bit grayscale color */  
  static Rgb colour_val; /* RGB32 color */
  static int usedsubpixorder;

  if ( config.signal_check_points > 0 )
  {
    if ( static_undef )
    {
      static_undef = false;
      usedsubpixorder = camera->SubpixelOrder();
      colour_val = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */
      colour_val = rgb_convert(colour_val, usedsubpixorder);
      red_val = RED_VAL_BGRA(signal_check_colour);
      green_val = GREEN_VAL_BGRA(signal_check_colour);
      blue_val = BLUE_VAL_BGRA(signal_check_colour);
      grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */
    }

    const uint8_t *buffer = image->Buffer();
    int pixels = image->Pixels();
    int width = image->Width();
    int colours = image->Colours();

    int index = 0;
    for ( int i = 0; i < config.signal_check_points; i++ )
    {
      while( true )
      {
        index = (int)(((long long)rand()*(long long)(pixels-1))/RAND_MAX);
        if ( !config.timestamp_on_capture || !label_format[0] )
          break;
        // Avoid sampling the rows with timestamp in
        if ( index < (label_coord.Y()*width) || index >= (label_coord.Y()+Image::LINE_HEIGHT)*width )
          break;
      }
      
    if(colours == ZM_COLOUR_GRAY8) {
      if ( *(buffer+index) != grayscale_val )
        return true;
      
    } else if(colours == ZM_COLOUR_RGB24) {
      const uint8_t *ptr = buffer+(index*colours);
      
      if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR) {
        if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) )
          return true;
      } else {
        /* Assume RGB */
        if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) )
          return true;
      }
      
    } else if(colours == ZM_COLOUR_RGB32) {
      if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) {
        if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) )
          return true;
      } else {
        /* Assume RGBA or BGRA */
        if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) )
          return true;
      }
    }
    
    }
    return( false );
  }
  return( true );
}

bool Monitor::Analyse()
{
  if ( shared_data->last_read_index == shared_data->last_write_index )
  {
    return( false );
  }

  struct timeval now;
  gettimeofday( &now, NULL );

  if ( image_count && fps_report_interval && !(image_count%fps_report_interval) )
  {
    fps = double(fps_report_interval)/(now.tv_sec-last_fps_time);
    Info( "%s: %d - Analysing at %.2f fps", name, image_count, fps );
    last_fps_time = now.tv_sec;
  }

  int index;
  if ( adaptive_skip )
  {
    int read_margin = shared_data->last_read_index - shared_data->last_write_index;
    if ( read_margin < 0 ) read_margin += image_buffer_count;

    int step = 1;
    if ( read_margin > 0 )
    {
      step = (9*image_buffer_count)/(5*read_margin);
    }

    int pending_frames = shared_data->last_write_index - shared_data->last_read_index;
    if ( pending_frames < 0 ) pending_frames += image_buffer_count;

    Debug( 4, "RI:%d, WI: %d, PF = %d, RM = %d, Step = %d", shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step );
    if ( step <= pending_frames )
    {
      index = (shared_data->last_read_index+step)%image_buffer_count;
    }
    else
    {
      if ( pending_frames )
      {
        Warning( "Approaching buffer overrun, consider slowing capture, simplifying analysis or increasing ring buffer size" );
      }
      index = shared_data->last_write_index%image_buffer_count;
    }
  }
  else
  {
    index = shared_data->last_write_index%image_buffer_count;
  }

  Snapshot *snap = &image_buffer[index];
  struct timeval *timestamp = snap->timestamp;
  Image *snap_image = snap->image;

  if ( shared_data->action )
  {
    if ( shared_data->action & RELOAD )
    {
      Info( "Received reload indication at count %d", image_count );
      shared_data->action &= ~RELOAD;
      Reload();
    }
    if ( shared_data->action & SUSPEND )
    {
      if ( Active() )
      {
        Info( "Received suspend indication at count %d", image_count );
        shared_data->active = false;
        //closeEvent();
      }
      if ( config.max_suspend_time )
      {
        auto_resume_time = now.tv_sec + config.max_suspend_time;
      }
      shared_data->action &= ~SUSPEND;
    }
    if ( shared_data->action & RESUME )
    {
      if ( Enabled() && !Active() )
      {
        Info( "Received resume indication at count %d", image_count );
        shared_data->active = true;
        ref_image = *snap_image;
        ready_count = image_count+(warmup_count/2);
        shared_data->alarm_x = shared_data->alarm_y = -1;
      }
      shared_data->action &= ~RESUME;
    }
  }
  if ( auto_resume_time && (now.tv_sec >= auto_resume_time) )
  {
    Info( "Auto resuming at count %d", image_count );
    shared_data->active = true;
    ref_image = *snap_image;
    ready_count = image_count+(warmup_count/2);
    auto_resume_time = 0;
  }

  static bool static_undef = true;
  static int last_section_mod = 0;
  static bool last_signal;

  if ( static_undef )
  {
    static_undef = false;
    timestamps = new struct timeval *[pre_event_count];
    images = new Image *[pre_event_count];
    last_signal = shared_data->signal;
  }

  if ( Enabled() )
  {
    bool signal = shared_data->signal;
    bool signal_change = (signal != last_signal);
    if ( trigger_data->trigger_state != TRIGGER_OFF )
    {
      unsigned int score = 0;
      if ( Ready() )
      {
        std::string cause;
        Event::StringSetMap noteSetMap;

        if ( trigger_data->trigger_state == TRIGGER_ON )
        {
          score += trigger_data->trigger_score;
          if ( !event )
          {
            if ( cause.length() )
              cause += ", ";
            cause += trigger_data->trigger_cause;
          }
          Event::StringSet noteSet;
          noteSet.insert( trigger_data->trigger_text );
          noteSetMap[trigger_data->trigger_cause] = noteSet;
        }
        if ( signal_change )
        {
          const char *signalText;
          if ( !signal )
            signalText = "Lost";
          else
          {
            signalText = "Reacquired";
            score += 100;
          }
          Warning( "%s: %s", SIGNAL_CAUSE, signalText );
          if ( event && !signal )
          {
            Info( "%s: %03d - Closing event %d, signal loss", name, image_count, event->Id() );
            closeEvent();
            last_section_mod = 0;
          }
          if ( !event )
          {
            if ( cause.length() )
              cause += ", ";
            cause += SIGNAL_CAUSE;
          }
          Event::StringSet noteSet;
          noteSet.insert( signalText );
          noteSetMap[SIGNAL_CAUSE] = noteSet;
          shared_data->state = state = IDLE;
          shared_data->active = signal;
          ref_image = *snap_image;
        }
        else if ( signal && Active() && (function == MODECT || function == MOCORD) )
        {
          Event::StringSet zoneSet;
          int motion_score = last_motion_score;
          if ( !(image_count % (motion_frame_skip+1) ) )
          {
            // Get new score.
            motion_score = last_motion_score = DetectMotion( *snap_image, zoneSet );
          }
          //int motion_score = DetectBlack( *snap_image, zoneSet );
          if ( motion_score )
          {
            if ( !event )
            {
              score += motion_score;
              if ( cause.length() )
                cause += ", ";
              cause += MOTION_CAUSE;
            }
            else
            {
              score += motion_score;
            }
            noteSetMap[MOTION_CAUSE] = zoneSet;

          }
          shared_data->active = signal;
        }
        if ( (!signal_change && signal) && n_linked_monitors > 0 )
        {
          bool first_link = true;
          Event::StringSet noteSet;
          for ( int i = 0; i < n_linked_monitors; i++ )
          {
            if ( linked_monitors[i]->isConnected() )
            {
              if ( linked_monitors[i]->hasAlarmed() )
              {
                if ( !event )
                {
                  if ( first_link )
                  {
                    if ( cause.length() )
                      cause += ", ";
                    cause += LINKED_CAUSE;
                    first_link = false;
                  }
                }
                noteSet.insert( linked_monitors[i]->Name() );
                score += 50;
              }
            }
            else
            {
              linked_monitors[i]->connect();
            }
          }
          if ( noteSet.size() > 0 )
            noteSetMap[LINKED_CAUSE] = noteSet;
        }
        if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) )
        {
          if ( event )
          {
            int section_mod = timestamp->tv_sec%section_length;
            if ( section_mod < last_section_mod )
            {
              if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME )
              {
                if ( state == TAPE )
                {
                  shared_data->state = state = IDLE;
                  Info( "%s: %03d - Closing event %d, section end", name, image_count, event->Id() )
                }
                else
                  Info( "%s: %03d - Closing event %d, section end forced ", name, image_count, event->Id() );
                closeEvent();
                last_section_mod = 0;
              }
            }
            else
            {
              last_section_mod = section_mod;
            }
          }
          if ( !event )
          {

            // Create event
            event = new Event( this, *timestamp, "Continuous", noteSetMap );
            shared_data->last_event = event->Id();

            Info( "%s: %03d - Opening new event %d, section start", name, image_count, event->Id() );

            /* To prevent cancelling out an existing alert\prealarm\alarm state */
            if ( state == IDLE )
            {
              shared_data->state = state = TAPE;
            }

            //if ( config.overlap_timed_events )
            if ( false )
            {
              int pre_index;
              int pre_event_images = pre_event_count;

              if ( analysis_fps )
              {
                // If analysis fps is set,
                // compute the index for pre event images in the dedicated buffer
                pre_index = image_count%pre_event_buffer_count;

                // Seek forward the next filled slot in to the buffer (oldest data)
                // from the current position
                while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec )
                {
                  pre_index = (pre_index + 1)%pre_event_buffer_count;
                  // Slot is empty, removing image from counter
                  pre_event_images--;
                }
              }
              else
              {
                // If analysis fps is not set (analysis performed at capturing framerate),
                // compute the index for pre event images in the capturing buffer
                pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count;

                // Seek forward the next filled slot in to the buffer (oldest data)
                // from the current position
                while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec )
                {
                  pre_index = (pre_index + 1)%image_buffer_count;
                  // Slot is empty, removing image from counter
                  pre_event_images--;
                }
              }

              if ( pre_event_images )
              {
                if ( analysis_fps )
                  for ( int i = 0; i < pre_event_images; i++ )
                  {
                    timestamps[i] = pre_event_buffer[pre_index].timestamp;
                    images[i] = pre_event_buffer[pre_index].image;
                    pre_index = (pre_index + 1)%pre_event_buffer_count;
                  }
                else
                  for ( int i = 0; i < pre_event_images; i++ )
                  {
                    timestamps[i] = image_buffer[pre_index].timestamp;
                    images[i] = image_buffer[pre_index].image;
                    pre_index = (pre_index + 1)%image_buffer_count;
                  }

                event->AddFrames( pre_event_images, images, timestamps );
              }
            }
          }
        }
        if ( score )
        {
          if ( (state == IDLE || state == TAPE || state == PREALARM ) )
          {
            if ( Event::PreAlarmCount() >= (alarm_frame_count-1) )
            {
              Info( "%s: %03d - Gone into alarm state", name, image_count );
              shared_data->state = state = ALARM;
              if ( signal_change || (function != MOCORD && state != ALERT) )
              {
                int pre_index;
                int pre_event_images = pre_event_count;

                if ( analysis_fps )
                {
                  // If analysis fps is set,
                  // compute the index for pre event images in the dedicated buffer
                  pre_index = image_count%pre_event_buffer_count;

                  // Seek forward the next filled slot in to the buffer (oldest data)
                  // from the current position
                  while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec )
                  {
                    pre_index = (pre_index + 1)%pre_event_buffer_count;
                    // Slot is empty, removing image from counter
                    pre_event_images--;
                  }

                  event = new Event( this, *(pre_event_buffer[pre_index].timestamp), cause, noteSetMap );
                }
                else
                {
                  // If analysis fps is not set (analysis performed at capturing framerate),
                  // compute the index for pre event images in the capturing buffer
                  if ( alarm_frame_count > 1 )
                    pre_index = ((index + image_buffer_count) - ((alarm_frame_count - 1) + pre_event_count))%image_buffer_count;
                  else
                    pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count;

                  // Seek forward the next filled slot in to the buffer (oldest data)
                  // from the current position
                  while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec )
                  {
                    pre_index = (pre_index + 1)%image_buffer_count;
                    // Slot is empty, removing image from counter
                    pre_event_images--;
                  }

                  event = new Event( this, *(image_buffer[pre_index].timestamp), cause, noteSetMap );
                }
                shared_data->last_event = event->Id();

                Info( "%s: %03d - Opening new event %d, alarm start", name, image_count, event->Id() );

                if ( pre_event_images )
                {
                  if ( analysis_fps )
                    for ( int i = 0; i < pre_event_images; i++ )
                    {
                      timestamps[i] = pre_event_buffer[pre_index].timestamp;
                      images[i] = pre_event_buffer[pre_index].image;
                      pre_index = (pre_index + 1)%pre_event_buffer_count;
                    }
                  else
                    for ( int i = 0; i < pre_event_images; i++ )
                    {
                      timestamps[i] = image_buffer[pre_index].timestamp;
                      images[i] = image_buffer[pre_index].image;
                      pre_index = (pre_index + 1)%image_buffer_count;
                    }

                  event->AddFrames( pre_event_images, images, timestamps );
                }
                if ( alarm_frame_count )
                {
                  event->SavePreAlarmFrames();
                }
              }
            }
            else if ( state != PREALARM )
            {
              Info( "%s: %03d - Gone into prealarm state", name, image_count );
              shared_data->state = state = PREALARM;
            }
          }
          else if ( state == ALERT )
          {
            Info( "%s: %03d - Gone back into alarm state", name, image_count );
            shared_data->state = state = ALARM;
          }
          last_alarm_count = image_count;
        }
        else
        {
          if ( state == ALARM )
          {
            Info( "%s: %03d - Gone into alert state", name, image_count );
            shared_data->state = state = ALERT;
          }
          else if ( state == ALERT )
          {
            if ( image_count-last_alarm_count > post_event_count )
            {
              Info( "%s: %03d - Left alarm state (%d) - %d(%d) images", name, image_count, event->Id(), event->Frames(), event->AlarmFrames() );
              //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE )
              if ( function != MOCORD || event_close_mode == CLOSE_ALARM )
              {
                shared_data->state = state = IDLE;
                Info( "%s: %03d - Closing event %d, alarm end%s", name, image_count, event->Id(), (function==MOCORD)?", section truncated":"" );
                closeEvent();
              }
              else
              {
                shared_data->state = state = TAPE;
              }
            }
          }
          if ( state == PREALARM )
          {
            if ( function != MOCORD )
            {
              shared_data->state = state = IDLE;
            }
            else
            {
              shared_data->state = state = TAPE;
            }
          }
          if ( Event::PreAlarmCount() )
            Event::EmptyPreAlarmFrames();
        }
        if ( state != IDLE )
        {
          if ( state == PREALARM || state == ALARM )
          {
            if ( config.create_analysis_images )
            {
              bool got_anal_image = false;
              alarm_image.Assign( *snap_image );
              for( int i = 0; i < n_zones; i++ )
              {
                if ( zones[i]->Alarmed() )
                {
                  if ( zones[i]->AlarmImage() )
                  {
                    alarm_image.Overlay( *(zones[i]->AlarmImage()) );
                    got_anal_image = true;
                  }
                  if ( config.record_event_stats && state == ALARM )
                  {
                    zones[i]->RecordStats( event );
                  }
                }
              }
              if ( got_anal_image )
              {
                if ( state == PREALARM )
                  Event::AddPreAlarmFrame( snap_image, *timestamp, score, &alarm_image );
                else
                  event->AddFrame( snap_image, *timestamp, score, &alarm_image );
              }
              else
              {
                if ( state == PREALARM )
                  Event::AddPreAlarmFrame( snap_image, *timestamp, score );
                else
                  event->AddFrame( snap_image, *timestamp, score );
              }
            }
            else
            {
              for( int i = 0; i < n_zones; i++ )
              {
                if ( zones[i]->Alarmed() )
                {
                  if ( config.record_event_stats && state == ALARM )
                  {
                    zones[i]->RecordStats( event );
                  }
                }
              }
              if ( state == PREALARM )
                Event::AddPreAlarmFrame( snap_image, *timestamp, score );
              else
                event->AddFrame( snap_image, *timestamp, score );
            }
            if ( event && noteSetMap.size() > 0 )
              event->updateNotes( noteSetMap );
          }
          else if ( state == ALERT )
          {
            event->AddFrame( snap_image, *timestamp );
            if ( noteSetMap.size() > 0 )
              event->updateNotes( noteSetMap );
          }
          else if ( state == TAPE )
          {
            if ( !(image_count%(frame_skip+1)) )
            {
              if ( config.bulk_frame_interval > 1 )
              {
                event->AddFrame( snap_image, *timestamp, (event->Frames()<pre_event_count?0:-1) );
              }
              else
              {
                event->AddFrame( snap_image, *timestamp );
              }
            }
          }
        }
      }
    }
    else
    {
      if ( event )
      {
        Info( "%s: %03d - Closing event %d, trigger off", name, image_count, event->Id() );
        closeEvent();
      }
      shared_data->state = state = IDLE;
      last_section_mod = 0;
    }
    if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) )
    {
      if ( state == ALARM ) {
         ref_image.Blend( *snap_image, alarm_ref_blend_perc );
      } else {
         ref_image.Blend( *snap_image, ref_blend_perc );
      }
    }
    last_signal = signal;
  }

  shared_data->last_read_index = index%image_buffer_count;
  //shared_data->last_read_time = image_buffer[index].timestamp->tv_sec;
  shared_data->last_read_time = now.tv_sec;

  if ( analysis_fps )
  {
    // If analysis fps is set, add analysed image to dedicated pre event buffer
    int pre_index = image_count%pre_event_buffer_count;
    pre_event_buffer[pre_index].image->Assign(*snap->image);
    memcpy( pre_event_buffer[pre_index].timestamp, snap->timestamp, sizeof(struct timeval) );
  }

  image_count++;

  return( true );
}

void Monitor::Reload()
{
  Debug( 1, "Reloading monitor %s", name );

  if ( event )
    Info( "%s: %03d - Closing event %d, reloading", name, image_count, event->Id() );

  closeEvent();

  static char sql[ZM_SQL_MED_BUFSIZ];
  snprintf( sql, sizeof(sql), "select Function+0, Enabled, LinkedMonitors, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = '%d'", id );

  if ( mysql_query( &dbconn, sql ) )
  {
    Error( "Can't run query: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }

  MYSQL_RES *result = mysql_store_result( &dbconn );
  if ( !result )
  {
    Error( "Can't use query result: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  int n_monitors = mysql_num_rows( result );
  if ( n_monitors != 1 )
  {
    Error( "Bogus number of monitors, %d, returned. Can't reload", n_monitors ); 
    return;
  }

  if ( MYSQL_ROW dbrow = mysql_fetch_row( result ) )
  {
    int index = 0;
    function = (Function)atoi(dbrow[index++]);
    enabled = atoi(dbrow[index++]);
    const char *p_linked_monitors = dbrow[index++];
    strncpy( event_prefix, dbrow[index++], sizeof(event_prefix) );
    strncpy( label_format, dbrow[index++], sizeof(label_format) );
    label_coord = Coord( atoi(dbrow[index]), atoi(dbrow[index+1]) ); index += 2;
    label_size = atoi(dbrow[index++]);
    warmup_count = atoi(dbrow[index++]);
    pre_event_count = atoi(dbrow[index++]);
    post_event_count = atoi(dbrow[index++]);
    alarm_frame_count = atoi(dbrow[index++]);
    section_length = atoi(dbrow[index++]);
    frame_skip = atoi(dbrow[index++]);
    motion_frame_skip = atoi(dbrow[index++]);
    analysis_fps = dbrow[index] ? strtod(dbrow[index], NULL) : 0; index++;
    analysis_update_delay = strtoul(dbrow[index++], NULL, 0);
    capture_delay = (dbrow[index]&&atof(dbrow[index])>0.0)?int(DT_PREC_3/atof(dbrow[index])):0; index++;
    alarm_capture_delay = (dbrow[index]&&atof(dbrow[index])>0.0)?int(DT_PREC_3/atof(dbrow[index])):0; index++;
    fps_report_interval = atoi(dbrow[index++]);
    ref_blend_perc = atoi(dbrow[index++]);
    alarm_ref_blend_perc = atoi(dbrow[index++]);
    track_motion = atoi(dbrow[index++]);
    

    if ( dbrow[index][0] == '#' )
      signal_check_colour = strtol(dbrow[index]+1,0,16);
    else
      signal_check_colour = strtol(dbrow[index],0,16);
    index++;

    shared_data->state = state = IDLE;
    shared_data->alarm_x = shared_data->alarm_y = -1;
    if ( enabled )
      shared_data->active = true;
    ready_count = image_count+warmup_count;

    ReloadLinkedMonitors( p_linked_monitors );
  }
  if ( mysql_errno( &dbconn ) )
  {
    Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  mysql_free_result( result );

  ReloadZones();
}

void Monitor::ReloadZones()
{
  Debug( 1, "Reloading zones for monitor %s", name );
  for( int i = 0; i < n_zones; i++ )
  {
    delete zones[i];
  }
  delete[] zones;
  zones = 0;
  n_zones = Zone::Load( this, zones );
  //DumpZoneImage();
}

void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors )
{
  Debug( 1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors );
  if ( n_linked_monitors )
  {
    for( int i = 0; i < n_linked_monitors; i++ )
    {
      delete linked_monitors[i];
    }
    delete[] linked_monitors;
    linked_monitors = 0;
  }

  n_linked_monitors = 0;
  if ( p_linked_monitors )
  {
    int n_link_ids = 0;
    unsigned int link_ids[256];

    char link_id_str[8];
    char *dest_ptr = link_id_str;
    const char *src_ptr = p_linked_monitors;
    while( 1 )
    {
      dest_ptr = link_id_str;
      while( *src_ptr >= '0' && *src_ptr <= '9' )
      {
        if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) )
        {
          *dest_ptr++ = *src_ptr++;
        }
        else
        {
          break;
        }
      }
      // Add the link monitor
      if ( dest_ptr != link_id_str )
      {
        *dest_ptr = '\0';
        unsigned int link_id = atoi(link_id_str);
        if ( link_id > 0 && link_id != id)
        {
          Debug( 3, "Found linked monitor id %d", link_id );
          int j;
          for ( j = 0; j < n_link_ids; j++ )
          {
            if ( link_ids[j] == link_id )
              break;
          }
          if ( j == n_link_ids ) // Not already found
          {
            link_ids[n_link_ids++] = link_id;
          }
        }
      }
      if ( !*src_ptr )
        break;
      while( *src_ptr && (*src_ptr < '0' || *src_ptr > '9') )
        src_ptr++;
      if ( !*src_ptr )
        break;
    }
    if ( n_link_ids > 0 )
    {
      Debug( 1, "Linking to %d monitors", n_link_ids );
      linked_monitors = new MonitorLink *[n_link_ids];
      int count = 0;
      for ( int i = 0; i < n_link_ids; i++ )
      {
        Debug( 1, "Checking linked monitor %d", link_ids[i] );

        static char sql[ZM_SQL_SML_BUFSIZ];
        snprintf( sql, sizeof(sql), "select Id, Name from Monitors where Id = %d and Function != 'None' and Function != 'Monitor' and Enabled = 1", link_ids[i] );
        if ( mysql_query( &dbconn, sql ) )
        {
          Error( "Can't run query: %s", mysql_error( &dbconn ) );
          exit( mysql_errno( &dbconn ) );
        }

        MYSQL_RES *result = mysql_store_result( &dbconn );
        if ( !result )
        {
          Error( "Can't use query result: %s", mysql_error( &dbconn ) );
          exit( mysql_errno( &dbconn ) );
        }
        int n_monitors = mysql_num_rows( result );
        if ( n_monitors == 1 )
        {
          MYSQL_ROW dbrow = mysql_fetch_row( result );
          Debug( 1, "Linking to monitor %d", link_ids[i] );
          linked_monitors[count++] = new MonitorLink( link_ids[i], dbrow[1] );
        }
        else
        {
          Warning( "Can't link to monitor %d, invalid id, function or not enabled", link_ids[i] );
        }
        mysql_free_result( result );
      }
      n_linked_monitors = count;
    }
  }
}

#if ZM_HAS_V4L
int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose purpose )
{
  std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'";
  if ( device[0] ) {
    sql += " AND Device='";
    sql += device;
    sql += "'";
  }
  if ( staticConfig.SERVER_ID ) {
    sql += stringtf( " AND ServerId=%d", staticConfig.SERVER_ID );
  }
  Debug( 1, "Loading Local Monitors with %s", sql.c_str() );

  MYSQL_RES *result = zmDbFetch( sql.c_str() );
  if ( !result ) {
    Error( "Can't load local monitors: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  int n_monitors = mysql_num_rows( result );
  Debug( 1, "Got %d monitors", n_monitors );
  delete[] monitors;
  monitors = new Monitor *[n_monitors];
  for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
  {
    int col = 0;

    int id = atoi(dbrow[col]); col++;
    const char *name = dbrow[col]; col++;
    unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
    int function = atoi(dbrow[col]); col++;
    int enabled = atoi(dbrow[col]); col++;
    const char *linked_monitors = dbrow[col]; col++;

    const char *device = dbrow[col]; col++;
    int channel = atoi(dbrow[col]); col++;
    int format = atoi(dbrow[col]); col++;
    bool v4l_multi_buffer = config.v4l_multi_buffer;
    if ( dbrow[col] ) {
      if (*dbrow[col] == '0' ) {
        v4l_multi_buffer = false;
      } else if ( *dbrow[col] == '1' ) {
        v4l_multi_buffer = true;
      } 
    }
    col++;
    
    int v4l_captures_per_frame = 0;
    if ( dbrow[col] ) {
       v4l_captures_per_frame = atoi(dbrow[col]);
    } else {
      v4l_captures_per_frame = config.captures_per_frame;
    }
Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
    col++;
    const char *method = dbrow[col]; col++;

    int width = atoi(dbrow[col]); col++;
    int height = atoi(dbrow[col]); col++;
    int colours = atoi(dbrow[col]); col++;
    int palette = atoi(dbrow[col]); col++;
    Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
    unsigned int deinterlacing = atoi(dbrow[col]); col++;
    int brightness = atoi(dbrow[col]); col++;
    int contrast = atoi(dbrow[col]); col++;
    int hue = atoi(dbrow[col]); col++;
    int colour = atoi(dbrow[col]); col++;

    const char *event_prefix = dbrow[col]; col++;
    const char *label_format = dbrow[col]; col++;

    int label_x = atoi(dbrow[col]); col++;
    int label_y = atoi(dbrow[col]); col++;
    int label_size = atoi(dbrow[col]); col++;

    int image_buffer_count = atoi(dbrow[col]); col++;
    int warmup_count = atoi(dbrow[col]); col++;
    int pre_event_count = atoi(dbrow[col]); col++;
    int post_event_count = atoi(dbrow[col]); col++;
    int stream_replay_buffer = atoi(dbrow[col]); col++;
    int alarm_frame_count = atoi(dbrow[col]); col++;
    int section_length = atoi(dbrow[col]); col++;
    int frame_skip = atoi(dbrow[col]); col++;
    int motion_frame_skip = atoi(dbrow[col]); col++;
    double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++;
    unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0);
    int capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
    int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
    int fps_report_interval = atoi(dbrow[col]); col++;
    int ref_blend_perc = atoi(dbrow[col]); col++;
    int alarm_ref_blend_perc = atoi(dbrow[col]); col++;
    int track_motion = atoi(dbrow[col]); col++;

    int signal_check_colour;
    if ( dbrow[col][0] == '#' )
      signal_check_colour = strtol(dbrow[col]+1,0,16);
    else
      signal_check_colour = strtol(dbrow[col],0,16);
    col++;
    bool embed_exif = (*dbrow[col] != '0'); col++;

    int extras = (deinterlacing>>24)&0xff;

    Camera *camera = new LocalCamera(
      id,
      device,
      channel,
      format,
      v4l_multi_buffer,
      v4l_captures_per_frame,
      method,
      width,
      height,
      colours,
      palette,
      brightness,
      contrast,
      hue,
      colour,
      purpose==CAPTURE,
      extras
    );

    monitors[i] = new Monitor(
      id,
      name,
      server_id,
      function,
      enabled,
      linked_monitors,
      camera,
      orientation,
      deinterlacing,
      event_prefix,
      label_format,
      Coord( label_x, label_y ),
      label_size,
      image_buffer_count,
      warmup_count,
      pre_event_count,
      post_event_count,
      stream_replay_buffer,
      alarm_frame_count,
      section_length,
      frame_skip,
      motion_frame_skip,
      analysis_fps,
      analysis_update_delay,
      capture_delay,
      alarm_capture_delay,
      fps_report_interval,
      ref_blend_perc,
      alarm_ref_blend_perc,
      track_motion,
      signal_check_colour,
      embed_exif,
      purpose,
      0,
      0
    );
    Zone **zones = 0;
    int n_zones = Zone::Load( monitors[i], zones );
    monitors[i]->AddZones( n_zones, zones );
    monitors[i]->AddPrivacyBitmask( zones );
    Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones );
  }
  if ( mysql_errno( &dbconn ) )
  {
    Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  // Yadda yadda
  mysql_free_result( result );

  return( n_monitors );
}
#endif // ZM_HAS_V4L

int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose )
{
  std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'";
  if ( staticConfig.SERVER_ID ) {
    sql += stringtf( " AND ServerId=%d", staticConfig.SERVER_ID );
  }

  if ( protocol ) {
    sql += stringtf(" AND Protocol = '%s' and Host = '%s' and Port = '%s' and Path = '%s'", protocol, host, port, path );
  }

  Debug( 1, "Loading Remote Monitors with %s", sql.c_str() );
  MYSQL_RES *result = zmDbFetch( sql.c_str() );
  if ( !result ) {
    Error( "Can't use query result: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  int n_monitors = mysql_num_rows( result );
  Debug( 1, "Got %d monitors", n_monitors );
  delete[] monitors;
  monitors = new Monitor *[n_monitors];
  for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
  {
    int col = 0;

    int id = atoi(dbrow[col]); col++;
    std::string name = dbrow[col]; col++;
    unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
    int function = atoi(dbrow[col]); col++;
    int enabled = atoi(dbrow[col]); col++;
    const char *linked_monitors = dbrow[col]; col++;

    std::string protocol = dbrow[col]; col++;
    std::string method = dbrow[col]; col++;
    std::string host = dbrow[col]; col++;
    std::string port = dbrow[col]; col++;
    std::string path = dbrow[col]; col++;

    int width = atoi(dbrow[col]); col++;
    int height = atoi(dbrow[col]); col++;
    int colours = atoi(dbrow[col]); col++;
    /* int palette = atoi(dbrow[col]); */ col++;
    Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
    unsigned int deinterlacing = atoi(dbrow[col]); col++;
    bool rtsp_describe = (*dbrow[col] != '0'); col++;
    int brightness = atoi(dbrow[col]); col++;
    int contrast = atoi(dbrow[col]); col++;
    int hue = atoi(dbrow[col]); col++;
    int colour = atoi(dbrow[col]); col++;

    std::string event_prefix = dbrow[col]; col++;
    std::string label_format = dbrow[col]; col++;

    int label_x = atoi(dbrow[col]); col++;
    int label_y = atoi(dbrow[col]); col++;
    int label_size = atoi(dbrow[col]); col++;

    int image_buffer_count = atoi(dbrow[col]); col++;
    int warmup_count = atoi(dbrow[col]); col++;
    int pre_event_count = atoi(dbrow[col]); col++;
    int post_event_count = atoi(dbrow[col]); col++;
    int stream_replay_buffer = atoi(dbrow[col]); col++;
    int alarm_frame_count = atoi(dbrow[col]); col++;
    int section_length = atoi(dbrow[col]); col++;
    int frame_skip = atoi(dbrow[col]); col++;
    int motion_frame_skip = atoi(dbrow[col]); col++;
    double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++;
    unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0);
    int capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
    int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
    int fps_report_interval = atoi(dbrow[col]); col++;
    int ref_blend_perc = atoi(dbrow[col]); col++;
    int alarm_ref_blend_perc = atoi(dbrow[col]); col++;
    int track_motion = atoi(dbrow[col]); col++;
    bool embed_exif = (*dbrow[col] != '0'); col++;

    Camera *camera = 0;
    if ( protocol == "http" )
    {
      camera = new RemoteCameraHttp(
        id,
        method,
        host, // Host
        port, // Port
        path, // Path
        width,
        height,
        colours,
        brightness,
        contrast,
        hue,
        colour,
        purpose==CAPTURE
      );
    }
#if HAVE_LIBAVFORMAT
    else if ( protocol == "rtsp" )
    {
      camera = new RemoteCameraRtsp(
        id,
        method,
        host, // Host
        port, // Port
        path, // Path
        width,
        height,
        rtsp_describe,
        colours,
        brightness,
        contrast,
        hue,
        colour,
        purpose==CAPTURE
      );
    }
#endif // HAVE_LIBAVFORMAT
    else
    {
      Fatal( "Unexpected remote camera protocol '%s'", protocol.c_str() );
    }

    monitors[i] = new Monitor(
      id,
      name.c_str(),
      server_id,
      function,
      enabled,
      linked_monitors,
      camera,
      orientation,
      deinterlacing,
      event_prefix.c_str(),
      label_format.c_str(),
      Coord( label_x, label_y ),
      label_size,
      image_buffer_count,
      warmup_count,
      pre_event_count,
      post_event_count,
      stream_replay_buffer,
      alarm_frame_count,
      section_length,
      frame_skip,
      motion_frame_skip,
      analysis_fps,
      analysis_update_delay,
      capture_delay,
      alarm_capture_delay,
      fps_report_interval,
      ref_blend_perc,
      alarm_ref_blend_perc,
      track_motion,
      RGB_WHITE,
      embed_exif,
      purpose,
      0,
      0

    );
    Zone **zones = 0;
    int n_zones = Zone::Load( monitors[i], zones );
    monitors[i]->AddZones( n_zones, zones );
    monitors[i]->AddPrivacyBitmask( zones );
    Debug( 1, "Loaded monitor %d(%s), %d zones", id, name.c_str(), n_zones );
  }
  if ( mysql_errno( &dbconn ) )
  {
    Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  // Yadda yadda
  mysql_free_result( result );

  return( n_monitors );
}

int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose )
{
    std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'";
  if ( file[0] ) {
    sql += " AND Path='";
    sql += file;
    sql += "'";
  }
  if ( staticConfig.SERVER_ID ) {
    sql += stringtf( " AND ServerId=%d", staticConfig.SERVER_ID );
  }
  Debug( 1, "Loading File Monitors with %s", sql.c_str() );
  MYSQL_RES *result = zmDbFetch( sql.c_str() );
  if ( !result )
  {
    Error( "Can't use query result: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  int n_monitors = mysql_num_rows( result );
  Debug( 1, "Got %d monitors", n_monitors );
  delete[] monitors;
  monitors = new Monitor *[n_monitors];
  for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
  {
    int col = 0;

    int id = atoi(dbrow[col]); col++;
    const char *name = dbrow[col]; col++;
    unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
    int function = atoi(dbrow[col]); col++;
    int enabled = atoi(dbrow[col]); col++;
    const char *linked_monitors = dbrow[col]; col++;

    const char *path = dbrow[col]; col++;

    int width = atoi(dbrow[col]); col++;
    int height = atoi(dbrow[col]); col++;
    int colours = atoi(dbrow[col]); col++;
    /* int palette = atoi(dbrow[col]); */ col++;
    Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
    unsigned int deinterlacing = atoi(dbrow[col]); col++;
    int brightness = atoi(dbrow[col]); col++;
    int contrast = atoi(dbrow[col]); col++;
    int hue = atoi(dbrow[col]); col++;
    int colour = atoi(dbrow[col]); col++;

    const char *event_prefix = dbrow[col]; col++;
    const char *label_format = dbrow[col]; col++;

    int label_x = atoi(dbrow[col]); col++;
    int label_y = atoi(dbrow[col]); col++;
    int label_size = atoi(dbrow[col]); col++;

    int image_buffer_count = atoi(dbrow[col]); col++;
    int warmup_count = atoi(dbrow[col]); col++;
    int pre_event_count = atoi(dbrow[col]); col++;
    int post_event_count = atoi(dbrow[col]); col++;
    int stream_replay_buffer = atoi(dbrow[col]); col++;
    int alarm_frame_count = atoi(dbrow[col]); col++;
    int section_length = atoi(dbrow[col]); col++;
    int frame_skip = atoi(dbrow[col]); col++;
    int motion_frame_skip = atoi(dbrow[col]); col++;
    double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++;
    unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0);
    int capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
    int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
    int fps_report_interval = atoi(dbrow[col]); col++;
    int ref_blend_perc = atoi(dbrow[col]); col++;
    int alarm_ref_blend_perc = atoi(dbrow[col]); col++;
    int track_motion = atoi(dbrow[col]); col++;
    bool embed_exif = (*dbrow[col] != '0'); col++;

    Camera *camera = new FileCamera(
      id,
      path, // File
      width,
      height,
      colours,
      brightness,
      contrast,
      hue,
      colour,
      purpose==CAPTURE
    );

    monitors[i] = new Monitor(
      id,
      name,
      server_id,
      function,
      enabled,
      linked_monitors,
      camera,
      orientation,
      deinterlacing,
      event_prefix,
      label_format,
      Coord( label_x, label_y ),
      label_size,
      image_buffer_count,
      warmup_count,
      pre_event_count,
      post_event_count,
      stream_replay_buffer,
      alarm_frame_count,
      section_length,
      frame_skip,
      motion_frame_skip,
      analysis_fps,
      analysis_update_delay,
      capture_delay,
      alarm_capture_delay,
      fps_report_interval,
      ref_blend_perc,
      alarm_ref_blend_perc,
      track_motion,
      embed_exif,
      RGB_WHITE,
      purpose,
      0,
      0
    );
    Zone **zones = 0;
    int n_zones = Zone::Load( monitors[i], zones );
    monitors[i]->AddZones( n_zones, zones );
    monitors[i]->AddPrivacyBitmask( zones );
    Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones );
  }
  if ( mysql_errno( &dbconn ) )
  {
    Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  // Yadda yadda
  mysql_free_result( result );

  return( n_monitors );
}

#if HAVE_LIBAVFORMAT
int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose purpose )
{
    std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'";
  if ( file[0] ) {
    sql += " AND Path = '";
    sql += file;
    sql += "'";
  }
  if ( staticConfig.SERVER_ID ) {
    sql += stringtf( " AND ServerId=%d", staticConfig.SERVER_ID );
  }
  Debug( 1, "Loading FFMPEG Monitors with %s", sql.c_str() );
  MYSQL_RES *result = zmDbFetch( sql.c_str() );
  if ( ! result ) {
    Error( "Cannot load FfmpegMonitors" );
    exit( mysql_errno( &dbconn ) );
  }

  int n_monitors = mysql_num_rows( result );
  Debug( 1, "Got %d monitors", n_monitors );
  delete[] monitors;
  monitors = new Monitor *[n_monitors];
  for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
  {
    int col = 0;

    int id = atoi(dbrow[col]); col++;
    const char *name = dbrow[col]; col++;
    unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
    int function = atoi(dbrow[col]); col++;
    int enabled = atoi(dbrow[col]); col++;
    const char *linked_monitors = dbrow[col]; col++;

    const char *path = dbrow[col]; col++;
    const char *method = dbrow[col]; col++;
    const char *options = dbrow[col]; col++;

    int width = atoi(dbrow[col]); col++;
    int height = atoi(dbrow[col]); col++;
    int colours = atoi(dbrow[col]); col++;
    /* int palette = atoi(dbrow[col]); */ col++;
    Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
    unsigned int deinterlacing = atoi(dbrow[col]); col++;
    int brightness = atoi(dbrow[col]); col++;
    int contrast = atoi(dbrow[col]); col++;
    int hue = atoi(dbrow[col]); col++;
    int colour = atoi(dbrow[col]); col++;

    const char *event_prefix = dbrow[col]; col++;
    const char *label_format = dbrow[col]; col++;

    int label_x = atoi(dbrow[col]); col++;
    int label_y = atoi(dbrow[col]); col++;
    int label_size = atoi(dbrow[col]); col++;

    int image_buffer_count = atoi(dbrow[col]); col++;
    int warmup_count = atoi(dbrow[col]); col++;
    int pre_event_count = atoi(dbrow[col]); col++;
    int post_event_count = atoi(dbrow[col]); col++;
    int stream_replay_buffer = atoi(dbrow[col]); col++;
    int alarm_frame_count = atoi(dbrow[col]); col++;
    int section_length = atoi(dbrow[col]); col++;
    int frame_skip = atoi(dbrow[col]); col++;
    int motion_frame_skip = atoi(dbrow[col]); col++;
    double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++;
    unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0);
    int capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
    int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
    int fps_report_interval = atoi(dbrow[col]); col++;
    int ref_blend_perc = atoi(dbrow[col]); col++;
    int alarm_ref_blend_perc = atoi(dbrow[col]); col++;
    int track_motion = atoi(dbrow[col]); col++;
    bool embed_exif = (*dbrow[col] != '0'); col++;

    Camera *camera = new FfmpegCamera(
      id,
      path, // File
      method,
      options,
      width,
      height,
      colours,
      brightness,
      contrast,
      hue,
      colour,
      purpose==CAPTURE
    );

    monitors[i] = new Monitor(
      id,
      name,
      server_id,
      function,
      enabled,
      linked_monitors,
      camera,
      orientation,
      deinterlacing,
      event_prefix,
      label_format,
      Coord( label_x, label_y ),
      label_size,
      image_buffer_count,
      warmup_count,
      pre_event_count,
      post_event_count,
      stream_replay_buffer,
      alarm_frame_count,
      section_length,
      frame_skip,
      motion_frame_skip,
      analysis_fps,
      analysis_update_delay,
      capture_delay,
      alarm_capture_delay,
      fps_report_interval,
      ref_blend_perc,
      alarm_ref_blend_perc,
      track_motion,
      embed_exif,
      RGB_WHITE,
      purpose,
      0,
      0
    );
    Zone **zones = 0;
    int n_zones = Zone::Load( monitors[i], zones );
    monitors[i]->AddZones( n_zones, zones );
    monitors[i]->AddPrivacyBitmask( zones );
    Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones );
  }
  if ( mysql_errno( &dbconn ) )
  {
    Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  // Yadda yadda
  mysql_free_result( result );

  return( n_monitors );
}
#endif // HAVE_LIBAVFORMAT

Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose )
{
  std::string sql = stringtf( "select Id, Name, ServerId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id );

  MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() );
  if ( ! dbrow ) {
    Error( "Can't use query result: %s", mysql_error( &dbconn ) );
    exit( mysql_errno( &dbconn ) );
  }
  Monitor *monitor = 0;
  unsigned int col = 0;

  unsigned int id = atoi(dbrow[col]); col++;
  std::string name = dbrow[col]; col++;
  unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
  std::string type = dbrow[col]; col++;
  int function = atoi(dbrow[col]); col++;
  int enabled = atoi(dbrow[col]); col++;
  std::string linked_monitors = dbrow[col]; col++;

  std::string device = dbrow[col]; col++;
  int channel = atoi(dbrow[col]); col++;
  int format = atoi(dbrow[col]); col++;

  bool v4l_multi_buffer = config.v4l_multi_buffer;
  if ( dbrow[col] ) {
    if (*dbrow[col] == '0' ) {
      v4l_multi_buffer = false;
    } else if ( *dbrow[col] == '1' ) {
      v4l_multi_buffer = true;
    }
  }
  col++;

  int v4l_captures_per_frame = 0;
  if ( dbrow[col] ) {
     v4l_captures_per_frame = atoi(dbrow[col]);
  } else {
    v4l_captures_per_frame = config.captures_per_frame;
  }
Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
  col++;

  std::string protocol = dbrow[col]; col++;
  std::string method = dbrow[col]; col++;
  std::string host = dbrow[col]; col++;
  std::string port = dbrow[col]; col++;
  std::string path = dbrow[col]; col++;
  std::string options = dbrow[col]; col++;
  std::string user = dbrow[col]; col++;
  std::string pass = dbrow[col]; col++;

  int width = atoi(dbrow[col]); col++;
  int height = atoi(dbrow[col]); col++;
  int colours = atoi(dbrow[col]); col++;
  int palette = atoi(dbrow[col]); col++;
  Orientation orientation = (Orientation)atoi(dbrow[col]); col++;
  unsigned int deinterlacing = atoi(dbrow[col]); col++;
  bool rtsp_describe = (*dbrow[col] != '0'); col++;
  int brightness = atoi(dbrow[col]); col++;
  int contrast = atoi(dbrow[col]); col++;
  int hue = atoi(dbrow[col]); col++;
  int colour = atoi(dbrow[col]); col++;

  std::string event_prefix = dbrow[col]; col++;
  std::string label_format = dbrow[col]; col++;

  int label_x = atoi(dbrow[col]); col++;
  int label_y = atoi(dbrow[col]); col++;
  int label_size = atoi(dbrow[col]); col++;

  int image_buffer_count = atoi(dbrow[col]); col++;
  int warmup_count = atoi(dbrow[col]); col++;
  int pre_event_count = atoi(dbrow[col]); col++;
  int post_event_count = atoi(dbrow[col]); col++;
  int stream_replay_buffer = atoi(dbrow[col]); col++;
  int alarm_frame_count = atoi(dbrow[col]); col++;
  int section_length = atoi(dbrow[col]); col++;
  int frame_skip = atoi(dbrow[col]); col++;
  int motion_frame_skip = atoi(dbrow[col]); col++;
  double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++;
  unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0);
  int capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
  int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
  int fps_report_interval = atoi(dbrow[col]); col++;
  int ref_blend_perc = atoi(dbrow[col]); col++;
  int alarm_ref_blend_perc = atoi(dbrow[col]); col++;
  int track_motion = atoi(dbrow[col]); col++;

  int signal_check_colour;
  if ( dbrow[col][0] == '#' )
    signal_check_colour = strtol(dbrow[col]+1,0,16);
  else
    signal_check_colour = strtol(dbrow[col],0,16);
  col++;
  bool embed_exif = (*dbrow[col] != '0'); col++;

  int extras = (deinterlacing>>24)&0xff;

  Camera *camera = 0;
  if ( type == "Local" )
  {
#if ZM_HAS_V4L
    camera = new LocalCamera(
      id,
      device.c_str(),
      channel,
      format,
      v4l_multi_buffer,
      v4l_captures_per_frame,
      method,
      width,
      height,
      colours,
      palette,
      brightness,
      contrast,
      hue,
      colour,
      purpose==CAPTURE,
      extras
    );
#else // ZM_HAS_V4L
    Fatal( "You must have video4linux libraries and headers installed to use local analog or USB cameras for monitor %d", id );
#endif // ZM_HAS_V4L
  }
  else if ( type == "Remote" )
  {
    if ( protocol == "http" )
    {
      camera = new RemoteCameraHttp(
        id,
        method.c_str(),
        host.c_str(),
        port.c_str(),
        path.c_str(),
        width,
        height,
        colours,
        brightness,
        contrast,
        hue,
        colour,
        purpose==CAPTURE
      );
    }
    else if ( protocol == "rtsp" )
    {
#if HAVE_LIBAVFORMAT
      camera = new RemoteCameraRtsp(
        id,
        method.c_str(),
        host.c_str(),
        port.c_str(),
        path.c_str(),
        width,
        height,
        rtsp_describe,
        colours,
        brightness,
        contrast,
        hue,
        colour,
        purpose==CAPTURE
      );
#else // HAVE_LIBAVFORMAT
      Fatal( "You must have ffmpeg libraries installed to use remote camera protocol '%s' for monitor %d", protocol.c_str(), id );
#endif // HAVE_LIBAVFORMAT
    }
    else
    {
      Fatal( "Unexpected remote camera protocol '%s' for monitor %d", protocol.c_str(), id );
    }
  }
  else if ( type == "File" )
  {
    camera = new FileCamera(
      id,
      path.c_str(),
      width,
      height,
      colours,
      brightness,
      contrast,
      hue,
      colour,
      purpose==CAPTURE
    );
  }
  else if ( type == "Ffmpeg" )
  {
#if HAVE_LIBAVFORMAT
    camera = new FfmpegCamera(
      id,
      path.c_str(),
      method,
      options,
      width,
      height,
      colours,
      brightness,
      contrast,
      hue,
      colour,
      purpose==CAPTURE
    );
#else // HAVE_LIBAVFORMAT
    Fatal( "You must have ffmpeg libraries installed to use ffmpeg cameras for monitor %d", id );
#endif // HAVE_LIBAVFORMAT
  }
  else if (type == "Libvlc")
  {
#if HAVE_LIBVLC
    camera = new LibvlcCamera(
      id,
      path.c_str(),
      method,
      options,
      width,
      height,
      colours,
      brightness,
      contrast,
      hue,
      colour,
      purpose==CAPTURE
    );
#else // HAVE_LIBVLC
    Fatal( "You must have vlc libraries installed to use vlc cameras for monitor %d", id );
#endif // HAVE_LIBVLC
  }
  else if ( type == "cURL" )
  {
#if HAVE_LIBCURL
    camera = new cURLCamera(
      id,
      path.c_str(),
      user.c_str(),
      pass.c_str(),
      width,
      height,
      colours,
      brightness,
      contrast,
      hue,
      colour,
      purpose==CAPTURE
    );
#else // HAVE_LIBCURL
    Fatal( "You must have libcurl installed to use ffmpeg cameras for monitor %d", id );
#endif // HAVE_LIBCURL
  }
  else
  {
    Fatal( "Bogus monitor type '%s' for monitor %d", type.c_str(), id );
  }
  monitor = new Monitor(
    id,
    name.c_str(),
    server_id,
    function,
    enabled,
    linked_monitors.c_str(),
    camera,
    orientation,
    deinterlacing,
    event_prefix.c_str(),
    label_format.c_str(),
    Coord( label_x, label_y ),
    label_size,
    image_buffer_count,
    warmup_count,
    pre_event_count,
    post_event_count,
    stream_replay_buffer,
    alarm_frame_count,
    section_length,
    frame_skip,
    motion_frame_skip,
    analysis_fps,
    analysis_update_delay,
    capture_delay,
    alarm_capture_delay,
    fps_report_interval,
    ref_blend_perc,
    alarm_ref_blend_perc,
    track_motion,
    signal_check_colour,
    embed_exif,
    purpose,
    0,
    0

  );

  int n_zones = 0;
  if ( load_zones )
  {
    Zone **zones = 0;
    n_zones = Zone::Load( monitor, zones );
    monitor->AddZones( n_zones, zones );
    monitor->AddPrivacyBitmask( zones );
  }
  Debug( 1, "Loaded monitor %d(%s), %d zones", id, name.c_str(), n_zones );
  return( monitor );
}

int Monitor::Capture()
{
  static int FirstCapture = 1;
  int captureResult;

  int index = image_count%image_buffer_count;
  Image* capture_image = image_buffer[index].image;

  if ( (deinterlacing & 0xff) == 4) {
    if ( FirstCapture != 1 ) {
      /* Copy the next image into the shared memory */
      capture_image->CopyBuffer(*(next_buffer.image)); 
    }

    /* Capture a new next image */
    captureResult = camera->Capture(*(next_buffer.image));

    if ( FirstCapture ) {
      FirstCapture = 0;
      return 0;
    }

  } else {
    /* Capture directly into image buffer, avoiding the need to memcpy() */
    captureResult = camera->Capture(*capture_image);
  }
  
  if ( captureResult != 0 )
  {
    // Unable to capture image for temporary reason
    // Fake a signal loss image
    Rgb signalcolor;
    signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */
    capture_image->Fill(signalcolor);
    captureResult = 0;
  } else { 
    captureResult = 1;
  }
  
  if ( captureResult == 1 )
  {
    
    /* Deinterlacing */
    if ( (deinterlacing & 0xff) == 1 ) {
      capture_image->Deinterlace_Discard();
    } else if ( (deinterlacing & 0xff) == 2 ) {
      capture_image->Deinterlace_Linear();
    } else if ( (deinterlacing & 0xff) == 3 ) {
      capture_image->Deinterlace_Blend();
    } else if ( (deinterlacing & 0xff) == 4 ) {
      capture_image->Deinterlace_4Field( next_buffer.image, (deinterlacing>>8)&0xff );
    } else if ( (deinterlacing & 0xff) == 5 ) {
      capture_image->Deinterlace_Blend_CustomRatio( (deinterlacing>>8)&0xff );
    }
    
    
    if ( orientation != ROTATE_0 )
    {
      switch ( orientation )
      {
        case ROTATE_0 :
        {
          // No action required
          break;
        }
        case ROTATE_90 :
        case ROTATE_180 :
        case ROTATE_270 :
        {
          capture_image->Rotate( (orientation-1)*90 );
          break;
        }
        case FLIP_HORI :
        case FLIP_VERT :
        {
          capture_image->Flip( orientation==FLIP_HORI );
          break;
        }
      }
    }

  }
  if ( true ) {

    if ( capture_image->Size() > camera->ImageSize() )
    {
      Error( "Captured image %d does not match expected size %d check width, height and colour depth",capture_image->Size(),camera->ImageSize() );
      return( -1 );
    }

    if ( ((unsigned int)index == shared_data->last_read_index) && (function > MONITOR) )
    {
      Warning( "Buffer overrun at index %d, image %d, slow down capture, speed up analysis or increase ring buffer size", index, image_count );
      time_t now = time(0);
      double approxFps = double(image_buffer_count)/double(now-image_buffer[index].timestamp->tv_sec);
      time_t last_read_delta = now - shared_data->last_read_time;
      if ( last_read_delta > (image_buffer_count/approxFps) )
      {
        Warning( "Last image read from shared memory %ld seconds ago, zma may have gone away", last_read_delta )
        shared_data->last_read_index = image_buffer_count;
      }
    }

    if ( privacy_bitmask )
      capture_image->MaskPrivacy( privacy_bitmask );

    gettimeofday( image_buffer[index].timestamp, NULL );
    if ( config.timestamp_on_capture )
    {
      TimestampImage( capture_image, image_buffer[index].timestamp );
    }
    shared_data->signal = CheckSignal(capture_image);
    shared_data->last_write_index = index;
    shared_data->last_write_time = image_buffer[index].timestamp->tv_sec;

    image_count++;

    if ( image_count && fps_report_interval && !(image_count%fps_report_interval) )
    {
      time_t now = image_buffer[index].timestamp->tv_sec;
      fps = double(fps_report_interval)/(now-last_fps_time);
      //Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time );
      //Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps );
      Info( "%s: %d - Capturing at %.2lf fps", name, image_count, fps );
      last_fps_time = now;
    }

    if ( shared_data->action & GET_SETTINGS )
    {
      shared_data->brightness = camera->Brightness();
      shared_data->hue = camera->Hue();
      shared_data->colour = camera->Colour();
      shared_data->contrast = camera->Contrast();
      shared_data->action &= ~GET_SETTINGS;
    }
    if ( shared_data->action & SET_SETTINGS )
    {
      camera->Brightness( shared_data->brightness );
      camera->Hue( shared_data->hue );
      camera->Colour( shared_data->colour );
      camera->Contrast( shared_data->contrast );
      shared_data->action &= ~SET_SETTINGS;
    }
    return( 0 );
  }
  shared_data->signal = false;
  return( -1 );
}

void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) const
{
  if ( label_format[0] )
  {
    // Expand the strftime macros first
    char label_time_text[256];
    strftime( label_time_text, sizeof(label_time_text), label_format, localtime( &ts_time->tv_sec ) );

    char label_text[1024];
    const char *s_ptr = label_time_text;
    char *d_ptr = label_text;
    while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) )
    {
      if ( *s_ptr == '%' )
      {
        bool found_macro = false;
        switch ( *(s_ptr+1) )
        {
          case 'N' :
            d_ptr += snprintf( d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", name );
            found_macro = true;
            break;
          case 'Q' :
            d_ptr += snprintf( d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", trigger_data->trigger_showtext );
            found_macro = true;
            break;
          case 'f' :
            d_ptr += snprintf( d_ptr, sizeof(label_text)-(d_ptr-label_text), "%02ld", ts_time->tv_usec/10000 );
            found_macro = true;
            break;
        }
        if ( found_macro )
        {
          s_ptr += 2;
          continue;
        }
      }
      *d_ptr++ = *s_ptr++;
    }
    *d_ptr = '\0';
    ts_image->Annotate( label_text, label_coord, label_size );
  }
}

bool Monitor::closeEvent()
{
  if ( event )
  {
    if ( function == RECORD || function == MOCORD )
    {
      gettimeofday( &(event->EndTime()), NULL );
    }
    delete event;
    event = 0;
    return( true );
  }
  return( false );
}

unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &zoneSet )
{
  bool alarm = false;
  unsigned int score = 0;

  if ( n_zones <= 0 ) return( alarm );

  if ( config.record_diag_images )
  {
    static char diag_path[PATH_MAX] = "";
    if ( !diag_path[0] )
    {
      snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-r.jpg", config.dir_events, id );
    }
    ref_image.WriteJpeg( diag_path );
  }

  ref_image.Delta( comp_image, &delta_image);

  if ( config.record_diag_images )
  {
    static char diag_path[PATH_MAX] = "";
    if ( !diag_path[0] )
    {
      snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-d.jpg", config.dir_events, id );
    }
    delta_image.WriteJpeg( diag_path );
  }

  // Blank out all exclusion zones
  for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
  {
    Zone *zone = zones[n_zone];
    // need previous alarmed state for preclusive zone, so don't clear just yet
    if (!zone->IsPreclusive())
      zone->ClearAlarm();
    if ( !zone->IsInactive() )
    {
      continue;
    }
    Debug( 3, "Blanking inactive zone %s", zone->Label() );
    delta_image.Fill( RGB_BLACK, zone->GetPolygon() );
  }

  // Check preclusive zones first
  for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
  {
    Zone *zone = zones[n_zone];
    if ( !zone->IsPreclusive() )
    {
      continue;
    }
    int old_zone_score = zone->Score();
    bool old_zone_alarmed = zone->Alarmed();
    Debug( 3, "Checking preclusive zone %s - old score: %d, state: %s", zone->Label(),old_zone_score, zone->Alarmed()?"alarmed":"quiet" );
    if ( zone->CheckAlarms( &delta_image ) )
    {
      alarm = true;
      score += zone->Score();
      zone->SetAlarm();
      Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() );
      zoneSet.insert( zone->Label() );
      //zone->ResetStats();
    } else {
      // check if end of alarm
      if (old_zone_alarmed) {
        Debug(3, "Preclusive Zone %s alarm Ends. Prevíous score: %d", zone->Label(), old_zone_score);
        if (old_zone_score > 0) {
          zone->SetExtendAlarmCount(zone->GetExtendAlarmFrames());
        }
        if (zone->CheckExtendAlarmCount()) {
          alarm=true;
      zone->SetAlarm();
        } else {
          zone->ClearAlarm();
        }
      } 
    }
  }

  Coord alarm_centre;
  int top_score = -1;

  if ( alarm )
  {
    alarm = false;
    score = 0;
  }
  else
  {
    // Find all alarm pixels in active zones
    for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
    {
      Zone *zone = zones[n_zone];
      if ( !zone->IsActive() || zone->IsPreclusive())
      {
        continue;
      }
      Debug( 3, "Checking active zone %s", zone->Label() );
      if ( zone->CheckAlarms( &delta_image ) )
      {
        alarm = true;
        score += zone->Score();
        zone->SetAlarm();
        Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() );
        zoneSet.insert( zone->Label() );
        if ( config.opt_control && track_motion )
        {
          if ( (int)zone->Score() > top_score )
          {
            top_score = zone->Score();
            alarm_centre = zone->GetAlarmCentre();
          }
        }
      }
    }

    if ( alarm )
    {
      for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
      {
        Zone *zone = zones[n_zone];
        if ( !zone->IsInclusive() )
        {
          continue;
        }
        Debug( 3, "Checking inclusive zone %s", zone->Label() );
        if ( zone->CheckAlarms( &delta_image ) )
        {
          alarm = true;
          score += zone->Score();
          zone->SetAlarm();
          Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() );
          zoneSet.insert( zone->Label() );
          if ( config.opt_control && track_motion )
          {
            if ( zone->Score() > (unsigned int)top_score )
            {
              top_score = zone->Score();
              alarm_centre = zone->GetAlarmCentre();
            }
          }
        }
      }
    }
    else
    {
      // Find all alarm pixels in exclusive zones
      for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
      {
        Zone *zone = zones[n_zone];
        if ( !zone->IsExclusive() )
        {
          continue;
        }
        Debug( 3, "Checking exclusive zone %s", zone->Label() );
        if ( zone->CheckAlarms( &delta_image ) )
        {
          alarm = true;
          score += zone->Score();
          zone->SetAlarm();
          Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() );
          zoneSet.insert( zone->Label() );
        }
      }
    }
  }

  if ( top_score > 0 )
  {
    shared_data->alarm_x = alarm_centre.X();
    shared_data->alarm_y = alarm_centre.Y();

    Info( "Got alarm centre at %d,%d, at count %d", shared_data->alarm_x, shared_data->alarm_y, image_count );
  }
  else
  {
    shared_data->alarm_x = shared_data->alarm_y = -1;
  }

  // This is a small and innocent hack to prevent scores of 0 being returned in alarm state
  return( score?score:alarm );
} 

bool Monitor::DumpSettings( char *output, bool verbose )
{
  output[0] = 0;

  sprintf( output+strlen(output), "Id : %d\n", id );
  sprintf( output+strlen(output), "Name : %s\n", name );
  sprintf( output+strlen(output), "Type : %s\n", camera->IsLocal()?"Local":(camera->IsRemote()?"Remote":"File") );
#if ZM_HAS_V4L
  if ( camera->IsLocal() )
  {
    sprintf( output+strlen(output), "Device : %s\n", ((LocalCamera *)camera)->Device().c_str() );
    sprintf( output+strlen(output), "Channel : %d\n", ((LocalCamera *)camera)->Channel() );
    sprintf( output+strlen(output), "Standard : %d\n", ((LocalCamera *)camera)->Standard() );
  }
  else
#endif // ZM_HAS_V4L
  if ( camera->IsRemote() )
  {
    sprintf( output+strlen(output), "Protocol : %s\n", ((RemoteCamera *)camera)->Protocol().c_str() );
    sprintf( output+strlen(output), "Host : %s\n", ((RemoteCamera *)camera)->Host().c_str() );
    sprintf( output+strlen(output), "Port : %s\n", ((RemoteCamera *)camera)->Port().c_str() );
    sprintf( output+strlen(output), "Path : %s\n", ((RemoteCamera *)camera)->Path().c_str() );
  }
  else if ( camera->IsFile() )
  {
    sprintf( output+strlen(output), "Path : %s\n", ((FileCamera *)camera)->Path() );
  }
#if HAVE_LIBAVFORMAT
  else if ( camera->IsFfmpeg() )
  {
    sprintf( output+strlen(output), "Path : %s\n", ((FfmpegCamera *)camera)->Path().c_str() );
  }
#endif // HAVE_LIBAVFORMAT
  sprintf( output+strlen(output), "Width : %d\n", camera->Width() );
  sprintf( output+strlen(output), "Height : %d\n", camera->Height() );
#if ZM_HAS_V4L
  if ( camera->IsLocal() )
  {
    sprintf( output+strlen(output), "Palette : %d\n", ((LocalCamera *)camera)->Palette() );
  }
#endif // ZM_HAS_V4L
  sprintf( output+strlen(output), "Colours : %d\n", camera->Colours() );
  sprintf( output+strlen(output), "Subpixel Order : %d\n", camera->SubpixelOrder() );
  sprintf( output+strlen(output), "Event Prefix : %s\n", event_prefix );
  sprintf( output+strlen(output), "Label Format : %s\n", label_format );
  sprintf( output+strlen(output), "Label Coord : %d,%d\n", label_coord.X(), label_coord.Y() );
  sprintf( output+strlen(output), "Label Size : %d\n", label_size );
  sprintf( output+strlen(output), "Image Buffer Count : %d\n", image_buffer_count );
  sprintf( output+strlen(output), "Warmup Count : %d\n", warmup_count );
  sprintf( output+strlen(output), "Pre Event Count : %d\n", pre_event_count );
  sprintf( output+strlen(output), "Post Event Count : %d\n", post_event_count );
  sprintf( output+strlen(output), "Stream Replay Buffer : %d\n", stream_replay_buffer );
  sprintf( output+strlen(output), "Alarm Frame Count : %d\n", alarm_frame_count );
  sprintf( output+strlen(output), "Section Length : %d\n", section_length );
  sprintf( output+strlen(output), "Maximum FPS : %.2f\n", capture_delay?DT_PREC_3/capture_delay:0.0 );
  sprintf( output+strlen(output), "Alarm Maximum FPS : %.2f\n", alarm_capture_delay?DT_PREC_3/alarm_capture_delay:0.0 );
  sprintf( output+strlen(output), "Reference Blend %%ge : %d\n", ref_blend_perc );
  sprintf( output+strlen(output), "Alarm Reference Blend %%ge : %d\n", alarm_ref_blend_perc );
  sprintf( output+strlen(output), "Track Motion : %d\n", track_motion );
  sprintf( output+strlen(output), "Function: %d - %s\n", function,
    function==NONE?"None":(
    function==MONITOR?"Monitor Only":(
    function==MODECT?"Motion Detection":(
    function==RECORD?"Continuous Record":(
    function==MOCORD?"Continuous Record with Motion Detection":(
    function==NODECT?"Externally Triggered only, no Motion Detection":"Unknown"
  ))))));
  sprintf( output+strlen(output), "Zones : %d\n", n_zones );
  for ( int i = 0; i < n_zones; i++ )
  {
    zones[i]->DumpSettings( output+strlen(output), verbose );
  }
  return( true );
}

bool MonitorStream::checkSwapPath( const char *path, bool create_path )
{
  uid_t uid = getuid();
  gid_t gid = getgid();

  struct stat stat_buf;
  if ( stat( path, &stat_buf ) < 0 )
  {
    if ( create_path && errno == ENOENT )
    {
      Debug( 3, "Swap path '%s' missing, creating", path );
      if ( mkdir( path, 0755 ) )
      {
        Error( "Can't mkdir %s: %s", path, strerror(errno));
        return( false );
      }
      if ( stat( path, &stat_buf ) < 0 )
      {
        Error( "Can't stat '%s': %s", path, strerror(errno) );
        return( false );
      }
    }
    else
    {
      Error( "Can't stat '%s': %s", path, strerror(errno) );
      return( false );
    }
  }
  if ( !S_ISDIR(stat_buf.st_mode) )
  {
    Error( "Swap image path '%s' is not a directory", path );
    return( false );
  }

  mode_t mask = 0;
  if ( uid == stat_buf.st_uid )
  {
    // If we are the owner
    mask = 00700;
  }
  else if ( gid == stat_buf.st_gid )
  {
    // If we are in the owner group
    mask = 00070;
  }
  else
  {
    // We are neither the owner nor in the group
    mask = 00007;
  }

  if ( (stat_buf.st_mode & mask) != mask )
  {
    Error( "Insufficient permissions on swap image path '%s'", path );
    return( false );
  }
  return( true );
}

void MonitorStream::processCommand( const CmdMsg *msg )
{
  Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] );
  // Check for incoming command
  switch( (MsgCommand)msg->msg_data[0] )
  {
    case CMD_PAUSE :
    {
      Debug( 1, "Got PAUSE command" );

      // Set paused flag
      paused = true;
      // Set delayed flag
      delayed = true;
      last_frame_sent = TV_2_FLOAT( now );
      break;
    }
    case CMD_PLAY :
    {
      Debug( 1, "Got PLAY command" );
      if ( paused )
      {
        // Clear paused flag
        paused = false;
        // Set delayed_play flag
        delayed = true;
      }
      replay_rate = ZM_RATE_BASE;
      break;
    }
    case CMD_VARPLAY :
    {
      Debug( 1, "Got VARPLAY command" );
      if ( paused )
      {
        // Clear paused flag
        paused = false;
        // Set delayed_play flag
        delayed = true;
      }
      replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768;
      break;
    }
    case CMD_STOP :
    {
      Debug( 1, "Got STOP command" );

      // Clear paused flag
      paused = false;
      // Clear delayed_play flag
      delayed = false;
      break;
    }
    case CMD_FASTFWD :
    {
      Debug( 1, "Got FAST FWD command" );
      if ( paused )
      {
        // Clear paused flag
        paused = false;
        // Set delayed_play flag
        delayed = true;
      }
      // Set play rate
      switch ( replay_rate )
      {
        case 2 * ZM_RATE_BASE :
          replay_rate = 5 * ZM_RATE_BASE;
          break;
        case 5 * ZM_RATE_BASE :
          replay_rate = 10 * ZM_RATE_BASE;
          break;
        case 10 * ZM_RATE_BASE :
          replay_rate = 25 * ZM_RATE_BASE;
          break;
        case 25 * ZM_RATE_BASE :
        case 50 * ZM_RATE_BASE :
          replay_rate = 50 * ZM_RATE_BASE;
          break;
        default :
          replay_rate = 2 * ZM_RATE_BASE;
          break;
      }
      break;
    }
    case CMD_SLOWFWD :
    {
      Debug( 1, "Got SLOW FWD command" );
      // Set paused flag
      paused = true;
      // Set delayed flag
      delayed = true;
      // Set play rate
      replay_rate = ZM_RATE_BASE;
      // Set step
      step = 1;
      break;
    }
    case CMD_SLOWREV :
    {
      Debug( 1, "Got SLOW REV command" );
      // Set paused flag
      paused = true;
      // Set delayed flag
      delayed = true;
      // Set play rate
      replay_rate = ZM_RATE_BASE;
      // Set step
      step = -1;
      break;
    }
    case CMD_FASTREV :
    {
      Debug( 1, "Got FAST REV command" );
      if ( paused )
      {
        // Clear paused flag
        paused = false;
        // Set delayed_play flag
        delayed = true;
      }
      // Set play rate
      switch ( replay_rate )
      {
        case -2 * ZM_RATE_BASE :
          replay_rate = -5 * ZM_RATE_BASE;
          break;
        case -5 * ZM_RATE_BASE :
          replay_rate = -10 * ZM_RATE_BASE;
          break;
        case -10 * ZM_RATE_BASE :
          replay_rate = -25 * ZM_RATE_BASE;
          break;
        case -25 * ZM_RATE_BASE :
        case -50 * ZM_RATE_BASE :
          replay_rate = -50 * ZM_RATE_BASE;
          break;
        default :
          replay_rate = -2 * ZM_RATE_BASE;
          break;
      }
      break;
    }
    case CMD_ZOOMIN :
    {
      x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
      y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
      Debug( 1, "Got ZOOM IN command, to %d,%d", x, y );
      switch ( zoom )
      {
        case 100:
          zoom = 150;
          break;
        case 150:
          zoom = 200;
          break;
        case 200:
          zoom = 300;
          break;
        case 300:
          zoom = 400;
          break;
        case 400:
        default :
          zoom = 500;
          break;
      }
      break;
    }
    case CMD_ZOOMOUT :
    {
      Debug( 1, "Got ZOOM OUT command" );
      switch ( zoom )
      {
        case 500:
          zoom = 400;
          break;
        case 400:
          zoom = 300;
          break;
        case 300:
          zoom = 200;
          break;
        case 200:
          zoom = 150;
          break;
        case 150:
        default :
          zoom = 100;
          break;
      }
      break;
    }
    case CMD_PAN :
    {
      x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
      y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
      Debug( 1, "Got PAN command, to %d,%d", x, y );
      break;
    }
    case CMD_SCALE :
    {
      scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
      Debug( 1, "Got SCALE command, to %d", scale );
      break;
    }
  case CMD_QUIT :
  {
     Info ("User initiated exit - CMD_QUIT");
     break;
  }
    case CMD_QUERY :
    {
      Debug( 1, "Got QUERY command, sending STATUS" );
      break;
    }
    default :
    {
      Error( "Got unexpected command %d", msg->msg_data[0] );
      break;
    }
  }

  struct {
    int id;
    int state;
    double fps;
    int buffer_level;
    int rate;
    double delay;
    int zoom;
    bool delayed;
    bool paused;
    bool enabled;
    bool forced;
  } status_data;

  status_data.id = monitor->Id();
  status_data.fps = monitor->GetFPS();
  status_data.state = monitor->shared_data->state;
  if ( playback_buffer > 0 )
    status_data.buffer_level = (MOD_ADD( (temp_write_index-temp_read_index), 0, temp_image_buffer_count )*100)/temp_image_buffer_count;
  else
    status_data.buffer_level = 0;
  status_data.delayed = delayed;
  status_data.paused = paused;
  status_data.rate = replay_rate;
  status_data.delay = TV_2_FLOAT( now ) - TV_2_FLOAT( last_frame_timestamp );
  status_data.zoom = zoom;
  //status_data.enabled = monitor->shared_data->active;
  status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF;
  status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON;
  Debug( 2, "L:%d, D:%d, P:%d, R:%d, d:%.3f, Z:%d, E:%d F:%d", 
    status_data.buffer_level,
    status_data.delayed,
    status_data.paused,
    status_data.rate,
    status_data.delay,
    status_data.zoom,
    status_data.enabled,
    status_data.forced
  );

  DataMsg status_msg;
  status_msg.msg_type = MSG_DATA_WATCH;
  memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) );
  int nbytes = 0;
  if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 )
  {
    //if ( errno != EAGAIN )
    {
      Error( "Can't sendto on sd %d: %s", sd, strerror(errno) );
      //exit( -1 );
    }
  }

  // quit after sending a status, if this was a quit request
  if ((MsgCommand)msg->msg_data[0]==CMD_QUIT)
  exit(0);

  updateFrameRate( monitor->GetFPS() );
}

bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp )
{
  bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE));

  if ( type != STREAM_JPEG )
    send_raw = false;
  if ( !config.timestamp_on_capture && timestamp )
    send_raw = false;

  if ( !send_raw )
  {
    Image temp_image( filepath );

    return( sendFrame( &temp_image, timestamp ) );
  }
  else
  {
    int img_buffer_size = 0;
    static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE];

    FILE *fdj = NULL;
    if ( (fdj = fopen( filepath, "r" )) )
    {
      img_buffer_size = fread( img_buffer, 1, sizeof(img_buffer), fdj );
      fclose( fdj );
    }
    else
    {
      Error( "Can't open %s: %s", filepath, strerror(errno) );
      return( false );
    }

    // Calculate how long it takes to actually send the frame
    struct timeval frameStartTime;
    gettimeofday( &frameStartTime, NULL );
    
    fprintf( stdout, "--ZoneMinderFrame\r\n" );
    fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size );
    fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" );
    if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 )
    {
      if ( !zm_terminate )
        Error( "Unable to send stream frame: %s", strerror(errno) );
      return( false );
    }
    fprintf( stdout, "\r\n\r\n" );
    fflush( stdout );

    struct timeval frameEndTime;
    gettimeofday( &frameEndTime, NULL );

    int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime );
    if ( frameSendTime > 1000/maxfps )
    {
      maxfps /= 2;
      Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps );
    }

    last_frame_sent = TV_2_FLOAT( now );

    return( true );
  }
  return( false );
}

bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp )
{
  Image *send_image = prepareImage( image );
  if ( !config.timestamp_on_capture && timestamp )
    monitor->TimestampImage( send_image, timestamp );

#if HAVE_LIBAVCODEC
  if ( type == STREAM_MPEG )
  {
    if ( !vid_stream )
    {
      vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() );
      fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() );
      vid_stream->OpenStream();
    }
    static struct timeval base_time;
    struct DeltaTimeval delta_time;
    if ( !frame_count )
      base_time = *timestamp;
    DELTA_TIMEVAL( delta_time, *timestamp, base_time, DT_PREC_3 );
    /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta );
  }
  else
#endif // HAVE_LIBAVCODEC
  {
    static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];

    int img_buffer_size = 0;
    unsigned char *img_buffer = temp_img_buffer;

    // Calculate how long it takes to actually send the frame
    struct timeval frameStartTime;
    gettimeofday( &frameStartTime, NULL );
    
    fprintf( stdout, "--ZoneMinderFrame\r\n" );
    switch( type )
    {
      case STREAM_JPEG :
        send_image->EncodeJpeg( img_buffer, &img_buffer_size );
        fprintf( stdout, "Content-Type: image/jpeg\r\n" );
        break;
      case STREAM_RAW :
        fprintf( stdout, "Content-Type: image/x-rgb\r\n" );
        img_buffer = (uint8_t*)send_image->Buffer();
        img_buffer_size = send_image->Size();
        break;
      case STREAM_ZIP :
        fprintf( stdout, "Content-Type: image/x-rgbz\r\n" );
        unsigned long zip_buffer_size;
        send_image->Zip( img_buffer, &zip_buffer_size );
        img_buffer_size = zip_buffer_size;
        break;
      default :
        Fatal( "Unexpected frame type %d", type );
        break;
    }
    fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size );
    if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 )
    {
      if ( !zm_terminate )
        Error( "Unable to send stream frame: %s", strerror(errno) );
      return( false );
    }
    fprintf( stdout, "\r\n\r\n" );
    fflush( stdout );

    struct timeval frameEndTime;
    gettimeofday( &frameEndTime, NULL );

    int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime );
    if ( frameSendTime > 1000/maxfps )
    {
      maxfps /= 1.5;
      Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps );
    }
  }
  last_frame_sent = TV_2_FLOAT( now );
  return( true );
}

void MonitorStream::runStream()
{
  if ( type == STREAM_SINGLE )
  {
    // Not yet migrated over to stream class
    monitor->SingleImage( scale );
    return;
  }

  openComms();

  checkInitialised();

  updateFrameRate( monitor->GetFPS() );

  if ( type == STREAM_JPEG )
    fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" );

  int last_read_index = monitor->image_buffer_count;

  time_t stream_start_time;
  time( &stream_start_time );

  frame_count = 0;

  temp_image_buffer = 0;
  temp_image_buffer_count = playback_buffer;
  temp_read_index = temp_image_buffer_count;
  temp_write_index = temp_image_buffer_count;

  char *swap_path = 0;
  bool buffered_playback = false;

  // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id
  const int max_swap_len_suffix = 15; 

  int swap_path_length = strlen(config.path_swap) + 1; // +1 for NULL terminator
  int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + 1;
  int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1;
  int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length;

  if ( connkey && playback_buffer > 0 ) {

    if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) {
      Error( "Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX );
    } else {
      swap_path = (char *)malloc( total_swap_path_length+max_swap_len_suffix );
      strncpy( swap_path, config.path_swap, swap_path_length );

      Debug( 3, "Checking swap path folder: %s", swap_path );
      if ( checkSwapPath( swap_path, false ) ) {
        // Append the subfolder name /zmswap-m{monitor-id} to the end of swap_path
        int ndx = swap_path_length - 1; // Array index of the NULL terminator
        snprintf( &(swap_path[ndx]), subfolder1_length, "/zmswap-m%d", monitor->Id() );

        Debug( 4, "Checking swap path subfolder: %s", swap_path );
        if ( checkSwapPath( swap_path, true ) ) {
          // Append the subfolder name /zmswap-q{connection key} to the end of swap_path
          ndx = swap_path_length+subfolder1_length - 2; // Array index of the NULL terminator
          snprintf( &(swap_path[ndx]), subfolder2_length, "/zmswap-q%06d", connkey );

          Debug( 4, "Checking swap path subfolder: %s", swap_path );
          if ( checkSwapPath( swap_path, true ) ) {
            buffered_playback = true;
          }
        }
      }

      if ( !buffered_playback ) {
        Error( "Unable to validate swap image path, disabling buffered playback" );
      } else {
        Debug( 2, "Assigning temporary buffer" );
        temp_image_buffer = new SwapImage[temp_image_buffer_count];
        memset( temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count );
        Debug( 2, "Assigned temporary buffer" );
      }
    }
  }

  float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs)
  while ( !zm_terminate )
  {
    bool got_command = false;
    if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() )
    {
      break;
    }

    gettimeofday( &now, NULL );

    if ( connkey )
    {
      while(checkCommandQueue()) {
        got_command = true;
      }
    }

    //bool frame_sent = false;
    if ( buffered_playback && delayed )
    {
      if ( temp_read_index == temp_write_index )
      {
        // Go back to live viewing
        Debug( 1, "Exceeded temporary streaming buffer" );
        // Clear paused flag
        paused = false;
        // Clear delayed_play flag
        delayed = false;
        replay_rate = ZM_RATE_BASE;
      }
      else
      {
        if ( !paused )
        {
          int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count );
          //Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index );
          SwapImage *swap_image = &temp_image_buffer[temp_index];

          if ( !swap_image->valid )
          {
            paused = true;
            delayed = true;
            temp_read_index = MOD_ADD( temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count );
          }
          else
          {
            //Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) );
            double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate;
            double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;

            //Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) );
            // If the next frame is due
            if ( actual_delta_time > expected_delta_time )
            {
              //Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time );
              if ( temp_index%frame_mod == 0 )
              {
                Debug( 2, "Sending delayed frame %d", temp_index );
                // Send the next frame
                if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) )
                  zm_terminate = true;
                memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) );
                //frame_sent = true;
              }
              temp_read_index = MOD_ADD( temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count );
            }
          }
        }
        else if ( step != 0 )
        {
          temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count );

          SwapImage *swap_image = &temp_image_buffer[temp_read_index];

          // Send the next frame
          if ( !sendFrame( temp_image_buffer[temp_read_index].file_name, &temp_image_buffer[temp_read_index].timestamp ) )
            zm_terminate = true;
          memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) );
          //frame_sent = true;
          step = 0;
        }
        else
        {
          int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count );

           double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
           if ( got_command || actual_delta_time > 5 )
           {
            // Send keepalive
            Debug( 2, "Sending keepalive frame %d", temp_index );
            // Send the next frame
            if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) )
              zm_terminate = true;
            //frame_sent = true;
          }
        }
      }
      if ( temp_read_index == temp_write_index )
      {
        // Go back to live viewing
        Warning( "Rewound over write index, resuming live play" );
        // Clear paused flag
        paused = false;
        // Clear delayed_play flag
        delayed = false;
        replay_rate = ZM_RATE_BASE;
      }
    }
    if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index )
    {
      int index = monitor->shared_data->last_write_index%monitor->image_buffer_count;
      last_read_index = monitor->shared_data->last_write_index;
      //Debug( 1, "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer );
      if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) )
      {
        if ( !paused && !delayed )
        {
          // Send the next frame
          Monitor::Snapshot *snap = &monitor->image_buffer[index];

          if ( !sendFrame( snap->image, snap->timestamp ) )
            zm_terminate = true;
          memcpy( &last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp) );
          //frame_sent = true;

          temp_read_index = temp_write_index;
        }
      }
      if ( buffered_playback )
      {
        if ( monitor->shared_data->valid )
        {
          if ( monitor->image_buffer[index].timestamp->tv_sec )
          {
            int temp_index = temp_write_index%temp_image_buffer_count;
            Debug( 2, "Storing frame %d", temp_index );
            if ( !temp_image_buffer[temp_index].valid )
            {
              snprintf( temp_image_buffer[temp_index].file_name, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path, temp_index );
              temp_image_buffer[temp_index].valid = true;
            }
            memcpy( &(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp) );
            monitor->image_buffer[index].image->WriteJpeg( temp_image_buffer[temp_index].file_name, config.jpeg_file_quality );
            temp_write_index = MOD_ADD( temp_write_index, 1, temp_image_buffer_count );
            if ( temp_write_index == temp_read_index )
            {
              // Go back to live viewing
              Warning( "Exceeded temporary buffer, resuming live play" );
              // Clear paused flag
              paused = false;
              // Clear delayed_play flag
              delayed = false;
              replay_rate = ZM_RATE_BASE;
            }
          }
          else
          {
            Warning( "Unable to store frame as timestamp invalid" );
          }
        }
        else
        {
          Warning( "Unable to store frame as shared memory invalid" );
        }
      }
      frame_count++;
    }
    usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) );
    if ( ttl )
    {
      if ( (now.tv_sec - stream_start_time) > ttl )
      {
        break;
      }
    }
    if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame )
    {
      Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame );
      break;
    }
  }
  if ( buffered_playback )
  {
    Debug( 1, "Cleaning swap files from %s", swap_path );
    struct stat stat_buf;
    if ( stat( swap_path, &stat_buf ) < 0 )
    {
      if ( errno != ENOENT )
      {
        Error( "Can't stat '%s': %s", swap_path, strerror(errno) );
      }
    }
    else if ( !S_ISDIR(stat_buf.st_mode) )
    {
      Error( "Swap image path '%s' is not a directory", swap_path );
    }
    else
    {
      char glob_pattern[PATH_MAX] = "";

      snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path );
      glob_t pglob;
      int glob_status = glob( glob_pattern, 0, 0, &pglob );
      if ( glob_status != 0 )
      {
        if ( glob_status < 0 )
        {
          Error( "Can't glob '%s': %s", glob_pattern, strerror(errno) );
        }
        else
        {
          Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status );
        }
      }
      else
      {
        for ( unsigned int i = 0; i < pglob.gl_pathc; i++ )
        {
          if ( unlink( pglob.gl_pathv[i] ) < 0 )
          {
            Error( "Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno) );
          }
        }
      }
      globfree( &pglob );
      if ( rmdir( swap_path ) < 0 )
      {
        Error( "Can't rmdir '%s': %s", swap_path, strerror(errno) );
      }
    }
  }
  if ( swap_path ) free( swap_path );
  closeComms();
}

void Monitor::SingleImage( int scale)
{
  int img_buffer_size = 0;
  static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE];
  Image scaled_image;
  int index = shared_data->last_write_index%image_buffer_count;
  Snapshot *snap = &image_buffer[index];
  Image *snap_image = snap->image;

  if ( scale != ZM_SCALE_BASE )
  {
    scaled_image.Assign( *snap_image );
    scaled_image.Scale( scale );
    snap_image = &scaled_image;
  }
  if ( !config.timestamp_on_capture )
  {
    TimestampImage( snap_image, snap->timestamp );
  }
  snap_image->EncodeJpeg( img_buffer, &img_buffer_size );
  
  fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size );
  fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" );
  fwrite( img_buffer, img_buffer_size, 1, stdout );
}

void Monitor::SingleImageRaw( int scale)
{
  Image scaled_image;
  int index = shared_data->last_write_index%image_buffer_count;
  Snapshot *snap = &image_buffer[index];
  Image *snap_image = snap->image;

  if ( scale != ZM_SCALE_BASE )
  {
    scaled_image.Assign( *snap_image );
    scaled_image.Scale( scale );
    snap_image = &scaled_image;
  }
  if ( !config.timestamp_on_capture )
  {
    TimestampImage( snap_image, snap->timestamp );
  }
  
  fprintf( stdout, "Content-Length: %d\r\n", snap_image->Size() );
  fprintf( stdout, "Content-Type: image/x-rgb\r\n\r\n" );
  fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout );
}

void Monitor::SingleImageZip( int scale)
{
  unsigned long img_buffer_size = 0;
  static Bytef img_buffer[ZM_MAX_IMAGE_SIZE];
  Image scaled_image;
  int index = shared_data->last_write_index%image_buffer_count;
  Snapshot *snap = &image_buffer[index];
  Image *snap_image = snap->image;

  if ( scale != ZM_SCALE_BASE )
  {
    scaled_image.Assign( *snap_image );
    scaled_image.Scale( scale );
    snap_image = &scaled_image;
  }
  if ( !config.timestamp_on_capture )
  {
    TimestampImage( snap_image, snap->timestamp );
  }
  snap_image->Zip( img_buffer, &img_buffer_size );
  
  fprintf( stdout, "Content-Length: %ld\r\n", img_buffer_size );
  fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" );
  fwrite( img_buffer, img_buffer_size, 1, stdout );
}
