/*
 * This program combines the schedule data for ID stations,
 * PSS logonchange data and attendance (FloorCoord) logonchange data
 * and tranfer them into a data file that contains the required information
 * for computing the PSS station down time;
 * the reason for separating it from computing downtime is that this
 * this information is only needed to  compute once for each run (there
 * 3-run one year).
 */

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

#define CLO_LOG_DIRECTORY 0
#define CLO_ROOTNAME 1
#define CLO_START_TIME 2
#define CLO_END_TIME 3
#define CLO_STATION_FILE 4
#define CLO_VERBOSE 5
#define OPTIONS 6

char *option[OPTIONS] = {
  "logDirectory", "rootname", "startTime", "endTime", "stationFile", "verbose"};

#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 TODAY_GIVEN 0x0040U

static char *USAGE = "preparePSSLogData <schedulefile> <outputDir> \n\
  [-logDirectory=<string>] [-rootname=PSS=<string>,FloorCoord=<string>]   \n\
  [-stationFile=<station file name>] \n\
  [-startTime={[year=<integer>,][month=<integer>,][day=<integer>]] | time=<value in seconds>] \n\
  [-endTime={[year=<integer>,][month=<integer>,][day=<integer>,]] | time=<value in seconds>] \n\
schedulefile            SDDS input file contains the schedule information for beamline stations.\n\
                        It has StartTime, and columns for beamline stations (for example: \n\
                        PA:01ID:B is the column name for ID 01 station B). \n\
                        This file is similar to the logonchange data file.\n\
outputDir               provide the output directory for writing the output files, each output files\n\
                        is named as  <outputDir>/<station_name>.sdds (e.g., <outputDir>/PA:01BM:C.sdds) \n\
                        Each output file contains the schedule, \n\
                        PSS station status (including the shutter, beam state), and attendance \n\
                        information for each station with the merged time from schedule file, PSS \n\
                        logonchange files for this schedule (entire run), and FloorCoord logonchange \n\
                        files for this schedule (entire run).\n\
logDirectory            the main log directory for PSS and attendance log data. \n\
                        default is /home/helios/oagData/logonchange. \n\
rootname                PPS provide the rootname of PSS log file, and FloorCoord porivide\n\
                        the rootname of the FloorCoord log file names. \n\
startTime, endTime      startTime and endTime provide the effective time range for outputing data to \n\
                        processing time and the output file size. Since the scheduel \n\
                        contains machine shutdown time, which is not considered in station downtime \n\
                        computation. For each schedule (run), the startTime should be the machine startup date,\n\
                        and the endTime should be machine shut down date for that particular run.\n\
stationFile             the file name that contains station name. It must has Station column for station names.\n\
                        If not provided, the station names will be obtained from schedule file.\n\
verbose                 print out message.\n\n\
Prepares PSS station log data. Programed by H. Shang ("__DATE__").\n";

typedef struct {
  long stations;
  char **stationName;
  char **beamReadyName, **beamActiveName, **globalOnlineName;
  char **acisShutterPermitName, **pssErrorName, **apsKeyName, **feShutterPermitName;
  char **absentControlName;
  short *beamReady, *beamActive, *globalOnline, *acisShutterPermit, *feShutterPermit;
  short *isAbsent, *pssError, *apsKey, *aps_fault, *srDesired, *srActual;
  short *exist, *pss_error_logged;
} PSS_STATION;
/*aps_fault is 1 if any of globalonline=0, acis shutter permit=0, FE shutter permit=0, aps enable key=0, PSS error!=0 is true */
/*through discussion with Yeldez Amer and Marty Smith on 10/10/2006, looks like that station shutter status was not used
  for determining downtime, but beam_active; however, it is included inside beam_ready and beam_active;
  so shutterClosed data do not need here;
  aps_fault is defined as any one of globalonline=0, acisShutterPermint=0, feShutterPermit=0, pssError!=0, apsKey=0 is true.
  however, we did not log PSS errors before 10/2006, so we use beam_ready and acisShutterPermit to check if
  it aps's fault even though beam not ready
  could be because of user's fault. (beam_ready containes everything of aps_fault but acisShutterPermit)
  the final output for each station should have following columns
  date (time), scheduled, beamReady (may not need), beamActive, aps_fault, S:DesiredMode, S:ActualMode, IsAbsent
*/

void allocate_pss_station_mem(PSS_STATION *pss_station);
void free_pss_station_value_mem(PSS_STATION *pss_station, long index);
void initialize_pss_station(PSS_STATION *pss_station);
void free_pss_station_name_mem(PSS_STATION *pss_station, long input_stations);

long get_schedule_information(char *scheduleFile, char *stationFile, char ***stationName, long *stations,
                              double **scheduleTime, short ***stationSchedule);

/*this function get the controlName of station shutter closed, station_absent from the station names */
void process_control_names(PSS_STATION *pss_station);

/*this function get the logonchange value for one station from one logonchange log file */
long get_logonchange_data(char *file, char **controlName, long controlNames, long sector, char st_name,
                          short **data, double **time0, short *pss_error_logged);

/* this function gets one station status data from all the PSS log files */
long get_station_status_data(char **fileList, long files, PSS_STATION *pss_station, long index, long sector, char st_name,
                             double **PSSTime, char **srModePV, double *starttime);

/* this function gets the (one) station attendance data from all the station attendance log files */
long get_floorcoord_data(char **fileList, long files, char *controlName, long sector, char st_name,
                         double **FCTime, short **absent0, double *starttime);

double *merge_and_sort_time(double *scheduleTime, long scheduleRows,
                            double *PSSTime, long PSSRows,
                            double *attendanceTime, long attendanceRows,
                            double startTime, double endTime, long *newPoints);

void SetupOutputFile(char *outputfile, SDDS_DATASET *out_data);

double computeDownTime(short *first, double *prevtime, double *time, double *down, long rows);

short interp_short(short *f, double *x, long n, double x0, long warnings, long order,
                   long *returnCode, long *next_start_pos);
short interp_absent(short *absent, double *FCTime, long FCRows,
                    double *scheduleTime, long scheduleRows, double time0);

static int verbose = 0;

