<?php
//
// ZoneMinder web timeline view file, $Date$, $Revision$
//
// 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.
//

if (!canView('Events')) {
  $view = 'error';
  return;
}

foreach (getSkinIncludes('includes/timeline_functions.php') as $includeFile)
  require_once $includeFile;

global $dateTimeFormatter;

//
// Date/time formats used in charts
//
// These are the time axis range text. The first of each pair is the start date/time
// and the second is the last so often contains additional information
//

// When the chart range is years
define( 'STRF_TL_AXIS_RANGE_YEAR1', 'M Y' );
define( 'STRF_TL_AXIS_RANGE_YEAR2', STRF_TL_AXIS_RANGE_YEAR1 );

// When the chart range is months
define( 'STRF_TL_AXIS_RANGE_MONTH1', 'M' );
define( 'STRF_TL_AXIS_RANGE_MONTH2', STRF_TL_AXIS_RANGE_MONTH1.' Y' );

// When the chart range is days
define( 'STRF_TL_AXIS_RANGE_DAY1', 'd' );
define( 'STRF_TL_AXIS_RANGE_DAY2', STRF_TL_AXIS_RANGE_DAY1.' M Y' );

// When the chart range is less than a day
define( 'STRF_TL_AXIS_RANGE_TIME1', 'H:i' );
define( 'STRF_TL_AXIS_RANGE_TIME2', STRF_TL_AXIS_RANGE_TIME1.', d M Y' );

//
// These are the time axis tick labels
//
define( 'STRF_TL_AXIS_LABEL_YEAR', 'Y' );
define( 'STRF_TL_AXIS_LABEL_MONTH', 'M' );
define( 'STRF_TL_AXIS_LABEL_WEEK', 'd/m' );
define( 'STRF_TL_AXIS_LABEL_DAY', 'd' );
define( 'STRF_TL_AXIS_LABEL_4HOUR', 'H:00' );
define( 'STRF_TL_AXIS_LABEL_HOUR', 'H:00' );
define( 'STRF_TL_AXIS_LABEL_10MINUTE', 'H:i' );
define( 'STRF_TL_AXIS_LABEL_MINUTE', 'H:i' );
define( 'STRF_TL_AXIS_LABEL_10SECOND', '%s' );
define( 'STRF_TL_AXIS_LABEL_SECOND', '%s' );

$mouseover = isset($_REQUEST['mouseover']) ? $_REQUEST['mouseover'] : true;

$mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : 'overlay';

$minEventWidth = 3;
$maxEventWidth = 6;

$chart = array(
    'width'=>700,
    'height'=>460,
    'image' => array(
      'width'=>264,
      'height'=>220,
      'topOffset'=>20,
      ),
    'imageText' => array(
      'width'=>400,
      'height'=>30,
      'topOffset'=>20,
      ),
    'graph' => array(
      'width'=>600,
      'height'=>160,
      'topOffset'=>30,
      ),
    'title' => array(
      'topOffset'=>50
      ),
    'key' => array(
        'topOffset'=>50
        ),
    'axes' => array(
        'x' => array(
          'height' => 20,
          ),
        'y' => array(
          'width' => 30,
          ),
        ),
    'grid' => array(
        'x' => array(
          'major' => array(
            'max' => 12,
            'min' => 4,
            ),
          'minor' => array(
            'max' => 48,
            'min' => 12,
            ),
          ),
        'y' => array(
          'major' => array(
            'max' => 8,
            'min' => 1,
            ),
          'minor' => array(
            'max' => 0,
            'min' => 0,
            ),
          ),
        ),
);

$monitors = array();

# The as E, and joining with Monitors is required for the filterSQL filters.
$rangeSql = 'SELECT
  min(E.StartDateTime) AS MinTime,
  max(E.EndDateTime) AS MaxTime
FROM Events AS E
INNER JOIN Monitors AS M ON (E.MonitorId = M.Id)
LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
LEFT JOIN Tags AS T ON T.Id = ET.TagId
WHERE NOT isnull(E.StartDateTime) AND NOT isnull(E.EndDateTime)';

$eventsSql = 'SELECT
  E.*, GROUP_CONCAT(T.Name SEPARATOR ", ") AS Tags
FROM Events AS E
INNER JOIN Monitors AS M ON (E.MonitorId = M.Id)
LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
LEFT JOIN Tags AS T ON T.Id = ET.TagId
WHERE NOT isnull(StartDateTime)';

$eventIdsSql = 'SELECT E.Id FROM Events AS E
INNER JOIN Monitors AS M ON (E.MonitorId = M.Id)
LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
LEFT JOIN Tags AS T ON T.Id = ET.TagId
WHERE NOT isnull(StartDateTime)';

