
#include "SDDS.h"
#include "mdb.h"
#include "scan.h"

#define CLO_START 0
#define CLO_END 1
#define CLO_BEAMLINE 2
#define CLO_DIRECTORY 3
#define CLO_CONTROL_NAME 4
#define CLO_PIPE 5
#define CLO_EXTENSIONS 6
#define CLO_ROOTNAME 7
#define CLO_SUFFIX 8
#define CLO_FILTER 9
#define CLO_USE_DEFAULT_EXTENSION 10
#define CLO_LOG_FILE 11
#define OPTIONS 12

char *option[OPTIONS] = {
  "start",
  "end",
  "beamline",
  "directory",
  "controlName",
  "pipe",
  "extensions",
  "rootname",
  "suffix",
  "filter",
  "useDefaultExtension",
  "logfile",
};

static char *USAGE = "beamlineDownTime\n\
 [outputfile] [-pipe=output] \n\
 -start={[year=<integer>,][month=<integer>,][day=<integer>,][hour=<value>]  |time=<value> | now} \n\
 -end={[year=<integer>,][month=<integer>,][day=<integer>,][hour=<value>] | time=value | now } \n\
 -beamlines=<beamlines> [-controlName=<name>,...| edit=<string> | format=<string>] \n\
 -logDirectory=<string> [-extension=<list of file extensions>] [-rootname=<string>]\n\
 [-suffix=<string>] [-filter=<string>] [-useDefaultExtension] [-logfile=<filename>] \n\
outputfile              SDDS output file for writing the data. \n\
pipe                    output the data to pipe. Either output file or pipe \n\
                        has to be provided.\n\
start                   required. give the start time for calculating from. \n\
                        either time or year, month, day and/or hour or now has to be given.\n\
                        if now is given, current time will be used for start time.\n\
                        julianDay can replace month and day, either julianDay or month/day\n\
                        may be given. the time is the start time given in seconds. \n\
                        If time and year/month/day are not given, \n\
                        today's year, month and day will be used. \n\
end                     required. similar to start option, \n\
                        it gives the end time for calcuating to. \n\
beamlines               required. give the list of beamlines separated by comma.\n\
                        for example, BM01, ID01. \n\
controlName             optional. The control names of the corresponding beamlines\n\
                        that are logged in the logger files are formed from the \n\
                        beamline names, eg. controlName=edit=ai/EPS:/ei/:BLEPS:SPER/ \n\
                        or controlName=format=EPS:%s:BLEPS:SPER \n\
                        The full controlName, for example, EPS:01:ID:BLEPS:SPER. \n\
                        If this option is not given, \n\
                        the control names will be the same as beamline names. \n\
directory               required. The directory of the logger files. \n\
extensions              provide a list of log file extensions separated by comma.\n\
useDefaultExtension     use default extensions which are \"\",\".????\", \".gz\" \n\
filter                  the matching string for log file names. \n\
rootname                the rootname of the log file names. \n\
suffix                  the suffix of the log file names. A log file is named as \n\
                        <rootname><year><filter><suffix><extension>. \n\
logfile                 if provided, write the intermediate data (such as SRMode status, beamline values)\n\
                        to logfile for review. \n\n\
Calculates beamline downtime. Programed by H. Shang ("__DATE__").\n";

#define YEAR_GIVEN 0x0001U
#define DAY_GIVEN 0x0002U
#define MONTH_GIVEN 0x0004U
#define JULIANDAY_GIVEN 0x0008U
#define HOUR_GIVEN 0x0010U
#define TIME_GIVEN 0x0020U
#define NOW_GIVEN 0x0040U

typedef struct {
  char **beamline, **controlName;
  long beamlines, rows;
  double *time, **value;
} BEAMLINE_DATA;

char **process_control_names(char **beamline, long beamlines, char *format, char *edit);
long get_logonchange_data(char *file, char **controlName, long controlNames, double startTime, double endTime,
                          double **time0, double ***value0, double ***downWithSchedule, double ***downWithBeam,
                          char *logfile, SDDS_DATASET *log_data);
void SetupOutputFile(char *outputfile, SDDS_DATASET *out_data);
void SetupLogFile(char *logfile, SDDS_DATASET *log_data, char **controlName, long controlNames);
double computeDownTime(short *first, double *prevtime, double *time, double *down, long rows);