int main(int argc, char **argv) {
  long i_arg, i, j, row, extensions, PSSLogFiles, FCLogFiles, scheduleRows, PSSRows, FCRows, nextPos, returnCode, nextPos1, nextPos2, nextPos3, logData, sector = 0, input_stations = 0;
  unsigned long dummyFlags = 0;
  SDDS_DATASET *outData;
  char st_name = 0;

  SCANNED_ARG *s_arg;
  char *scheduleFile, *outputDir, *filter, **extensionList, *suffix, **PSSLogFileList, **FCLogFileList, *stationFile;
  char *logDir = "/home/helios/oagData/logonchange";
  char *PSSRootname = "PSS";
  char *FCRootname = "FloorCoord";
  char **srModePV, *stamp, *stamp1, **input_stationName = NULL;

  char rootname[1024], tmpDir[1024], tmpstr[256];
  double startTime, endTime, *scheduleTime, *PSSTime, *FCTime;
  short startYear, startJDay, endYear, endJDay, startMonth, startDay, endMonth, endDay;
  long *schedulePos, *PSSPos, *FCPos, totalCount;

  /*final results after merge with time, and interpolate */
  double *totalTime, PSSStartTime, FCStartTime;
  short schedule, ready, desired, actual, absent, absent0, active, aps_fault;
  PSS_STATION pss_station;
  short **stationSchedule;

  stamp = stamp1 = NULL;
  schedulePos = PSSPos = FCPos = NULL;
  scheduleFile = outputDir = stationFile = NULL;
  filter = suffix = NULL;
  extensionList = NULL;
  stationSchedule = NULL;
  scheduleTime = PSSTime = FCTime = NULL;
  scheduleRows = PSSRows = FCRows = totalCount = 0;
  PSSLogFiles = FCLogFiles = 0;
  PSSLogFileList = FCLogFileList = NULL;
  startYear = endYear = startJDay = endJDay = 0;

  totalTime = NULL;

  PSSStartTime = FCStartTime = 0;
  startTime = endTime = 0;
  startYear = endYear = startMonth = endMonth = startDay = endDay = 0;
  logData = 0;
  srModePV = (char **)malloc(sizeof(*srModePV) * 2);
  SDDS_CopyString(&srModePV[0], "S:DesiredMode");
  SDDS_CopyString(&srModePV[1], "S:ActualMode");

  argc = scanargs(&s_arg, argc, argv);
  if (argc < 2)
    bomb(NULL, USAGE);

  initialize_pss_station(&pss_station);
  /* get today's date -- year, month and day */
  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_ROOTNAME:
        if (s_arg[i_arg].n_items < 2)
          SDDS_Bomb("Invalid -rootname syntax");
        s_arg[i_arg].n_items--;
        if (!scanItemList(&dummyFlags, s_arg[i_arg].list + 1, &s_arg[i_arg].n_items, 0,
                          "PSS", SDDS_STRING, &PSSRootname, 1, 0,
                          "FloorCoord", SDDS_STRING, &FCRootname, 1, 0,
                          NULL))
          SDDS_Bomb("Invalid -rootname syntax");
        s_arg[i_arg].n_items++;
        break;
      case CLO_LOG_DIRECTORY:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("Invalid -logDirectory syntax.");
        logDir = s_arg[i_arg].list[1];
        break;
      case CLO_START_TIME:
        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,
                          "time", SDDS_DOUBLE, &startTime, 1, TIME_GIVEN,
                          NULL))
          SDDS_Bomb("invalid -startTime syntax.");
        s_arg[i_arg].n_items++;
        if (!(dummyFlags & TIME_GIVEN)) {
          if (startYear <= 0 || startMonth <= 0 || startDay <= 0)
            SDDS_Bomb("invalid -startTime syntax, year, month and day have to be provided.");
          TimeBreakdownToEpoch(startYear, 0, startMonth, startDay, 0, &startTime);
        }
        break;
      case CLO_END_TIME:
        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,
                          "time", SDDS_DOUBLE, &endTime, 1, TIME_GIVEN,
                          "today", -1, NULL, 0, TODAY_GIVEN,
                          NULL))
          SDDS_Bomb("invalid -endTime syntax.");
        s_arg[i_arg].n_items++;
        if (dummyFlags & TODAY_GIVEN) {
          endTime = getTimeInSecs();
        } else {
          if (!(dummyFlags & TIME_GIVEN)) {
            if (endYear <= 0 || endMonth <= 0 || endDay <= 0)
              SDDS_Bomb("invalid -endTime syntax, year, month and day have to be provided.");
            TimeBreakdownToEpoch(endYear, 0, endMonth, endDay, 24, &endTime);
          }
        }
        break;
      case CLO_STATION_FILE:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("invalid -stationFile syntax");
        stationFile = s_arg[i_arg].list[1];
        break;
      case CLO_VERBOSE:
        verbose = 1;
        break;
      default:
        bomb("unknown option given", USAGE);
        break;
      }
    } else {
      if (!scheduleFile)
        scheduleFile = s_arg[i_arg].list[0];
      else if (!outputDir)
        outputDir = s_arg[i_arg].list[0];
      else
        SDDS_Bomb("too many filenames given");
    }
  }

  if (!scheduleFile || !outputDir)
    SDDS_Bomb("Both scheduleFile and OutputDir has to be provided.");

  if (startTime && endTime && endTime < startTime)
    SDDS_Bomb("Invalid time range given, end time is smaller than start time.");

  cp_str(&filter, "-??\?-??\??");
  extensions = 1;
  extensionList = (char **)malloc(sizeof(*extensionList) * extensions);
  SDDS_CopyString(&extensionList[0], ".gz");

  /*get schedule info from schedule file */
  pss_station.stations = input_stations;
  pss_station.stationName = input_stationName;

  scheduleRows = get_schedule_information(scheduleFile, stationFile, &(pss_station.stationName), &(pss_station.stations),
                                          &scheduleTime, &stationSchedule);

  if (!pss_station.stations)
    SDDS_Bomb("No stations provided!");

  allocate_pss_station_mem(&pss_station);
  /*get the pv names for each station */
  process_control_names(&pss_station);

  /* get log files */
  if (!startTime)
    startTime = scheduleTime[0];
  if (!endTime)
    endTime = scheduleTime[scheduleRows - 1];
  TimeEpochToBreakdown(&startYear, &startJDay, NULL, NULL, NULL, startTime);
  TimeEpochToBreakdown(&endYear, &endJDay, NULL, NULL, NULL, endTime);

  /*get the PSS log files */
  sprintf(tmpDir, "%s/%s", logDir, PSSRootname);
  sprintf(rootname, "%s-", PSSRootname);
  PSSLogFileList = find_files_between_dates(tmpDir, rootname, suffix, startYear, 0, 0, startJDay,
                                            endYear, 0, 0, endJDay, filter, extensionList, extensions,
                                            0, &PSSLogFiles, 1);
  /*get attendance log files */
  sprintf(tmpDir, "%s/%s", logDir, FCRootname);
  sprintf(rootname, "%s-", FCRootname);
  FCLogFileList = find_files_between_dates(tmpDir, rootname, suffix, startYear, 0, 0, startJDay,
                                           endYear, 0, 0, endJDay, filter, extensionList, extensions,
                                           0, &FCLogFiles, 1);
  /*above has been tested.*/
  /*setup output file*/
  outData = NULL;
  if (!(outData = malloc(sizeof(*outData) * pss_station.stations)))
    SDDS_Bomb("Memory allocation error.");

  for (i = 0; i < pss_station.stations; i++) {
    if (!(pss_station.exist[i]))
      continue;
    fprintf(stdout, "processing data for station %s\n", pss_station.stationName[i]);
    /* get PSS logonchange data for one station*/
    if (sscanf(pss_station.stationName[i], "PA:%ld", &sector) != 1) {
      fprintf(stderr, "Invalid station name - %s given.\n", pss_station.stationName[i]);
      exit(1);
    }
    st_name = pss_station.stationName[i][strlen(pss_station.stationName[i]) - 1];
    PSSRows = get_station_status_data(PSSLogFileList, PSSLogFiles, &pss_station, i, sector, st_name,
                                      &PSSTime, srModePV, &PSSStartTime);
    /* get attendance logonchange data for one station */
    FCRows = get_floorcoord_data(FCLogFileList, FCLogFiles, pss_station.absentControlName[i], sector, st_name,
                                 &FCTime, &(pss_station.isAbsent), &FCStartTime);
    /* if (startTime<PSSTime[0])
       startTime = PSSTime[0];
       if (startTime<FCTime[0])
       startTime = FCTime[0]; */

    /* merge time data */
    totalTime = merge_and_sort_time(scheduleTime, scheduleRows, PSSTime,
                                    PSSRows, FCTime, FCRows, startTime, endTime, &totalCount);

    sprintf(tmpstr, "%s/%s.sdds", outputDir, pss_station.stationName[i]);
    SetupOutputFile(tmpstr, &outData[i]);
    if (!SDDS_StartPage(&outData[i], totalCount)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      exit(1);
    }

    nextPos = nextPos1 = nextPos2 = nextPos3 = 0;
    for (row = 0; row < totalCount; row++) {
      /* inherit value from previous point, order=-1 */
      desired = interp_short(pss_station.srDesired, PSSTime, PSSRows,
                             totalTime[row], 0, -1, &returnCode, &nextPos);
      /* since nextPos = lo, it will be the same for inserting PSS data*/
      actual = pss_station.srActual[nextPos];
      aps_fault = pss_station.aps_fault[nextPos];
      ready = pss_station.beamReady[nextPos];
      active = pss_station.beamActive[nextPos];

      /* find the position for schedule and FC data */
      schedule = interp_short(stationSchedule[i], scheduleTime,
                              scheduleRows, totalTime[row],
                              0, -1, &returnCode, &nextPos1);
      /* do not insert absent or dispute value,
         since the one time within one 8-hour shift is marked as absent, then
         the whole shift is consider to be absent. */
      absent0 = 0;
      for (j = 0; j < FCRows; j++) {
        if (totalTime[row] == FCTime[j]) {
          absent0 = pss_station.isAbsent[j];
          break;
        }
      }
      absent = interp_absent(pss_station.isAbsent, FCTime, FCRows,
                             scheduleTime, scheduleRows, totalTime[row]);
      /*absent0 is the absent data without interperlating */
      if (!SDDS_SetRowValues(&outData[i], SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, row,
                             "Scheduled", schedule,
                             "BeamActive", active,
                             "BeamReady", ready,
                             "APS_Fault", aps_fault,
                             "IsAbsent", (absent == 1) ? 1 : 0,
                             "IsAbsent0", (absent0 == 1) ? 1 : 0,
                             "S:DesiredMode", desired,
                             "S:ActualMode", actual,
                             NULL)) {
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
        exit(1);
      }
    }
    SDDS_CopyString(&stamp, makeTimeStamp(startTime));
    SDDS_CopyString(&stamp1, makeTimeStamp(endTime));
    if (!SDDS_SetParameters(&outData[i], SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                            "StartTime", startTime,
                            "EndTime", endTime,
                            "StartTimeStamp", stamp,
                            "EndTimeStamp", stamp1, NULL) ||
        !SDDS_SetColumnFromDoubles(&outData[i], SDDS_SET_BY_NAME, totalTime, totalCount, "Time"))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    if (!SDDS_WritePage(&outData[i]) || !SDDS_Terminate(&outData[i]))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    free(PSSTime);
    free(FCTime);
    free(totalTime);
    PSSTime = FCTime = NULL;
    totalTime = NULL;
    totalCount = PSSRows = FCRows = 0;
    free_pss_station_value_mem(&pss_station, i);
  }
  free(outData);
  free(scheduleTime);
  free_pss_station_name_mem(&pss_station, input_stations);
  for (i = 0; i < pss_station.stations; i++)
    free(stationSchedule[i]);
  free(stationSchedule);
  SDDS_FreeStringArray(PSSLogFileList, PSSLogFiles);
  free(PSSLogFileList);
  SDDS_FreeStringArray(FCLogFileList, FCLogFiles);
  free(FCLogFileList);
  SDDS_FreeStringArray(srModePV, 2);
  free(srModePV);
  free(filter);
  SDDS_FreeStringArray(extensionList, extensions);
  free(extensionList);
  free_scanargs(&s_arg, argc);
  return 0;
}