$eventsValues = array();

if ( count($user->unviewableMonitorIds()) ) {
  $monFilterSql = ' AND E.MonitorId IN ('.$user->viewableMonitorIds().')';
  $rangeSql .= $monFilterSql;
  $eventsSql .= $monFilterSql;
  $eventIdsSql .= $monFilterSql;
}

if ( isset($_REQUEST['range']) ) $range = validHtmlStr($_REQUEST['range']);
if ( isset($_REQUEST['minTime']) ) $minTime = validHtmlStr($_REQUEST['minTime']);
if ( isset($_REQUEST['midTime']) ) $midTime = validHtmlStr($_REQUEST['midTime']);
if ( isset($_REQUEST['maxTime']) ) $maxTime = validHtmlStr($_REQUEST['maxTime']);

if ( isset($range) and validInt($range) ) {
  $halfRange = (int)($range/2);
  if ( isset($midTime) ) {
    $midTimeT = strtotime($midTime);
    $minTimeT = $midTimeT-$halfRange;
    $maxTimeT = $midTimeT+$halfRange;
    if ( !($range%1) ) {
      $maxTimeT--;
    }
    $minTime = date(STRF_FMT_DATETIME_DB, $minTimeT);
    $maxTime = date(STRF_FMT_DATETIME_DB, $maxTimeT);
  } elseif ( isset($minTime) ) {
    $minTimeT = strtotime($minTime);
    $maxTimeT = $minTimeT + $range;
    $midTimeT = $minTimeT + $halfRange;
    $midTime = date(STRF_FMT_DATETIME_DB, $midTimeT);
    $maxTime = date(STRF_FMT_DATETIME_DB, $maxTimeT);
  } elseif ( isset($maxTime) ) {
    $maxTimeT = strtotime($maxTime);
    $minTimeT = $maxTimeT - $range;
    $midTimeT = $minTimeT + $halfRange;
    $minTime = date(STRF_FMT_DATETIME_DB, $minTimeT);
    $midTime = date(STRF_FMT_DATETIME_DB, $midTimeT);
  }
} elseif ( isset($minTime) && isset($maxTime) ) {
  $minTimeT = strtotime($minTime);
  $maxTimeT = strtotime($maxTime);
  $range = ($maxTimeT - $minTimeT) + 1;
  $halfRange = (int)($range/2);
  $midTimeT = $minTimeT + $halfRange;
  $midTime = date(STRF_FMT_DATETIME_DB, $midTimeT);
}

$tree = false;
if ( isset($_REQUEST['filter']) ) {
  $filter = ZM\Filter::parse($_REQUEST['filter']);
  $filter->remove_invalid_terms();
  $tree = $filter->tree();
  ZM\Debug(print_r($tree, true));
}
$tempMinTime = $tempMaxTime = $tempExpandable = false;
extractDatetimeRange($tree, $tempMinTime, $tempMaxTime, $tempExpandable);

$filterSql = $filter->sql();
if ( $filterSql ) {
  $eventsSql .= ' AND '.$filterSql;
  $eventIdsSql .= ' AND '.$filterSql;
}

if (!isset($minTime) || !isset($maxTime)) {
  if ($filterSql) $rangeSql .= ' AND '.$filterSql;
  // Dynamically determine range
  $row = dbFetchOne($rangeSql);
  if ( $row ) {
    if ( !isset($minTime) )
      $minTime = $row['MinTime'];
    if ( !isset($maxTime) )
      $maxTime = $row['MaxTime'];
  }

  if ( empty($minTime) )
    $minTime = $tempMinTime;
  if ( empty($maxTime) )
    $maxTime = $tempMaxTime;
  if ( empty($maxTime) )
    $maxTime = 'now';

  $minTimeT = strtotime($minTime);
  $maxTimeT = strtotime($maxTime);
  $range = ($maxTimeT - $minTimeT) + 1;
  $halfRange = (int)($range/2);
  $midTimeT = $minTimeT + $halfRange;
  $midTime = date(STRF_FMT_DATETIME_DB, $midTimeT);
}

if ( $tree ) {
  appendDatetimeRange($tree, $minTime, $maxTime);
  $filterQuery = parseTreeToQuery($tree);
} else {
  $filterQuery = false;
}