int main(int argc, char **argv) {
  long i_arg, beamlines, startGiven, endGiven, extensions, i, j, logfiles, extensionGiven, defaultExt, defaultFilter, controlNames, rows;
  SCANNED_ARG *s_arg;
  short startYear, startMonth, startDay, endYear, endMonth, endDay, year, month, day, julianDay, startJDay, endJDay, srLogYear, srLogMonth, srLogDay, srLogJDay;
  double startHour, endHour, startTime, endTime, hour, srLogTime, start, end;
  double *downTimeWithSchedule, *downTimeWithBeam, **beamlineValue, *beamlineTime, *srDesiredMode, *srActualMode, *totalDownTime, **downWithSchedule, **downWithBeam;
  char *outputfile, *controlFormat, *controlEdit, *filter, *rootname, *suffix, *logDirectory = NULL, *logfile;
  char **beamline, **controlName, **extensionList, **logfileList;
  unsigned long pipeFlags, dummyFlags;
  time_t longtime;
  SDDS_DATASET out_data, log_data;
  char *stamp = NULL;
  double **prevtime = NULL;
  short **first = NULL;

  logfile = NULL;
  downWithSchedule = downWithBeam = NULL;
  downTimeWithSchedule = downTimeWithBeam = totalDownTime = NULL;
  beamlineTime = srDesiredMode = srActualMode = NULL;
  beamlineValue = NULL;
  start = end = 0;
  defaultFilter = 0;
  rootname = filter = suffix = NULL;
  logfileList = NULL;
  logfiles = 0;
  startGiven = endGiven = extensionGiven = defaultExt = 0;
  pipeFlags = dummyFlags = 0;
  beamlines = controlNames = 0;
  beamline = controlName = NULL;
  controlFormat = controlEdit = NULL;
  outputfile = NULL;
  year = month = day = julianDay = 0;
  hour = 0;
  startYear = startMonth = startDay = endYear = endMonth = endDay = startJDay = endJDay = 0;
  startHour = endHour = 0;
  startTime = endTime = 0;
  extensions = 0;
  extensionList = NULL;
  argc = scanargs(&s_arg, argc, argv);
  if (argc < 2)
    bomb(NULL, USAGE);

  /* get today's date -- year, month and day */
  time(&longtime);
  TimeEpochToBreakdown(&year, &julianDay, &month, &day, &hour, longtime);
  /* the SR mode is logged since Oct 18 15:48 2004, here set to Oct 19 0:00 2004 */
  srLogYear = 2004;
  srLogMonth = 10;
  srLogDay = 19;
  srLogJDay = 293;
  TimeBreakdownToEpoch(srLogYear, srLogJDay, srLogMonth, srLogDay, 0, &srLogTime);
  for (i_arg = 1; i_arg < argc; i_arg++) {
    if (s_arg[i_arg].arg_type == OPTION) {
      switch (match_string(s_arg[i_arg].list[0], option, OPTIONS, 0)) {
      case CLO_START:
        startGiven = 1;
        s_arg[i_arg].n_items--;
        if (!scanItemList(&dummyFlags, s_arg[i_arg].list + 1, &s_arg[i_arg].n_items, 0,
                          "year", SDDS_SHORT, &startYear, 1, YEAR_GIVEN,
                          "month", SDDS_SHORT, &startMonth, 1, MONTH_GIVEN,
                          "day", SDDS_SHORT, &startDay, 1, DAY_GIVEN,
                          "julianDay", SDDS_SHORT, &startJDay, 1, JULIANDAY_GIVEN,
                          "hour", SDDS_DOUBLE, &startHour, 1, HOUR_GIVEN,
                          "time", SDDS_DOUBLE, &startTime, 1, TIME_GIVEN,
                          "now", -1, NULL, 0, NOW_GIVEN,
                          NULL))
          SDDS_Bomb("invalid -start syntax.");
        if (dummyFlags & NOW_GIVEN) {
          startYear = year;
          startMonth = month;
          startDay = day;
          startJDay = julianDay;
          startHour = hour;
          startTime = (double)longtime;
        } else {
          if (dummyFlags & TIME_GIVEN && (dummyFlags & YEAR_GIVEN || dummyFlags & MONTH_GIVEN || dummyFlags & DAY_GIVEN || dummyFlags & HOUR_GIVEN || dummyFlags & JULIANDAY_GIVEN))
            SDDS_Bomb("invalid -start syntax, both time and year/month/day/hour are given!");
          if (dummyFlags & YEAR_GIVEN)
            startYear = make_four_digit_year(startYear);
          if (!(dummyFlags & TIME_GIVEN)) {
            if (!(dummyFlags & YEAR_GIVEN))
              startYear = year;
            if (!(dummyFlags & JULIANDAY_GIVEN)) {
              if (!(dummyFlags & MONTH_GIVEN))
                startMonth = month;
              if (!(dummyFlags & DAY_GIVEN))
                startDay = day;
            }
            TimeBreakdownToEpoch(startYear, startJDay, startMonth, startDay, startHour, &startTime);
          }
          TimeEpochToBreakdown(&startYear, &startJDay, &startMonth, &startDay, &startHour, startTime);
        }
        dummyFlags = 0;
        s_arg[i_arg].n_items++;
        break;
      case CLO_END:
        endGiven = 1;
        s_arg[i_arg].n_items--;
        if (!scanItemList(&dummyFlags, s_arg[i_arg].list + 1, &s_arg[i_arg].n_items, 0,
                          "year", SDDS_SHORT, &endYear, 1, YEAR_GIVEN,
                          "month", SDDS_SHORT, &endMonth, 1, MONTH_GIVEN,
                          "day", SDDS_SHORT, &endDay, 1, DAY_GIVEN,
                          "julianDay", SDDS_SHORT, &endJDay, 1, JULIANDAY_GIVEN,
                          "hour", SDDS_DOUBLE, &endHour, 1, HOUR_GIVEN,
                          "time", SDDS_DOUBLE, &endTime, 1, TIME_GIVEN,
                          "now", -1, NULL, 0, NOW_GIVEN,
                          NULL))
          SDDS_Bomb("invalid -end syntax.");
        if (dummyFlags & NOW_GIVEN) {
          endYear = year;
          endMonth = month;
          endDay = day;
          endJDay = julianDay;
          endHour = hour;
          endTime = (double)longtime;
        } else {
          if (dummyFlags & TIME_GIVEN && (dummyFlags & YEAR_GIVEN || dummyFlags & MONTH_GIVEN || dummyFlags & DAY_GIVEN || dummyFlags & HOUR_GIVEN || dummyFlags & JULIANDAY_GIVEN))
            SDDS_Bomb("invalid -end syntax, both time and year/month/day/hour are given!");
          if (dummyFlags & YEAR_GIVEN)
            endYear = make_four_digit_year(endYear);
          if (!(dummyFlags & TIME_GIVEN)) {
            if (!(dummyFlags & YEAR_GIVEN))
              endYear = year;
            if (!(dummyFlags & JULIANDAY_GIVEN)) {
              if (!(dummyFlags & MONTH_GIVEN))
                endMonth = month;
              if (!(dummyFlags & DAY_GIVEN))
                endDay = day;
            }
            TimeBreakdownToEpoch(endYear, endJDay, endMonth, endDay, endHour, &endTime);
          }
          if (endHour == 24)
            endTime = endTime - 1.0e-6;
          TimeEpochToBreakdown(&endYear, &endJDay, &endMonth, &endDay, &endHour, endTime);
        }
        dummyFlags = 0;
        s_arg[i_arg].n_items++;
        break;
      case CLO_PIPE:
        if (!processPipeOption(s_arg[i_arg].list + 1, s_arg[i_arg].n_items - 1, &pipeFlags))
          SDDS_Bomb("invalid -pipe syntax");
        if (pipeFlags != USE_STDOUT)
          SDDS_Bomb("only -pipe=out syntax is valid!");
        break;
      case CLO_LOG_FILE:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("invalid -logfile syntax");
        logfile = s_arg[i_arg].list[1];
        break;
      case CLO_BEAMLINE:
        if (s_arg[i_arg].n_items < 2)
          SDDS_Bomb("invalid -beamlines syntax");
        beamlines = s_arg[i_arg].n_items - 1;
        beamline = s_arg[i_arg].list + 1;
        break;
      case CLO_EXTENSIONS:
        if (s_arg[i_arg].n_items < 2)
          SDDS_Bomb("invalid -extensions syntax");
        extensions = s_arg[i_arg].n_items - 1;
        extensionList = s_arg[i_arg].list + 1;
        extensionGiven = 1;
        break;
      case CLO_USE_DEFAULT_EXTENSION:
        defaultExt = 1;
        break;
      case CLO_CONTROL_NAME:
        if (s_arg[i_arg].n_items < 2)
          SDDS_Bomb("Invalid -controlName syntax");
        s_arg[i_arg].n_items--;
        if (!scanItemList(&dummyFlags, s_arg[i_arg].list + 1, &s_arg[i_arg].n_items, 0,
                          "edit", SDDS_STRING, &controlEdit, 1, 0,
                          "format", SDDS_STRING, &controlFormat, 1, 0,
                          NULL))
          SDDS_Bomb("invalid -controlName syntax.");
        if (controlFormat && controlEdit)
          SDDS_Bomb("invalid -controlName syntax, edit and format are both given!");
        if (!controlFormat && !controlEdit) {
          controlName = s_arg[i_arg].list + 1;
          controlNames = s_arg[i_arg].n_items;
        }
        s_arg[i_arg].n_items++;
        break;
      case CLO_FILTER:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("Invalid -filter syntax");
        filter = s_arg[i_arg].list[1];
        break;
      case CLO_ROOTNAME:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("Invalid -rootname syntax");
        rootname = s_arg[i_arg].list[1];
        break;
      case CLO_SUFFIX:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("Invalid -suffix syntax");
        suffix = s_arg[i_arg].list[1];
        break;
      case CLO_DIRECTORY:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("Invalid -logDirectory syntax.");
        logDirectory = s_arg[i_arg].list[1];
        break;
      default:
        bomb("unknown option given", USAGE);
        break;
      }
    } else {
      if (!outputfile)
        outputfile = s_arg[i_arg].list[0];
      else
        SDDS_Bomb("too many filenames given");
    }
  }
  if (outputfile && pipeFlags)
    SDDS_Bomb("Both outputfile and pipe=out are given!");
  if (!outputfile && !pipeFlags)
    SDDS_Bomb("No outputfile is given!");
  if (!beamlines)
    SDDS_Bomb("No beamlines given!");
  if (!startGiven || !endGiven)
    SDDS_Bomb("both start(time) and end(time) have to be given!");
  if (startTime > endTime)
    SDDS_Bomb("The start time is greater than end time!");
  if (startTime < srLogTime) {
    fprintf(stderr, "Please use BeamlineDownTimeCalculator script to get beamline downtime before 10/19/2004.\n");
    exit(1);
  }
  if (extensionGiven && defaultExt)
    SDDS_Bomb("-extensions and -useDefaultExtension are incompatible.");
  if (!rootname)
    SDDS_Bomb("-rootname is not given!");
  if (!filter) {
    defaultFilter = 1;
    cp_str(&filter, "-??\?-??\??");
  }
  if (defaultExt) {
    extensions = 3;
    extensionList = (char **)malloc(sizeof(*extensionList) * extensions);
    SDDS_CopyString(&extensionList[0], "");
    SDDS_CopyString(&extensionList[1], ".gz");
    SDDS_CopyString(&extensionList[2], ".??\??");
  }
  if (!controlFormat && !controlEdit) {
    if (controlNames && controlNames < beamlines) {
      fprintf(stderr, "Invalid -controlName option given, the number of control names is less than that of beamline.\n");
      exit(1);
    }
  }
  if (controlFormat || !controlNames || controlEdit) {
    controlName = process_control_names(beamline, beamlines, controlFormat, controlEdit);
  }
  /*above is tested */
  logfileList = find_files_between_dates(logDirectory, rootname, suffix,
                                         startYear, 0, 0, startJDay, endYear, 0, 0, endJDay,
                                         filter, extensionList, extensions, 0, &logfiles, 1);
  if (logfile)
    SetupLogFile(logfile, &log_data, controlName, beamlines);
  downTimeWithSchedule = (double *)calloc(sizeof(*downTimeWithSchedule), beamlines);
  downTimeWithBeam = (double *)calloc(sizeof(*downTimeWithBeam), beamlines);
  totalDownTime = (double *)calloc(sizeof(*totalDownTime), beamlines);
  prevtime = (double **)malloc(sizeof(*prevtime) * beamlines);
  first = (short **)malloc(sizeof(*first) * beamlines);
  for (i = 0; i < beamlines; i++) {
    prevtime[i] = (double *)calloc(sizeof(double), 3);
    first[i] = (short *)malloc(sizeof(short) * 3);
    for (j = 0; j < 3; j++)
      first[i][j] = 1;
  }

  if (!logfiles) {
    fprintf(stderr, "no logfiles found between %s and %s.\n", makeTimeStamp(startTime), makeTimeStamp(endTime));
    exit(1);
  }
  for (i = 0; i < logfiles; i++) {
    start = end = 0;
    /*only the first and last file need filter */
    if (i == 0)
      start = startTime;
    if (i == logfiles - 1)
      end = endTime;

    rows = get_logonchange_data(logfileList[i], controlName, beamlines, start, end,
                                &beamlineTime, &beamlineValue, &downWithSchedule, &downWithBeam,
                                logfile, &log_data);
    if (rows) {
      for (j = 0; j < beamlines; j++) {
        totalDownTime[j] += computeDownTime(&(first[j][0]), &(prevtime[j][0]), beamlineTime, beamlineValue[j], rows);
        downTimeWithSchedule[j] += computeDownTime(&(first[j][1]), &(prevtime[j][1]), beamlineTime, downWithSchedule[j], rows);
        downTimeWithBeam[j] += computeDownTime(&(first[j][2]), &(prevtime[j][2]), beamlineTime, downWithBeam[j], rows);
      }
      free(beamlineTime);
      for (j = 0; j < beamlines; j++) {
        free(downWithSchedule[j]);
        free(downWithBeam[j]);
        free(beamlineValue[j]);
      }
    } /*end of if rows */
    free(downWithSchedule);
    free(downWithBeam);
    free(beamlineValue);

  } /*end of for i<logfiles loop*/
  for (i = 0; i < beamlines; i++) {
    free(first[i]);
    free(prevtime[i]);
  }
  free(first);
  free(prevtime);
  if (logfile && !SDDS_Terminate(&log_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  SetupOutputFile(outputfile, &out_data);
  if (!SDDS_StartPage(&out_data, beamlines)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  SDDS_CopyString(&stamp, makeTimeStamp(startTime));
  if (!SDDS_SetParameters(&out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                          "StartTimeStamp", stamp,
                          "EndTimeStamp", makeTimeStamp(endTime), NULL)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  for (i = 0; i < beamlines; i++) {
    if (!SDDS_SetRowValues(&out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, i,
                           "Beamline", beamline[i],
                           "TotalDownTime", totalDownTime[i] / 3600.0,
                           "DownWithSchedule", downTimeWithSchedule[i] / 3600.0,
                           "DownWithBeam", downTimeWithBeam[i] / 3600.0,
                           NULL)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
      exit(1);
    }
  }
  if (!SDDS_WritePage(&out_data) || !SDDS_Terminate(&out_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  free(downTimeWithSchedule);
  free(downTimeWithBeam);
  free(totalDownTime);
  if (defaultExt) {
    for (i = 0; i < extensions; i++)
      free(extensionList[i]);
    free(extensionList);
  }
  if (defaultFilter)
    free(filter);
  for (i = 0; i < beamlines; i++)
    free(controlName[i]);
  free(controlName);
  for (i = 0; i < logfiles; i++)
    free(logfileList[i]);
  free(logfileList);
  if (controlFormat)
    free(controlFormat);
  if (controlEdit)
    free(controlEdit);
  free_scanargs(&s_arg, argc);
  return 0;
}

char **process_control_names(char **beamline, long beamlines, char *format, char *edit) {
  char **controlName;
  char edit_buffer[256], format_buffer[256], tmpstr[256];
  long i, sector;

  controlName = (char **)malloc(sizeof(*controlName) * beamlines);
  for (i = 0; i < beamlines; i++) {
    if (sscanf(beamline[i], "ID%ld", &sector) == 1) {
      sprintf(tmpstr, "%02ld:ID", sector);
    } else if (sscanf(beamline[i], "BM%ld", &sector) == 1) {
      sprintf(tmpstr, "%02ld:BM", sector);
    } else {
      SDDS_CopyString(&controlName[i], beamline[i]);
      continue;
    }
    if (format) {
      sprintf(format_buffer, format, tmpstr);
      SDDS_CopyString(&controlName[i], format_buffer);
    } else if (edit) {
      strcpy(edit_buffer, tmpstr);
      if (!edit_string(edit_buffer, edit))
        SDDS_Bomb("error editing name");
      SDDS_CopyString(&controlName[i], edit_buffer);
    } else {
      SDDS_CopyString(&controlName[i], beamline[i]);
    }
  }
  return controlName;
}

/* this function obtain the time, beamline value,
   down value (down is 1 if beamline value is 0 when SR mode is 1)*/
long get_logonchange_data(char *file, char **controlName, long controlNames, double startTime, double endTime,
                          double **time0, double ***value0,
                          double ***downWithSchedule, double ***downWithBeam,
                          char *logfile, SDDS_DATASET *log_data) {
  long i, j, rows, index1, index2, timeIndex, valueIndex, previousRowIndex, controlNameIndexIndex, page, startrow, row, count, *index, keyGroups, desiredIndex = 0, actualIndex = 0, insertStart;
  int32_t *controlNameIndexData, *previousRowData;
  double **data, *valueData, *timeData, *rowData, *time, *desired, *actual, **down1, **down2;

  SDDS_DATASET dataset;
  SDDS_ARRAY *array, *array2;
  KEYED_EQUIVALENT **keyGroup;
  char **srModePV = NULL;

  insertStart = 0;
  rows = page = count = 0;
  valueData = timeData = rowData = time = NULL;
  controlNameIndexData = previousRowData = NULL;
  array = array2 = NULL;
  data = NULL;
  keyGroup = NULL;
  keyGroups = 0;
  index = NULL;
  desired = actual = NULL;
  down1 = down2 = NULL;

  if (!file) {
    fprintf(stderr, "Error(convert_logonchange_data): file is not provided for converting logonchange data!\n");
    exit(1);
  }
  if (!controlName || !controlNames) {
    fprintf(stderr, "Error(get_logonchange_data): controlName is not provided.\n");
    exit(1);
  }
  srModePV = (char **)malloc(sizeof(*srModePV) * 2);
  SDDS_CopyString(&srModePV[0], "S:DesiredMode");
  SDDS_CopyString(&srModePV[1], "S:ActualMode");

  data = (double **)malloc(sizeof(*data) * controlNames);
  down1 = (double **)malloc(sizeof(*down1) * controlNames);
  down2 = (double **)malloc(sizeof(*down2) * controlNames);
  for (i = 0; i < controlNames; i++)
    data[i] = NULL;

  if (!SDDS_InitializeInput(&dataset, file)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  index1 = SDDS_VerifyArrayExists(&dataset, FIND_SPECIFIED_TYPE, SDDS_STRING, "ReadbackName");
  index2 = SDDS_VerifyArrayExists(&dataset, FIND_SPECIFIED_TYPE, SDDS_STRING, "ControlName");
  if (index1 == -1 && index2 == -1) {
    fprintf(stderr, "Error(convert_logonchange_data): ReadbackName and ControlName arrays are both missing from the input file.\n");
    exit(1);
  }
  if ((timeIndex = SDDS_VerifyColumnExists(&dataset, FIND_NUMERIC_TYPE, "Time")) == -1) {
    fprintf(stderr, "Error(convert_logonchange_data): Time column is missing\n");
    exit(1);
  }
  if ((valueIndex = SDDS_VerifyColumnExists(&dataset, FIND_NUMERIC_TYPE, "Value")) == -1) {
    fprintf(stderr, "Error(convert_logonchange_data): Value column is missing\n");
    exit(1);
  }
  if ((controlNameIndexIndex = SDDS_VerifyColumnExists(&dataset, FIND_INTEGER_TYPE, "ControlNameIndex")) == -1) {
    fprintf(stderr, "Error(convert_logonchange_data): ControlNameIndex column is missing\n");
    exit(1);
  }
  previousRowIndex = SDDS_VerifyColumnExists(&dataset, FIND_INTEGER_TYPE, "PreviousRow");

  while (SDDS_ReadPage(&dataset) > 0) {
    page++;
    if ((rows = SDDS_CountRowsOfInterest(&dataset)) <= 0) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
      exit(1);
    }
    if (page == 1) {
      if (index1 != -1) {
        array = SDDS_GetArray(&dataset, "ReadbackName", NULL);
      } else {
        array = SDDS_GetArray(&dataset, "ControlName", NULL);
      }
      if (array == NULL) {
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
        exit(1);
      }
      rowData = calloc(sizeof(double), array->elements);
      keyGroup = MakeSortedKeyGroups(&keyGroups, SDDS_STRING, array->data, array->elements);
      index = (long *)malloc(sizeof(*index) * controlNames);
      for (i = 0; i < controlNames; i++) {
        if ((j = FindMatchingKeyGroup(keyGroup, keyGroups, SDDS_STRING, controlName + i, 0)) < 0) {
          fprintf(stderr, "Error(get_logonchange_data): %s does not exist in input file array.\n", controlName[i]);
          exit(1);
        }
        index[i] = j;
      }
      desiredIndex = FindMatchingKeyGroup(keyGroup, keyGroups, SDDS_STRING, srModePV, 0);
      actualIndex = FindMatchingKeyGroup(keyGroup, keyGroups, SDDS_STRING, srModePV + 1, 0);
      if (desiredIndex == -1) {
        fprintf(stderr, "Error(get_logonchange_data): S:DesiredMode does not exist in input file array.\n");
        exit(1);
      }
      if (actualIndex == -1) {
        fprintf(stderr, "Error(get_logonchange_data): S:ActualMode does not exist in input file array.\n");
        exit(1);
      }
    } else {
      if (index1 != -1) {
        array2 = SDDS_GetArray(&dataset, "ReadbackName", NULL);
      } else {
        array2 = SDDS_GetArray(&dataset, "ControlName", NULL);
      }
      if (array2 == NULL) {
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
        exit(1);
      }
      if (array->elements != array2->elements) {
        fprintf(stderr, "Error: Unable to process multiple pages with different ReadbackName and/or ControlName columns\n");
        exit(1);
      }
      for (i = 0; i < array->elements; i++) {
        if (strcmp((char *)((char **)array->data)[i], (char *)((char **)array2->data)[i]) != 0) {
          fprintf(stderr, "Error: Unable to process multiple pages with different ReadbackName and/or ControlName columns\n");
          exit(1);
        }
      }
      SDDS_FreeArray(array2);
    }
    if ((timeData = SDDS_GetColumnInDoubles(&dataset, "Time")) == NULL ||
        (valueData = SDDS_GetColumnInDoubles(&dataset, "Value")) == NULL ||
        (controlNameIndexData = SDDS_GetColumnInLong(&dataset, "ControlNameIndex")) == NULL) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
      exit(1);
    }
    startrow = 0;
    if (previousRowIndex != -1) {
      if ((previousRowData = SDDS_GetColumnInLong(&dataset, "PreviousRow")) == NULL) {
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
        exit(1);
      }
      for (row = 0; row < rows; row++) {
        if (previousRowData[row] < 0)
          startrow = row;
        else
          break;
      }
    }
    for (row = 0; row < rows; row++) {
      rowData[controlNameIndexData[row]] = valueData[row];
      if (row >= startrow) {
        if (timeData[row] < startTime)
          continue;
        if (endTime && timeData[row] > endTime) {
          break;
        }
        if (!count && startTime && (timeData[row] - 2) > startTime) {
          /*insert start time value */
          time = SDDS_Realloc(time, sizeof(double) * (count + 1));
          actual = SDDS_Realloc(actual, sizeof(double) * (count + 1));
          desired = SDDS_Realloc(desired, sizeof(double) * (count + 1));
          for (i = 0; i < controlNames; i++)
            data[i] = SDDS_Realloc(data[i], sizeof(double) * (count + 1));
          time[count] = startTime;
          insertStart = 1;
          count++;
        }
        time = SDDS_Realloc(time, sizeof(double) * (count + 1));
        actual = SDDS_Realloc(actual, sizeof(double) * (count + 1));
        desired = SDDS_Realloc(desired, sizeof(double) * (count + 1));
        time[count] = timeData[row];
        for (i = 0; i < controlNames; i++) {
          data[i] = SDDS_Realloc(data[i], sizeof(double) * (count + 1));
          data[i][count] = rowData[index[i]];
        }
        actual[count] = rowData[actualIndex] == 4.0 ? 1.0 : 0;
        desired[count] = rowData[desiredIndex] == 1.0 ? 1.0 : 0;
        actual[count] = actual[count] * (desired[count]);
        if (insertStart) {
          for (i = 0; i < controlNames; i++)
            data[i][count - 1] = !(data[i][count]);
          actual[count - 1] = !(actual[count]);
          desired[count - 1] = !(desired[count]);
          insertStart = 0;
        }
        count++;
      }
    }
    free(timeData);
    free(valueData);
    free(controlNameIndexData);
    if (previousRowIndex != -1)
      free(previousRowData);
  } /*end of read table*/
  if (!SDDS_Terminate(&dataset)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  SDDS_FreeArray(array);
  if (endTime && count && time[count - 1] < endTime) {
    /*insert end time value */
    time = SDDS_Realloc(time, sizeof(double) * (count + 1));
    time[count] = endTime;
    for (i = 0; i < controlNames; i++) {
      data[i] = SDDS_Realloc(data[i], sizeof(double) * (count + 1));
      data[i][count] = data[i][count - 1];
    }
    actual = SDDS_Realloc(actual, sizeof(double) * (count + 1));
    desired = SDDS_Realloc(desired, sizeof(double) * (count + 1));
    actual[count] = actual[count - 1];
    desired[count] = desired[count - 1];
    count++;
  }
  for (i = 0; i < controlNames; i++) {
    down1[i] = (double *)calloc(sizeof(double), count);
    down2[i] = (double *)calloc(sizeof(double), count);
    for (j = 0; j < count; j++) {
      down1[i][j] = (data[i][j] - desired[j]) == -1.0 ? 1 : 0;
      down2[i][j] = (data[i][j] - actual[j]) == -1 ? 1 : 0;
      data[i][j] = !(data[i][j]);
    }
  }
  if (logfile && count) {
    char *timestamp, tmp1[256], tmp2[256];
    timestamp = makeTimeStamp(time[0]);
    if (!SDDS_StartPage(log_data, count)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
      exit(1);
    }
    if (!SDDS_SetParameters(log_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, "TimeStamp", timestamp,
                            "StartTime", time[0], NULL)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
      exit(1);
    }
    for (i = 0; i < controlNames; i++) {
      sprintf(tmp1, "%sDownWithSchedule", controlName[i]);
      sprintf(tmp2, "%sDownWithBeam", controlName[i]);
      if (!SDDS_SetColumnFromDoubles(log_data, SDDS_SET_BY_NAME, time, count, "Time") ||
          !SDDS_SetColumnFromDoubles(log_data, SDDS_SET_BY_NAME, data[i], count, controlName[i]) ||
          !SDDS_SetColumnFromDoubles(log_data, SDDS_SET_BY_NAME, down1[i], count, tmp1) ||
          !SDDS_SetColumnFromDoubles(log_data, SDDS_SET_BY_NAME, down2[i], count, tmp2)) {
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
        exit(1);
      }
    }
    if (!SDDS_WritePage(log_data)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
      exit(1);
    }
  }
  free(desired);
  free(actual);
  free(rowData);
  free(index);
  for (i = 0; i < keyGroups; i++) {
    free(keyGroup[i]->equivalent);
    free(keyGroup[i]);
  }
  free(keyGroup);
  free(srModePV[0]);
  free(srModePV[1]);
  free(srModePV);

  *value0 = data;
  *time0 = time;
  *downWithSchedule = down1;
  *downWithBeam = down2;
  return count;
}

void SetupOutputFile(char *outputfile, SDDS_DATASET *out_data) {
  if (!SDDS_InitializeOutput(out_data, SDDS_BINARY, 1, NULL, NULL, outputfile)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  if (SDDS_DefineParameter(out_data, "StartTimeStamp", NULL, NULL, NULL, NULL, SDDS_STRING, NULL) < 0 ||
      SDDS_DefineParameter(out_data, "EndTimeStamp", NULL, NULL, NULL, NULL, SDDS_STRING, NULL) < 0 ||
      !SDDS_DefineSimpleColumn(out_data, "Beamline", NULL, SDDS_STRING) ||
      SDDS_DefineColumn(out_data, "TotalDownTime", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
      SDDS_DefineColumn(out_data, "DownWithSchedule", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
      SDDS_DefineColumn(out_data, "DownWithBeam", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }

  if (!SDDS_SaveLayout(out_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  if (!SDDS_WriteLayout(out_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
}

void SetupLogFile(char *logfile, SDDS_DATASET *log_data, char **controlName, long controlNames) {
  long i;
  char tmp1[256], tmp2[256];

  if (!SDDS_InitializeOutput(log_data, SDDS_BINARY, 1, NULL, NULL, logfile)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  if (SDDS_DefineParameter(log_data, "TimeStamp", NULL, NULL, NULL, NULL, SDDS_STRING, NULL) < 0 ||
      SDDS_DefineParameter(log_data, "StartTime", NULL, "s", NULL, NULL, SDDS_DOUBLE, NULL) < 0 ||
      !SDDS_DefineSimpleColumn(log_data, "Time", NULL, SDDS_DOUBLE) ||
      !SDDS_DefineSimpleColumn(log_data, "S:DesiredMode", NULL, SDDS_DOUBLE) ||
      !SDDS_DefineSimpleColumn(log_data, "S:ActualMode", NULL, SDDS_DOUBLE)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  for (i = 0; i < controlNames; i++) {
    sprintf(tmp1, "%sDownWithSchedule", controlName[i]);
    sprintf(tmp2, "%sDownWithBeam", controlName[i]);
    if (!SDDS_DefineSimpleColumn(log_data, controlName[i], NULL, SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(log_data, tmp1, NULL, SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(log_data, tmp2, NULL, SDDS_DOUBLE)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
      exit(1);
    }
  }
  if (!SDDS_SaveLayout(log_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  if (!SDDS_WriteLayout(log_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
}

/* this function compute the down time */
double computeDownTime(short *first, double *prevtime, double *time, double *down, long rows) {
  long i;
  double downtime = 0;

  for (i = 0; i < rows; i++) {
    if (down[i]) {
      /* record the time at the first down point */
      if (*first) {
        *prevtime = time[i];
        *first = 0;
      } else if (i == rows - 1) {
        /* if the last point is down and it is not the first down point, add up the down time*/
        downtime += time[i] - (*prevtime);
        *prevtime = time[i];
        *first = 0;
      }
      /* else skip the continuous down point, until the first not-down point is reached,
         and then compute the downtime to the first not-down point */
    } else {
      if (!(*first)) {
        /* now, need add up the downtime, since it is no longer down*/
        downtime += time[i] - (*prevtime);
        /* reset the first to 1 */
        *first = 1;
      }
    }
  }
  return downtime;
}