void initialize_pss_station(PSS_STATION *pss_station) {
  pss_station->stations = 0;
  pss_station->stationName = pss_station->beamReadyName =
    pss_station->beamActiveName = pss_station->globalOnlineName =
    pss_station->acisShutterPermitName = pss_station->feShutterPermitName =
    pss_station->pssErrorName = pss_station->apsKeyName = pss_station->absentControlName = NULL;
  pss_station->isAbsent = pss_station->exist = pss_station->pssError = pss_station->apsKey = pss_station->aps_fault = pss_station->beamReady = pss_station->beamActive = pss_station->globalOnline = pss_station->acisShutterPermit =
    pss_station->feShutterPermit = pss_station->srDesired = pss_station->srActual = NULL;
}

void allocate_pss_station_mem(PSS_STATION *pss_station) {
  if (pss_station->stations) {
    if (!(pss_station->beamReadyName = malloc(sizeof(char *) * pss_station->stations)) ||
        !(pss_station->beamActiveName = malloc(sizeof(char *) * pss_station->stations)) ||
        !(pss_station->globalOnlineName = malloc(sizeof(char *) * pss_station->stations)) ||
        !(pss_station->acisShutterPermitName = malloc(sizeof(char *) * pss_station->stations)) ||
        !(pss_station->pssErrorName = malloc(sizeof(char *) * pss_station->stations)) ||
        !(pss_station->apsKeyName = malloc(sizeof(char *) * pss_station->stations)) ||
        !(pss_station->feShutterPermitName = malloc(sizeof(char *) * pss_station->stations)) ||
        !(pss_station->absentControlName = malloc(sizeof(char *) * pss_station->stations)) ||
        !(pss_station->exist = malloc(sizeof(short) * pss_station->stations)))
      SDDS_Bomb("Memory allocation failure!");
  }
}