$scales = array(
  array( 'name'=>'year',     'factor'=>60*60*24*365, 'align'=>1,  'zoomout'=>2,    'label'=>STRF_TL_AXIS_LABEL_YEAR ),
  array( 'name'=>'month',    'factor'=>60*60*24*30,  'align'=>1,  'zoomout'=>12,   'label'=>STRF_TL_AXIS_LABEL_MONTH ),
  array( 'name'=>'week',     'factor'=>60*60*24*7,   'align'=>1,  'zoomout'=>4.25, 'label'=>STRF_TL_AXIS_LABEL_WEEK,     'labelCheck'=>'W' ),
  array( 'name'=>'day',      'factor'=>60*60*24,     'align'=>1,  'zoomout'=>7,    'label'=>STRF_TL_AXIS_LABEL_DAY ),
  array( 'name'=>'hour4',    'factor'=>60*60,        'align'=>4,  'zoomout'=>6,    'label'=>STRF_TL_AXIS_LABEL_4HOUR,    'labelCheck'=>'H' ),
  array( 'name'=>'hour',     'factor'=>60*60,        'align'=>1,  'zoomout'=>4,    'label'=>STRF_TL_AXIS_LABEL_HOUR,     'labelCheck'=>'H' ),
  array( 'name'=>'minute10', 'factor'=>60,           'align'=>10, 'zoomout'=>6,    'label'=>STRF_TL_AXIS_LABEL_10MINUTE, 'labelCheck'=>'i' ),
  array( 'name'=>'minute',   'factor'=>60,           'align'=>1,  'zoomout'=>10,   'label'=>STRF_TL_AXIS_LABEL_MINUTE,   'labelCheck'=>'i' ),
  array( 'name'=>'second10', 'factor'=>1,            'align'=>10, 'zoomout'=>6,    'label'=>STRF_TL_AXIS_LABEL_10SECOND ),
  array( 'name'=>'second',   'factor'=>1,            'align'=>1,  'zoomout'=>10,   'label'=>STRF_TL_AXIS_LABEL_SECOND ),
);

$majXScale = getDateScale($scales, $range, $chart['grid']['x']['major']['min'], $chart['grid']['x']['major']['max']);

// Adjust the range etc for scale
$minTimeT -= $minTimeT%($majXScale['factor']*$majXScale['align']);
$minTime = date(STRF_FMT_DATETIME_DB, $minTimeT);
$maxTimeT += (($majXScale['factor']*$majXScale['align'])-$maxTimeT%($majXScale['factor']*$majXScale['align']))-1;
if ( $maxTimeT > time() )
  $maxTimeT = time();
$maxTime = date(STRF_FMT_DATETIME_DB, $maxTimeT);
$range = ($maxTimeT - $minTimeT) + 1;
$halfRange = (int)($range/2);
$midTimeT = $minTimeT + $halfRange;
$midTime = date(STRF_FMT_DATETIME_DB, $midTimeT);

if ( isset($minTime) && isset($maxTime) ) {
  $eventsSql .= " AND EndDateTime >= '$minTime' AND StartDateTime <= '$maxTime'";
  $eventIdsSql .= " AND EndDateTime >= '$minTime' AND StartDateTime <= '$maxTime'";
}

$framesByEventId = array();
$eventsSql .= ' GROUP BY E.Id ORDER BY E.Id ASC';
$eventIdsSql .= ' GROUP BY E.Id';
$framesSql = "SELECT EventId,FrameId,Delta,Score FROM Frames WHERE EventId IN($eventIdsSql) AND Score > 0 ORDER BY Score DESC";
$frames_result = dbQuery($framesSql);
if ($frames_result) {
  while ( $row = $frames_result->fetch(PDO::FETCH_ASSOC) ) {
    if ( !isset($framesByEventId[$row['EventId']]) ) {
      $framesByEventId[$row['EventId']] = array();
    }
    $framesByEventId[$row['EventId']][] = $row;
  }
}

$chart['data'] = array(
  'x' => array(
    'lo' => strtotime($minTime),
    'hi' => strtotime($maxTime),
  ),
  'y' => array(
    'lo' => 0,
    'hi' => 0,
  )
);

$chart['data']['x']['range'] = ($chart['data']['x']['hi'] - $chart['data']['x']['lo']) + 1;
$chart['data']['x']['density'] = $chart['data']['x']['range']/$chart['graph']['width'];

$monEventSlots = array();
$monFrameSlots = array();
$events_result = dbQuery($eventsSql);
if ( !$events_result ) {
  ZM\Error('SQL-ERR');
  return;
}

$max_aspect_ratio = 0;

while ($event = $events_result->fetch(PDO::FETCH_ASSOC)) {
  if ( !isset($monitors[$event['MonitorId']]) ) {
    $monitor = $monitors[$event['MonitorId']] = ZM\Monitor::find_one(array('Id'=>$event['MonitorId']));
    $monEventSlots[$event['MonitorId']] = array();
    $monFrameSlots[$event['MonitorId']] = array();
    $aspect_ratio = round($monitor->Width() / $monitor->Height(), 2);
    if ( $aspect_ratio > $max_aspect_ratio )
      $max_aspect_ratio = $aspect_ratio;
  }

  $currEventSlots = &$monEventSlots[$event['MonitorId']];
  $currFrameSlots = &$monFrameSlots[$event['MonitorId']];

  $startTimeT = strtotime($event['StartDateTime']);
  $startIndex = $rawStartIndex = (int)(($startTimeT - $chart['data']['x']['lo']) / $chart['data']['x']['density']);
  if ( $startIndex < 0 )
    $startIndex = 0;

  if ( isset($event['EndDateTime']) )
    $endTimeT = strtotime($event['EndDateTime']);
  else
    $endTimeT = time();
  $endIndex = $rawEndIndex = (int)(($endTimeT - $chart['data']['x']['lo']) / $chart['data']['x']['density']);

  if ( $endIndex >= $chart['graph']['width'] )
    $endIndex = $chart['graph']['width'] - 1;

  for ( $i = $startIndex; $i <= $endIndex; $i++ ) {
    if ( !isset($currEventSlots[$i]) ) {
      if ( $rawStartIndex == $rawEndIndex ) {
        $offset = 1;
      } else {
        $offset = 1 + ($event['Frames']?((int)(($event['Frames']-1)*(($i-$rawStartIndex)/($rawEndIndex-$rawStartIndex)))):0);
      }
      $currEventSlots[$i] = array( 'count'=>0, 'width'=>1, 'offset'=>$offset, 'event'=>$event );
    } else {
      $currEventSlots[$i]['count']++;
    }
  }

  if ( $event['MaxScore'] > 0 ) {
    if ( $startIndex == $endIndex ) {
      # Only fills 1 slot, so just get the max Score
      $framesSql = 'SELECT FrameId, Score FROM Frames WHERE EventId = ? AND Score > 0 ORDER BY Score DESC LIMIT 1';
      $frame = dbFetchOne($framesSql, NULL, array($event['Id']));

      $i = $startIndex;
      if ( !isset($currFrameSlots[$i]) ) {
        $currFrameSlots[$i] = array('count'=>1, 'value'=>$event['MaxScore'], 'event'=>$event, 'frame'=>$frame, 'id'=>$frame['FrameId']);
      } else {
        $currFrameSlots[$i]['count']++;
        if ( $event['MaxScore'] > $currFrameSlots[$i]['value'] ) {
          $currFrameSlots[$i]['value'] = $event['MaxScore'];
          $currFrameSlots[$i]['event'] = $event;
          $currFrameSlots[$i]['frame'] = $frame;
        }
      }
      if ( $event['MaxScore'] > $chart['data']['y']['hi'] ) {
        $chart['data']['y']['hi'] = $event['MaxScore'];
      }
    } else {
      # Fills multiple Slots, so need multiple scores to generate the graph over multiple slots.
      if (isset($framesByEventId[$event['Id']])) {
        foreach ( $framesByEventId[$event['Id']] as $frame ) {
          $frameTimeT = $startTimeT + $frame['Delta'];
          $frameIndex = (int)(($frameTimeT - $chart['data']['x']['lo']) / $chart['data']['x']['density']);
          if ( $frameIndex < 0 )
            continue;
          if ( $frameIndex >= $chart['graph']['width'] )
            continue;

          if ( !isset($currFrameSlots[$frameIndex]) ) {
            $currFrameSlots[$frameIndex] = array('count'=>1, 'value'=>$frame['Score'], 'event'=>$event, 'frame'=>$frame);
          } else {
            $currFrameSlots[$frameIndex]['count']++;
            if ( $frame['Score'] > $currFrameSlots[$frameIndex]['value'] ) {
              $currFrameSlots[$frameIndex]['value'] = $frame['Score'];
              $currFrameSlots[$frameIndex]['event'] = $event;
              $currFrameSlots[$frameIndex]['frame'] = $frame;
            }
          }
          if ( $frame['Score'] > $chart['data']['y']['hi'] ) {
            $chart['data']['y']['hi'] = $frame['Score'];
          }
        } // end foreach frame
      }
    }
  } // end if MaxScore > 0
} // end foreach event

//ksort( $monitorIds, SORT_NUMERIC );
ksort( $monEventSlots, SORT_NUMERIC );
ksort( $monFrameSlots, SORT_NUMERIC );