void free_pss_station_value_mem(PSS_STATION *pss_station, long index) {
  if (pss_station->beamReady)
    free(pss_station->beamReady);
  if (pss_station->beamActive)
    free(pss_station->beamActive);
  if (pss_station->globalOnline)
    free(pss_station->globalOnline);
  if (pss_station->acisShutterPermit)
    free(pss_station->acisShutterPermit);
  if (pss_station->feShutterPermit)
    free(pss_station->feShutterPermit);
  if (pss_station->isAbsent)
    free(pss_station->isAbsent);
  if (pss_station->pssError)
    free(pss_station->pssError);
  if (pss_station->apsKey)
    free(pss_station->apsKey);
  if (pss_station->aps_fault)
    free(pss_station->aps_fault);
  if (pss_station->srDesired)
    free(pss_station->srDesired);
  if (pss_station->srActual)
    free(pss_station->srActual);
  pss_station->beamReady = pss_station->beamActive = pss_station->globalOnline = pss_station->acisShutterPermit = pss_station->feShutterPermit = pss_station->isAbsent = pss_station->pssError = pss_station->apsKey = pss_station->aps_fault = pss_station->srDesired = pss_station->srActual = NULL;
  if (pss_station->beamReadyName[index])
    free(pss_station->beamReadyName[index]);
  if (pss_station->beamActiveName[index])
    free(pss_station->beamActiveName[index]);
  if (pss_station->globalOnlineName[index])
    free(pss_station->globalOnlineName[index]);
  if (pss_station->acisShutterPermitName[index])
    free(pss_station->acisShutterPermitName[index]);
  if (pss_station->pssErrorName[index])
    free(pss_station->pssErrorName[index]);
  if (pss_station->apsKeyName[index])
    free(pss_station->apsKeyName[index]);
  if (pss_station->feShutterPermitName[index])
    free(pss_station->feShutterPermitName[index]);
  if (pss_station->absentControlName[index])
    free(pss_station->absentControlName[index]);
}

void free_pss_station_name_mem(PSS_STATION *pss_station, long input_station) {
  if (!input_station)
    if (pss_station->stationName)
      free(pss_station->stationName);
  if (pss_station->beamReadyName)
    free(pss_station->beamReadyName);
  if (pss_station->beamActiveName)
    free(pss_station->beamActiveName);
  if (pss_station->globalOnlineName)
    free(pss_station->globalOnlineName);
  if (pss_station->acisShutterPermitName)
    free(pss_station->acisShutterPermitName);
  if (pss_station->pssErrorName)
    free(pss_station->pssErrorName);
  if (pss_station->apsKeyName)
    free(pss_station->apsKeyName);
  if (pss_station->feShutterPermitName)
    free(pss_station->feShutterPermitName);
  if (pss_station->absentControlName)
    free(pss_station->absentControlName);
  if (pss_station->exist)
    free(pss_station->exist);
}