// No longer needed?
if ( false ) {
  // Add on missing frames
  foreach( array_keys($monFrameSlots) as $monitorId ) {
    unset( $currFrameSlots );
    $currFrameSlots = &$monFrameSlots[$monitorId];
    for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
      if ( isset($currFrameSlots[$i]) ) {
        if ( !isset($currFrameSlots[$i]['frame']) ) {
          $framesSql = 'SELECT FrameId, Score FROM Frames WHERE EventId = ? AND Score > 0 ORDER BY FrameId LIMIT 1';
          $currFrameSlots[$i]['frame'] = dbFetchOne( $framesSql, NULL, array( $currFrameSlots[$i]['event']['Id'] ) );
        }
      }
    }
  }
}

$chart['data']['y']['range'] = ($chart['data']['y']['hi'] - $chart['data']['y']['lo']) + 1;
$chart['data']['y']['density'] = $chart['data']['y']['range']/$chart['graph']['height'];

$majYScale = getYScale(
  $chart['data']['y']['range'],
  $chart['grid']['y']['major']['min'],
  $chart['grid']['y']['major']['max']);

// Optimise boxes
foreach (array_keys($monEventSlots) as $monitorId) {
  unset($currEventSlots);
  $currEventSlots = &$monEventSlots[$monitorId];
  for ($i = 0; $i < $chart['graph']['width']; $i++) {
    if ( isset($currEventSlots[$i]) ) {
      if ( isset($currSlot) ) {
        if ( $currSlot['event']['Id'] == $currEventSlots[$i]['event']['Id'] ) {
          if ( $currSlot['width'] < $maxEventWidth ) {
            // Merge slots for the same long event
            $currSlot['width']++;
            unset( $currEventSlots[$i] );
            continue;
          } else if ( $currSlot['offset'] < $currEventSlots[$i]['offset'] ) {
            // Split very long events
            $currEventSlots[$i]['frame'] = array( 'FrameId'=>$currEventSlots[$i]['offset'] );
          }
        } else if ( $currSlot['width'] < $minEventWidth ) {
          // Merge multiple small events
          $currSlot['width']++;
          unset( $currEventSlots[$i] );
          continue;
        }
      }
      $currSlot = &$currEventSlots[$i];
    } else {
      unset($currSlot);
    }
  }  # end foreach x
  unset($currSlot);
} // end foreach Event Monitors
//print_r( $monEventSlots );

// Stack events
$frameSlots = array();
$frameMonitorIds = array_keys($monFrameSlots);
for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
  foreach ( $frameMonitorIds as $frameMonitorId ) {
    $currFrameSlots = &$monFrameSlots[$frameMonitorId];
    if ( isset($currFrameSlots[$i]) ) {
      if ( !isset($frameSlots[$i]) ) {
        $frameSlots[$i] = array();
        $frameSlots[$i][] = &$currFrameSlots[$i];
      } else {
        $slotCount = count($frameSlots[$i]);
        for ( $j = 0; $j < $slotCount; $j++ ) {
          if ( $currFrameSlots[$i]['value'] > $frameSlots[$i][$j]['value'] ) {
            for ( $k = $slotCount; $k > $j; $k-- ) {
              $frameSlots[$i][$k] = $frameSlots[$i][$k-1];
            }
            $frameSlots[$i][$j] = &$currFrameSlots[$i];
            break 2;
          }
        }
        $frameSlots[$i][] = &$currFrameSlots[$i];
      }
    }
    unset($currFrameSlots);
  } # end foreach MonitorId
}  # end foreach x

//ZM\Debug(print_r( $monEventSlots,true ));
//print_r( $monFrameSlots );
//print_r( $chart );

$graphHeight = $chart['graph']['height'];

if ( $mode == 'overlay' ) {
  $minEventBarHeight = 10;
  $maxEventBarHeight = 40;

  if ( count($monitors) ) {
    $chart['graph']['eventBarHeight'] = $minEventBarHeight;
    while ( ($chart['graph']['eventsHeight'] = (($chart['graph']['eventBarHeight'] * count($monitors)) + (count($monitors)-1))) < $maxEventBarHeight ) {
      $chart['graph']['eventBarHeight']++;
    }
  } else {
    $chart['graph']['eventBarHeight'] = $maxEventBarHeight;
    $chart['graph']['eventsHeight'] = $maxEventBarHeight;
  }
  $chart['graph']['activityHeight'] = ($graphHeight - $chart['graph']['eventsHeight']);
  $chart['data']['y']['density'] = $chart['data']['y']['range']/$chart['graph']['activityHeight'];

  $chart['eventBars'] = array();
  $top = $chart['graph']['activityHeight'];
  foreach ( array_keys($monitors) as $monitorId ) {
    $chart['eventBars'][$monitorId] = array( 'top' => $top );
    $top += $chart['graph']['eventBarHeight']+1;
  }
} else if ( $mode == 'split' ) {
  $minActivityBarHeight = 30;
  $minEventBarHeight = 10;
  $maxEventBarHeight = 40;

  if ( count($monitors) ) {
    $chart['graph']['eventBarHeight'] = $minEventBarHeight;
    $chart['graph']['activityBarHeight'] = $minActivityBarHeight;
    while ( ((($chart['graph']['eventBarHeight']+$chart['graph']['activityBarHeight']) * count($monitors)) + ((2*count($monitors))-1)) < $graphHeight ) {
      $chart['graph']['activityBarHeight']++;
      if ( $chart['graph']['eventBarHeight'] < $maxEventBarHeight ) {
        $chart['graph']['eventBarHeight']++;
      }
    }
  } else {
    $chart['graph']['eventBarHeight'] = $maxEventBarHeight;
    $chart['graph']['activityBarHeight'] = $graphHeight - $chart['graph']['eventBarHeight'];
  }
  $chart['data']['y']['density'] = $chart['data']['y']['range']/$chart['graph']['activityBarHeight'];

  $chart['activityBars'] = array();
  $chart['eventBars'] = array();
  $top = 0;
  $barCount = 1;
  foreach ( array_keys($monitors) as $monitorId ) {
    $chart['eventBars'][$monitorId] = array( 'top' => $top );
    $chart['eventBars'][$monitorId] = array( 'top' => $top+$chart['graph']['activityBarHeight']+1 );
    $top +=  $chart['graph']['activityBarHeight']+1+$chart['graph']['eventBarHeight']+1;
  }
} else {
  ZM\Warning("No mode $mode");
}

preg_match('/^(\d+)-(\d+)-(\d+) (\d+):(\d+)/', $minTime, $startMatches);
preg_match('/^(\d+)-(\d+)-(\d+) (\d+):(\d+)/', $maxTime, $endMatches);

if ( $startMatches[1] != $endMatches[1] ) {
  // Different years
  $title = date( STRF_TL_AXIS_RANGE_YEAR1, $chart['data']['x']['lo'] ).' - '.date( STRF_TL_AXIS_RANGE_YEAR2, $chart['data']['x']['hi'] );
} else if ( $startMatches[2] != $endMatches[2] ) {
  // Different months
  $title = date( STRF_TL_AXIS_RANGE_MONTH1, $chart['data']['x']['lo'] ).' - '.date( STRF_TL_AXIS_RANGE_MONTH2, $chart['data']['x']['hi'] );
} else if ( $startMatches[3] != $endMatches[3] ) {
  // Different dates
  $title = date( STRF_TL_AXIS_RANGE_DAY1, $chart['data']['x']['lo'] ).' - '.date( STRF_TL_AXIS_RANGE_DAY2, $chart['data']['x']['hi'] );
} else {
  // Different times
  $title = date( STRF_TL_AXIS_RANGE_TIME1, $chart['data']['x']['lo'] ).' - '.date( STRF_TL_AXIS_RANGE_TIME2, $chart['data']['x']['hi'] );
}