void process_control_names(PSS_STATION *pss_station) {
  char ready[256], active[256], online[256], acis_shutter[256], fe_shutter[256], pss_error[256], aps_key[256], absent[256];
  char beam_type[18];
  long len, i, sector;

  for (i = 0; i < pss_station->stations; i++) {
    len = strlen(pss_station->stationName[i]);
    if (sscanf(pss_station->stationName[i], "PA:%ld", &sector) != 1) {
      fprintf(stderr, "Invalid station name - %s given.\n", pss_station->stationName[i]);
      exit(1);
    }
    if (strstr(pss_station->stationName[i], "ID"))
      sprintf(beam_type, "ID");
    else if (strstr(pss_station->stationName[i], "BM"))
      sprintf(beam_type, "BM");
    else {
      fprintf(stderr, "Invalid station name - %s given.\n", pss_station->stationName[i]);
      exit(1);
    }

    sprintf(absent, "S%ld%s%c:MarkedAbsent", sector, beam_type, pss_station->stationName[i][len - 1]);
    sprintf(online, "PA:%02ld%s:IACIS_GLBONLN", sector, beam_type);
    sprintf(acis_shutter, "PA:%02ld%s:IACIS_FEPERM", sector, beam_type);
    sprintf(fe_shutter, "PA:%02ld%s:IFEEPS_FEPERM", sector, beam_type);
    sprintf(ready, "%s_BEAM_READY", pss_station->stationName[i]);
    sprintf(active, "%s_BEAM_ACTIVE", pss_station->stationName[i]);
    sprintf(aps_key, "%s_APS_STATUS", pss_station->stationName[i]);
    sprintf(pss_error, "PA:%02ld%s:FLT:1", sector, beam_type);

    if (sector == 8 && pss_station->stationName[i][len - 1] == 'I') {
      /*pv of 8ID I station are special, maybe I station should be F station instead.*/
      sprintf(ready, "%s", "PA:08ID:F_BEAM_READY");
      sprintf(active, "%s", "PA:08ID:F_BEAM_ACTIVE");
      sprintf(aps_key, "PA:08ID:F_APS_STATUS");
    }
    pss_station->exist[i] = 1;

    SDDS_CopyString(&pss_station->beamReadyName[i], ready);
    SDDS_CopyString(&pss_station->beamActiveName[i], active);
    SDDS_CopyString(&pss_station->globalOnlineName[i], online);
    SDDS_CopyString(&pss_station->acisShutterPermitName[i], acis_shutter);
    SDDS_CopyString(&pss_station->feShutterPermitName[i], fe_shutter);
    SDDS_CopyString(&pss_station->pssErrorName[i], pss_error);
    SDDS_CopyString(&pss_station->apsKeyName[i], aps_key);
    SDDS_CopyString(&pss_station->absentControlName[i], absent);
  }
}

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 | SDDS_EXIT_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_DefineParameter(out_data, "StartTime", NULL, NULL, NULL, NULL, SDDS_DOUBLE, NULL) < 0 ||
      SDDS_DefineParameter(out_data, "EndTime", NULL, NULL, NULL, NULL, SDDS_DOUBLE, NULL) < 0 ||
      SDDS_DefineColumn(out_data, "Time", NULL, NULL, NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
      SDDS_DefineColumn(out_data, "S:DesiredMode", NULL, NULL, NULL, NULL, SDDS_SHORT, 0) < 0 ||
      SDDS_DefineColumn(out_data, "S:ActualMode", NULL, NULL, NULL, NULL, SDDS_SHORT, 0) < 0 ||
      SDDS_DefineColumn(out_data, "Scheduled", NULL, NULL, NULL, NULL, SDDS_SHORT, 0) < 0 ||
      SDDS_DefineColumn(out_data, "BeamActive", NULL, NULL, NULL, NULL, SDDS_SHORT, 0) < 0 ||
      SDDS_DefineColumn(out_data, "BeamReady", NULL, NULL, NULL, NULL, SDDS_SHORT, 0) < 0 ||
      SDDS_DefineColumn(out_data, "APS_Fault", NULL, NULL, NULL, NULL, SDDS_SHORT, 0) < 0 ||
      SDDS_DefineColumn(out_data, "IsAbsent", NULL, NULL, NULL, NULL, SDDS_SHORT, 0) < 0 ||
      SDDS_DefineColumn(out_data, "IsAbsent0", NULL, NULL, NULL, NULL, SDDS_SHORT, 0) < 0) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }

  if (!SDDS_SaveLayout(out_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }
  if (!SDDS_WriteLayout(out_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_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;
}

long get_logonchange_data(char *file, char **controlName, long controlNames, long sector, char st_name,
                          short **data, double **time0, short *pss_error_logged) {
  long i, rows, index1, index2, timeIndex, valueIndex, controlNameIndexIndex, page, row, count, index, count0;
  int32_t *controlNameIndexData;
  double *time, *timeData, hours = 0, hours0 = 0;
  short *valueData, *rowData, *exist;
  long *origToNewIndex = NULL;
  char new_name[50]; /*the maximum PV name length is 40 */

  SDDS_DATASET dataset;
  SDDS_ARRAY *array, *array2;

  rows = page = count = 0;
  timeData = time = NULL;
  valueData = rowData = NULL;
  exist = NULL;
  controlNameIndexData = NULL;
  array = array2 = NULL;
  if (!data) {
    fprintf(stderr, "Memory is not allocated for data!\n");
    exit(1);
  }
  *pss_error_logged = 1;
  for (i = 0; i < controlNames; i++)
    data[i] = NULL;
  if (!(exist = malloc(sizeof(*exist) * controlNames)))
    SDDS_Bomb("Memory allocation failure!");

  if (!file) {
    fprintf(stderr, "Error(convert_logonchange_data): file is not provided for converting logonchange data!\n");
    exit(1);
  }
  if (!controlName) {
    fprintf(stderr, "Error(get_logonchange_data): controlName is not provided.\n");
    exit(1);
  }
  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);
  }

  while (SDDS_ReadPage(&dataset) > 0) {
    page++;
    if ((rows = SDDS_CountRowsOfInterest(&dataset)) < 0) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      exit(1);
    }
    if (!rows)
      continue;
    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 | SDDS_EXIT_PrintErrors);
      exit(1);
    }
    rowData = calloc(sizeof(*rowData), array->elements);
    if (!(origToNewIndex = malloc(sizeof(*origToNewIndex) * array->elements)))
      SDDS_Bomb("Memory allocation error!");
    for (i = 0; i < array->elements; i++)
      origToNewIndex[i] = -1;
    for (i = 0; i < controlNames; i++) {
      exist[i] = 1;
      if ((index = match_string(controlName[i], (char **)array->data, array->elements, EXACT_MATCH)) < 0) {
        /* the station ready, active, aps_key PV names for ID 4 changed during 2006-Run3 */
        if (sector == 4) {
          switch (i) {
          case 0:
            /* station ready name */
            sprintf(new_name, "PC:04ID:%cBeamReady", st_name);
            break;
          case 1:
            /* station active name */
            sprintf(new_name, "PC:04ID:%cBeamActive", st_name);
            break;
          case 2:
            /* station global online name */
            sprintf(new_name, "PC:04ID:AGlobalOnline"); /*name used before 10/23/06*/
            if ((index = match_string(new_name, (char **)array->data, array->elements, EXACT_MATCH)) < 0) {
              sprintf(new_name, "PA:04ID:IA_ACIS_GLBONLN"); /*name probably used between 10/23/2006 and 1/30/2007 */
            }
            break;
          case 3:
            /* acis shutter permit */
            sprintf(new_name, "PC:04ID:AAcisFePerm");
            if ((index = match_string(new_name, (char **)array->data, array->elements, EXACT_MATCH)) < 0) {
              sprintf(new_name, "PA:04ID:IA_ACIS_FE_PERMIT"); /*name probably used between 10/23/2006 and 1/30/2007 */
            }
            break;
          case 4:
            /* pss error name, fault name */
            sprintf(new_name, "PA:04ID:FLT:1");
            break;
          case 5:
            /* aps key name */
            sprintf(new_name, "PC:04ID:%cApsStatus", st_name);
            break;
          case 6:
            /* front end shutter permit name */
            sprintf(new_name, "PC:04ID:AFeEpsPerm");
            if ((index = match_string(new_name, (char **)array->data, array->elements, EXACT_MATCH)) < 0) {
              sprintf(new_name, "PC:04ID:IC_FEEPS_FE_PERMIT"); /*name probably used between 10/23/2006 and 1/30/2007 */
            }
          default:
            sprintf(new_name, "Unknown");
            break;
          }
          if (verbose)
            fprintf(stdout, "The actual name in file %s is %s\n", file, new_name);
          index = match_string(new_name, (char **)array->data, array->elements, EXACT_MATCH);
          if (index == -1 && verbose)
            fprintf(stdout, "Warning, %s did not found in %s file.\n", new_name, file);
        }
      }
      if (index < 0) {
        exist[i] = 0;
        *pss_error_logged = 0;
      } else
        origToNewIndex[index] = i;
      /* the index of the index-th array element is i of the input control name */
      /* here index is the ControlName index in the array */
    }
    if ((timeData = SDDS_GetColumnInDoubles(&dataset, "Time")) == NULL ||
        (valueData = SDDS_GetColumnInShort(&dataset, "Value")) == NULL ||
        (controlNameIndexData = SDDS_GetColumnInLong(&dataset, "ControlNameIndex")) == NULL) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      exit(1);
    }

    for (row = 0; row < rows; row++) {
      if (origToNewIndex[controlNameIndexData[row]] == -1)
        continue;
      rowData[origToNewIndex[controlNameIndexData[row]]] = valueData[row];
      time = SDDS_Realloc(time, sizeof(double) * (count + 1));
      time[count] = timeData[row];
      for (i = 0; i < controlNames; i++) {
        data[i] = SDDS_Realloc(data[i], sizeof(**data) * (count + 1));
        if (exist[i]) {
          data[i][count] = rowData[i];
          if (wild_match(controlName[i], "*Absent*")) {
            /* reset absent value after 60 seconds at 0, 8, 16 hours, wait for the reset absent pv cron job done.*/
            /* if absent value may have not been set at 0.02, 8.02 and 16.02 hour, ignore the value between
               0 - 0.02, 8 - 8.02, 16 - 16.02 */
            makeTimeBreakdown(time[count], NULL, NULL, &hours, NULL, NULL, NULL, NULL);
            /* if (hours<0.02 ||  (hours>8 && hours<8.02) || (hours>16 && hours<16.02)) {
               data[i][count] = 0;
               } */

            /* since floor coodinator would be mark the station as absent in the beginning of a shift, therefore,
               if the value of absent is 1 in first one hour of the shift, it must be for the last shift*/
            if (hours < 1 || (hours >= 8 && hours < 9) || (hours >= 16 && hours < 17)) {
              if (data[i][count] == 1) {
                if (count) {
                  count0 = count - 1;
                  while (1) {
                    if (count0 < 0)
                      break;
                    makeTimeBreakdown(time[count0], NULL, NULL, &hours0, NULL, NULL, NULL, NULL);
                    if (hours < 1) {
                      if (hours0 > 1)
                        break;
                    } else if (hours >= 8 && hours < 9) {
                      if (hours0 < 8)
                        break;
                    } else if (hours >= 16 && hours < 17) {
                      if (hours0 < 16)
                        break;
                    }
                    count0--;
                  }
                  if (count0 >= 0)
                    data[i][count0] = 1;
                }
                data[i][count] = 0;
              }
            }
          }
        } else
          /*only the PSS error may not be logging, to give user more credit, always assume that is
            aps's fault, so set pss_error to 1 if not logging */
          data[i][count] = 1;
      }
      count++;
    }
    SDDS_FreeArray(array);
    free(timeData);
    free(valueData);
    free(controlNameIndexData);
    free(origToNewIndex);
    origToNewIndex = NULL;
    free(rowData);
    rowData = NULL;
  } /*end of read table*/
  if (!SDDS_Terminate(&dataset)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }
  *time0 = time;
  return count;
}