function drawXGrid( $chart, $scale, $labelClass, $tickClass, $gridClass, $zoomClass=false ) {
  $html = '';
  ob_start();
  $labelCount = 0;
  $lastTick = 0;
  unset( $lastLabel );
  $labelCheck = isset($scale['labelCheck']) ? $scale['labelCheck'] : $scale['label'];
  echo '<div id="xScale">';
  for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
    $x = round(100*(($i)/$chart['graph']['width']), 1);
    $timeOffset = (int)($chart['data']['x']['lo'] + ($i * $chart['data']['x']['density']));
    if ( $scale['align'] > 1 ) {
      $label = (int)((int)date( $labelCheck, $timeOffset )/$scale['align']);
    } else {
      $label = date($labelCheck, $timeOffset);
    }
    if ( !isset($lastLabel) || ($lastLabel != $label) ) {
      $labelCount++;
    }
    if ( $labelCount >= $scale['divisor'] ) {
      $labelCount = 0;
      if ( isset($lastLabel) ) {
        if ( $labelClass ) {
?>
            <div class="<?php echo $labelClass ?>" style="left: <?php echo $x-round(100*(11/$chart['graph']['width']),1) ?>%;"><?php echo date( $scale['label'], $timeOffset ); ?></div>
<?php
        }
        if ( $tickClass ) {
?>
            <div class="<?php echo $tickClass ?>" style="left: <?php echo $x ?>%;"></div>
<?php
        }
        if ( $gridClass ) {
?>
            <div class="<?php echo $gridClass ?>" style="left: <?php echo $x ?>%;"></div>
<?php
        }
        if ( $scale['name'] != 'second' && $zoomClass ) {
          $zoomMinTime = date( STRF_FMT_DATETIME_DB, (int)($chart['data']['x']['lo'] + ($lastTick * $chart['data']['x']['density'])) );
          $zoomMaxTime = date( STRF_FMT_DATETIME_DB, (int)($chart['data']['x']['lo'] + ($i * $chart['data']['x']['density'])) );
?>
            <div class="<?php echo $zoomClass ?>" style="left: <?php echo 100*($lastTick-1)/$chart['graph']['width'] ?>%; width: <?php echo round(100*($i-$lastTick)/$chart['graph']['width'],1) ?>%;" title="<?php echo translate('ZoomIn') ?>" data-on-click="tlZoomBounds" data-zoom-min-time="<?php echo $zoomMinTime ?>" data-zoom-max-time="<?php echo $zoomMaxTime ?>"></div>
<?php
        }
        $lastTick = $i;
      } # end if $lastLabel
    }
    $lastLabel = $label;
  } # end foreach width segment

  if ( $zoomClass ) {
    $zoomMinTime = date( STRF_FMT_DATETIME_DB, (int)($chart['data']['x']['lo'] + ($lastTick * $chart['data']['x']['density'])) );
    $zoomMaxTime = date( STRF_FMT_DATETIME_DB, (int)($chart['data']['x']['lo'] + ($i * $chart['data']['x']['density'])) );
?>
            <div class="<?php echo $zoomClass ?>" style="left: <?php echo $lastTick-1 ?>px; width: <?php echo $i-$lastTick ?>px;" title="<?php echo translate('ZoomIn') ?>" data-on-click="tlZoomBounds" data-zoom-min-time="<?php echo $zoomMinTime ?>" data-zoom-max-time="<?php echo $zoomMaxTime ?>"></div>
<?php
  }
?>
          </div>
<?php
  return ob_get_clean();
} # end function drawXGrid

function drawYGrid( $chart, $scale, $labelClass, $tickClass, $gridClass ) {
  ob_start();
?>
  <div id="yScale">
<?php
  for ( $i = 0; $i < $scale['lines']; $i++ ) {
    $label = (int)($i * $scale['divisor']);
    $y = $chart['graph']['eventsHeight']+(int)(($i * $scale['divisor'])/$chart['data']['y']['density'])-1;
    if ( $labelClass ) {
?>
       <div class="<?php echo $labelClass ?>" style="top: <?php echo $chart['graph']['height']-($y+8) ?>px;"><?php echo $label ?></div>
<?php
    }
    if ( $tickClass ) {
?>
       <div class="<?php echo $tickClass ?>" style="top: <?php echo $chart['graph']['height']-($y+2) ?>px;"></div>
<?php
    }
    if ( $gridClass ) {
?>
       <div class="<?php echo $gridClass ?>" style="top: <?php echo $chart['graph']['height']-($y+2) ?>px;<?php echo $i <= 0?' border-top: solid 1px black;':'' ?>"></div>
<?php
    }
  } # end foreach line segment
?>
  </div>
<?php
  return ob_get_clean();
} # end function drawYGrid

$focusWindow = true;

xhtmlHeaders(__FILE__, translate('Timeline'));
getBodyTopHTML();
echo getNavBarHTML();
?>
  <div id="page" class="p-0">
    <div class="d-flex p-1">
      <div class="mr-auto" id="toolbar" >
        <button id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button>
        <button id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button>
        <button id="listBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('List') ?>" ><i class="fa fa-list"></i></button>
      </div>
      <h2 class="align-self-end"><?php echo translate('Timeline') ?></h2>
    </div>

    <div id="content" class="chartSize">
      <div id="instruction">
        <p><?php echo translate('TimelineTip1') ?></p>
        <p><?php echo translate('TimelineTip2') ?></p>
        <p><?php echo translate('TimelineTip3') ?></p>
        <p><?php echo translate('TimelineTip4') ?></p>
      </div>
      <div id="topPanel" class="graphWidth">
<?php
foreach ( $monitors as $monitor ) {
?>
        <div class="monitorPanel" style="width:<?php echo 100/count($monitors); ?>%; float:left;">
        <div class="imagePanel"<?php echo count($monitors)==1?' style="width: 50%; float: left;"' :''?>>
            <div class="imageHeight image">
              <img id="imageSrc<?php echo $monitor->Id() ?>" class="imageWidth" src="graphics/transparent.png" alt="<?php echo translate('ViewEvent') ?>" title="<?php echo translate('ViewEvent') ?>" loading="lazy" />
            </div>
          </div>
          <div id="dataPanel<?php echo $monitor->Id() ?>" class="dataPanel"<?php echo count($monitors)==1?' style="width: 50%; float: left;"' :''?>>
            <div id="textPanel<?php echo $monitor->Id() ?>">
              <div id="eventData<?php echo $monitor->Id() ?>">
              </div>
            </div>
          </div>
        </div>
<?php
} # end foreach monitor
?>
          <div id="navPanel">
            <button type="button" title="<?php echo translate('PanLeft') ?>" data-on-click="tlPanLeft">
            <i class="material-icons md-18">fast_rewind</i>
            </button>
            <button type="button" title="<?php echo translate('ZoomOut') ?>" data-on-click="tlZoomOut">