/*get PPS data for one station from all PSS log files. */
long get_station_status_data(char **fileList, long files, PSS_STATION *pss_station, long index, long sector, char st_name,
                             double **PSSTime, char **srModePV, double *starttime) {
  long rows, i, ifile, count, j;
  short **data = NULL, pss_error_logged, *globalOnline, *acisShutter, *feShutter, *pssError, *apsKey, *beamReady;
  double *time;
  char **controlName = NULL;
  long controlNames;

  controlNames = 9;
  if (!(controlName = malloc(sizeof(*controlName) * controlNames)) ||
      !(data = malloc(sizeof(*data) * controlNames)))
    SDDS_Bomb("Memory allocation error!");
  for (i = 0; i < controlNames; i++)
    data[i] = NULL;
  controlName[0] = pss_station->beamReadyName[index];
  controlName[1] = pss_station->beamActiveName[index];
  controlName[2] = pss_station->globalOnlineName[index];
  controlName[3] = pss_station->acisShutterPermitName[index];
  controlName[4] = pss_station->pssErrorName[index];
  controlName[5] = pss_station->apsKeyName[index];
  controlName[6] = pss_station->feShutterPermitName[index];
  controlName[7] = srModePV[0]; /* S:DesiredMode */
  controlName[8] = srModePV[1]; /* S:ActualMode */

  time = NULL;
  count = 0;
  *PSSTime = NULL;

  for (ifile = 0; ifile < files; ifile++) {
    rows = get_logonchange_data(fileList[ifile], controlName, controlNames, sector, st_name, data, &time, &pss_error_logged);

    if (rows) {
      if (!count)
        *starttime = time[0];
      *PSSTime = SDDS_Realloc(*PSSTime, sizeof(**PSSTime) * (count + rows));
      pss_station->beamReady = SDDS_Realloc(pss_station->beamReady, sizeof(short) * (count + rows));
      pss_station->beamActive = SDDS_Realloc(pss_station->beamActive, sizeof(short) * (count + rows));
      pss_station->aps_fault = SDDS_Realloc(pss_station->aps_fault, sizeof(short) * (count + rows));
      pss_station->srDesired = SDDS_Realloc(pss_station->srDesired, sizeof(short) * (count + rows));
      pss_station->srActual = SDDS_Realloc(pss_station->srActual, sizeof(short) * (count + rows));
      memcpy(*PSSTime + count, time, sizeof(*time) * rows);
      free(time);
      memcpy(pss_station->beamReady + count, data[0], sizeof(**data) * rows);
      memcpy(pss_station->beamActive + count, data[1], sizeof(**data) * rows);
      free(data[1]);
      /*aps_fault is derived from beam_ready and acis shutter if pss_error_logged=0, or from
        globalonline, acis shutter, fe shutter, aps key and pss_error if pss_error_logged=1 */
      beamReady = data[0];
      globalOnline = data[2];
      acisShutter = data[3];
      pssError = data[4];
      apsKey = data[5];
      feShutter = data[6];
      if (!pss_error_logged) {
        for (j = 0; j < rows; j++) {
          if (beamReady[j] == 0 || acisShutter[j] == 0)
            pss_station->aps_fault[count + j] = 1;
          else
            pss_station->aps_fault[count + j] = 0;
        }
      } else {
        for (j = 0; j < rows; j++) {
          if (globalOnline[j] == 0 || acisShutter[j] == 0 || pssError[j] != 0 ||
              apsKey[j] == 0 || feShutter[j] == 0)
            pss_station->aps_fault[count + j] = 1;
          else
            pss_station->aps_fault[count + j] = 0;
        }
      }
      free(data[0]);
      free(data[2]);
      free(data[3]);
      free(data[4]);
      free(data[5]);
      free(data[6]);
      memcpy(pss_station->srDesired + count, data[7], sizeof(**data) * rows);
      free(data[7]);
      memcpy(pss_station->srActual + count, data[8], sizeof(**data) * rows);
      free(data[8]);
      count += rows;
    }
  }
  free(controlName);
  free(data);
  return count;
}

long get_floorcoord_data(char **fileList, long files, char *controlName, long sector, char st_name,
                         double **FCTime, short **absent0, double *starttime) {
  long rows, ifile, count, ControlNames;
  double *time;
  short **data = NULL, pss_error_logged;
  char **ControlName;

  time = NULL;
  *absent0 = NULL;
  *FCTime = NULL;
  ControlNames = 1;

  if (!(ControlName = malloc(sizeof(*ControlName) * ControlNames)) ||
      !(data = malloc(sizeof(*data) * ControlNames)))
    SDDS_Bomb("Memory allocation error!");
  ControlName[0] = controlName;
  data[0] = NULL;

  count = 0;

  for (ifile = 0; ifile < files; ifile++) {
    rows = get_logonchange_data(fileList[ifile], ControlName, ControlNames, sector, st_name, data, &time, &pss_error_logged);
    if (rows) {
      if (!count)
        *starttime = time[0];
      *FCTime = SDDS_Realloc(*FCTime, sizeof(**FCTime) * (count + rows));
      memcpy(*FCTime + count, time, sizeof(*time) * rows);
      free(time);
      *absent0 = SDDS_Realloc(*absent0, sizeof(**absent0) * (count + rows));
      memcpy(*absent0 + count, data[0], sizeof(**data) * rows);
      free(data[0]);
      count += rows;
    }
  }
  return count;
}

double *merge_and_sort_time(double *scheduleTime, long scheduleRows,
                            double *PSSTime, long PSSRows,
                            double *FCTime, long FCRows,
                            double startTime, double endTime, long *newPoints) {
  long i, count, count0;
  double *time = NULL;
  /* +2 to insert startTime and endTime */
  count = scheduleRows + PSSRows + FCRows;
  time = malloc(sizeof(*time) * count);
  memcpy(time, scheduleTime, sizeof(*scheduleTime) * scheduleRows);
  memcpy(time + scheduleRows, PSSTime, sizeof(*PSSTime) * PSSRows);
  memcpy(time + scheduleRows + PSSRows, FCTime, sizeof(*FCTime) * FCRows);

  qsort((void *)time, count, sizeof(*time), double_cmpasc);
  count0 = 0;

  for (i = 0; i < count; i++) {
    if (time[i] < startTime)
      continue;
    else if (time[i] > endTime)
      break;
    else {
      time[count0] = time[i];
      count0++;
    }
  }
  *newPoints = count0;
  return time;
}