<i class="material-icons md-18">zoom_out</i>
            </button>
            <button type="button" title="<?php echo translate('PanRight') ?>" data-on-click="tlPanRight">
            <i class="material-icons md-18">fast_forward</i>
            </button>
          </div>
      </div>
      <div id="chartPanel">
        <div id="chart" class="graphSize">
<?php

function drawSlot($slot, $index) {
  global $chart;
  global $monitors;
  global $mouseover;
  $height = (int)($slot['value']/$chart['data']['y']['density']);

  if ( $height <= 0 )
    return '';
  $left = round(100*($index/$chart['graph']['width']),1);

  return "<div class=\"activity monitorColour{$slot['event']['MonitorId']}\"
            style=\"left:{$left}%; height: {$height}px;\"
  data-event-id=\"{$slot['event']['Id']}\" data-frame-id=\"".getSlotFrame($slot)."\"".
  ( $mouseover ? ' data-on-mouseover-this="previewEvent" data-on-click-this="showEvent"' : ' data-on-click-this="previewEvent"').
  '></div>';
}

if ( $mode == 'overlay' ) {
  echo drawYGrid( $chart, $majYScale, 'majLabelY', 'majTickY', 'majGridY graphWidth' );
}
echo drawXGrid( $chart, $majXScale, 'majLabelX', 'majTickX', 'majGridX graphHeight', 'tlzoom graphHeight' );
if ( $mode == 'overlay' ) {
?>
          <div id="activity" class="activitySize">
<?php
    foreach ( $frameSlots as $index=>$slots ) {
      foreach ( $slots as $slot ) {
        echo drawSlot($slot, $index);
      }
    }
?>
          </div>
<?php
} else if ( $mode == 'split' ) {
  foreach ( array_keys($monFrameSlots) as $monitorId ) {
?>
        <div id="activity<?php echo $monitorId ?>" class="activitySize">
<?php
    $currFrameSlots = &$monFrameSlots[$monitorId];
    foreach ( $currFrameSlots as $index=>$slot ) {
      echo drawSlot($slot, $index);
    } # end foreach $currFrameSlots
    unset($currFrameSlots);
?>
        </div>
<?php
  } # end foreach $MonitorId
}
foreach ( array_keys($monEventSlots) as $monitorId ) {
?>
          <div id="events<?php echo $monitorId ?>" class="events eventsSize eventsPos<?php echo $monitorId ?>">
<?php
  $currEventSlots = &$monEventSlots[$monitorId];
  for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
    if ( isset($currEventSlots[$i]) ) {
      $slot = &$currEventSlots[$i];

  $left = round(100*($i/$chart['graph']['width']),1);
  $width = round(100*($slot['width']/$chart['graph']['width']),1);

  echo "<div class=\"event monitorColour{$slot['event']['MonitorId']}\"
            style=\"left:{$left}%; width: {$width}%;\"
  data-event-id=\"{$slot['event']['Id']}\" data-frame-id=\"".getSlotFrame($slot)."\"".
  ( $mouseover ? ' data-on-mouseover-this="previewEvent" data-on-click-this="showEvent"' : ' data-on-click-this="previewEvent"').
  '></div>';
      unset( $slot );
    } # end if isset($currEventSlots[$i])
  } # end foreach width segment
  unset ($currEventSlots);
?>
          </div>
<?php
}
?>
        </div>
      </div>
      <div id="chartLabels" class="graphWidth">
        <div id="key">
<?php
foreach( array_keys($monEventSlots) as $monitorId ) {
?>
          <span class="keyEntry"><?php echo $monitors[$monitorId]->Name() ?>
          <div id="keyBox<?php echo $monitorId ?>" class="keyBox monitorColour<?php echo $monitorId ?>" title="<?php echo $monitors[$monitorId]->Name() ?>" style="background-color: <?php echo $monitors[$monitorId]->WebColour() ?>;"></div>
          </span>
<?php
}
?>
        </div>
        <div id="range"><?php echo $title ?></div>
      </div>
    </div>
  </div>
<?php xhtmlFooter() ?>