long get_schedule_information(char *scheduleFile, char *stationFile, char ***stationName, long *stations,
                              double **scheduleTime, short ***stationSchedule) {
  long i, count;
  int32_t columns = 0, timeIndex, rows;
  char **name, **columnName;
  SDDS_DATASET dataset, name_data;
  short *maintenance = NULL;

  name = columnName = NULL;
  rows = 0;

  if (stationFile) {
    if (!SDDS_InitializeInput(&name_data, stationFile)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      exit(1);
    }
    /* schedule file has only one page */
    if (SDDS_ReadPage(&name_data) <= 0) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      exit(1);
    }
    if (!(*stations = SDDS_CountRowsOfInterest(&name_data))) {
      fprintf(stderr, "No data found in the stationFile.\n");
      exit(1);
    }
    if (!(*stationName = (char **)SDDS_GetColumn(&name_data, "Station")))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

    if (!SDDS_Terminate(&name_data))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  }

  if (!SDDS_InitializeInput(&dataset, scheduleFile)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }

  if (!(columnName = SDDS_GetColumnNames(&dataset, &columns))) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }

  if ((timeIndex = SDDS_VerifyColumnExists(&dataset, FIND_NUMERIC_TYPE, "StartTime")) == -1) {
    fprintf(stderr, "Error: Time column is missing\n");
    exit(1);
  }

  /* schedule file has only one page */
  if (SDDS_ReadPage(&dataset) <= 0) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }

  if (!(rows = SDDS_CountRowsOfInterest(&dataset))) {
    fprintf(stderr, "No data found in the scheduleFile.\n");
    exit(1);
  }
  if (!(maintenance = (short *)SDDS_GetColumnInShort(&dataset, "Maintenance")))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

  if (!(*scheduleTime = (double *)SDDS_GetColumnInDoubles(&dataset, "StartTime"))) {
    fprintf(stderr, "Can not get Time value in the schedule file.\n");
    exit(1);
  }

  if (!stationFile) {
    count = 0;
    *stationSchedule = NULL;
    if (*stations) {
      *stationSchedule = malloc(sizeof(**stationSchedule) * (*stations));
      for (i = 0; i < *stations; i++) {
        (*stationSchedule)[i] = NULL;
        if (!((*stationSchedule)[i] = SDDS_GetColumnInShort(&dataset, (*stationName)[i]))) {
          SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
          exit(1);
        }
      }
    } else {
      for (i = 0; i < columns; i++) {
        if (strstr(columnName[i], "PA:")) {
          name = SDDS_Realloc(name, sizeof(*name) * (count + 1));
          SDDS_CopyString(&name[count], columnName[i]);
          *stationSchedule = SDDS_Realloc(*stationSchedule, sizeof(**stationSchedule) * (count + 1));
          (*stationSchedule)[count] = SDDS_GetColumnInShort(&dataset, columnName[i]);
          count++;
        }
      }
      *stations = count;
      *stationName = name;
    }
  } else {
    long j;
    *stationSchedule = malloc(sizeof(**stationSchedule) * (*stations));
    (*stationSchedule)[0] = NULL;
    (*stationSchedule)[0] = malloc(sizeof(***stationSchedule) * rows);
    for (j = 0; j < rows; j++)
      if (maintenance[j])
        (*stationSchedule)[0][j] = 0;
      else
        (*stationSchedule)[0][j] = 1;
    for (i = 1; i < *stations; i++) {
      (*stationSchedule)[i] = NULL;
      (*stationSchedule)[i] = malloc(sizeof(***stationSchedule) * rows);
      memcpy((*stationSchedule)[i], (*stationSchedule)[0], sizeof(short) * rows);
    }
  }
  free(maintenance);

  if (!SDDS_Terminate(&dataset)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }
  SDDS_FreeStringArray(columnName, columns);
  free(columnName);
  return rows;
}

short interp_short(short *f, double *x, long n, double xo, long warnings, long order,
                   long *returnCode, long *next_start_pos) {
  long hi, lo, mid;
  short value;

  lo = 0;
  hi = n - 1;
  if (lo == hi) {
    if (warnings)
      printf("warning: only one point--returning value for that point\n");
    *returnCode = 0;
    *next_start_pos = lo;
    return (f[0]);
  }
  if (x[lo] < x[hi]) { /* indep variable ordered from small to large */
    if (xo < x[lo = 0]) {
      if (warnings)
        printf("warning: %22.15e outside [%22.15e,%22.15e] (interp)\n",
               xo, x[0], x[n - 1]);
      *returnCode = 0;
      *next_start_pos = lo;
      return (f[lo]);
    }
    if (xo > x[hi = n - 1]) {
      if (warnings)
        printf("warning: %22.15e outside [%22.15e,%22.15e] (interp)\n",
               xo, x[0], x[n - 1]);
      *returnCode = 0;
      *next_start_pos = hi;
      return (f[hi]);
    }

    /* do binary search for closest point */
    while ((hi - lo) > 1) {
      mid = (lo + hi) / 2;
      if (xo < x[mid])
        hi = mid;
      else
        lo = mid;
    }
  } else { /* indep variable ordered from large to small */
    if (xo > x[lo = 0]) {
      if (warnings)
        printf("warning: %22.15e outside [%22.15e,%22.15e] (interp)\n",
               xo, x[n - 1], x[0]);
      *returnCode = 0;
      *next_start_pos = lo;
      return (f[lo]);
    }
    if (xo < x[hi = n - 1]) {
      if (warnings)
        printf("warning: %22.15e outside [%22.15e,%22.15e] (interp)\n",
               xo, x[n - 1], x[0]);
      *returnCode = 0;
      *next_start_pos = hi;
      return (f[hi]);
    }

    /* do binary search for closest point */
    while ((hi - lo) > 1) {
      mid = (lo + hi) / 2;
      if (xo > x[mid])
        hi = mid;
      else
        lo = mid;
    }
  }
  /*remember this position so that one can start from her to speed up the interp of
    other points assume the interping points are sorted.*/
  *next_start_pos = lo;
  if (order == -1)
    /* inherit value from previous point*/
    value = f[lo];
  else if (order == -2)
    /* inherit value from next point */
    value = f[hi];
  else
    value = (f[hi] - f[lo]) / (xo - x[lo]);
  return value;
}

short interp_absent(short *absent, double *FCTime, long FCRows,
                    double *scheduleTime, long scheduleRows, double time0) {
  int i, j;
  double startTime = 0, endTime;
  short absent0 = 0;

  /* first find the start time that time0 is inside the schedule time */
  for (i = 0; i < scheduleRows; i++) {
    /*find the first point the scheduleTime is greater than time0 */
    if (scheduleTime[i] == time0) {
      startTime = scheduleTime[i];
      break;
    } else if (scheduleTime[i] > time0) {
      startTime = scheduleTime[i - 1];
      break;
    }
  }
  if (!startTime)
    startTime = scheduleTime[scheduleRows - 1];

  endTime = startTime + 8.0 * 3600;
  absent0 = 0;

  for (j = 0; j < FCRows; j++) {
    if (FCTime[j] < startTime)
      continue;
    if (FCTime[j] > endTime)
      break;
    if (absent[j] == 1) {
      absent0 = 1;
      break;
    }
  }
  return absent0;
}
