/*
 * computes PSS station downtime.
 * can not merge with beamlineDownTime.c,
 * since the schedule data is manual input, and it requires two additional
 * log data, PPS.log and attendance.log; it is more complicated than
 * beamline downtime computation.
 */

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

#define CLO_START 0
#define CLO_END 1
#define CLO_STATION 2
#define CLO_DATA_DIRECTORY 3
#define CLO_SCHEDULE_DATA 4
#define CLO_WITH_DETAIL 5
#define OPTIONS 6

char *option[OPTIONS] = {
  "start",
  "end",
  "stations",
  "dataDirectory",
  "scheduledata",
  "withdetail",
};

static char *USAGE = "stationDownTime [outputfile]  \n\
 -start={[year=<integer>,][month=<integer>,][day=<integer>,][hour=<value>]|time=<value> } \n\
 -end={[year=<integer>,][month=<integer>,][day=<integer>,][hour=<value>]| time=value } \n\
 -stations=<list of stations> -dataDirectory=<pss data directory> [-withdetail] \n\
 [-scheduleData] \n\
outputfile              SDDS output file for writing the data. \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\
stations                give the list of stations separated by comma.\n\
                        for example, PA:01BM:A, PA:01ID:C. The station name should has the \n\
                        same format as the station column name in the schedule file, i.e., with \n\
                        format of PA:02ID:B, PA:02BM:C. \n\
dataDirectory           required, provideds the directory of the data that contains the schedule, \n\
                        PSS, and absent information for each station in one run. These files are \n\
                        created by prepare_pss_logdata. These data have be to created before computing\n\
                        the downtime, fortunately, it is only needed to be created once for each run.\n\
withdetail              if given, the actual downtime will be computed, otherwise, user down time is \n\
                        considered only when the shutter is closed more than 1 hour and the total down \n\
                        is equal to 8 hours within one 8-hour schedule. \n\
scheduleData            if provided, the down time within each schedule is computed. \n\
                        The schedule info is provided by the schedule file schedule.sdds \n\
                        in the data directory.\n\n\
Calculates PSS station 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

typedef struct {
  char *station;
  long rows;
  short *schedule, *beamReady, *beamActive, *aps_fault, *absent, *srBeam;
  short *userDown, *srDown, *pssDown, *down;
  double userDownTime, srDownTime, apsDownTime, scheduleTime, activeBeamTime, pssDownTime, totalDownTime, absentTime;
  double *time;
} STATION_INFO;

void Initialize_Station_Info(STATION_INFO *station_info, char *station);
void Free_Station_Info_Mem(STATION_INFO *station_info);
long get_station_info(char *dataDir, STATION_INFO *station_info, double starTime, double endTime);
long compute_station_down_time(STATION_INFO *station_info, double *startTime, double *endTime, long schedule);

char **get_station_names(char *scheduleFile, char **station, long *stations);
void SetupOutputFile(char *outputfile, SDDS_DATASET *out_data, long schedule);
short interp_short(short *f, double *x, long n, double xo, long warnings, long order,
                   long *returnCode, long *next_start_pos);
void ConvertAbsentData(SDDS_DATASET *out_data);

/* this function compute the down time */
double computeDownTime(double *time, long startIndex, long endIndex, short *down);
void SetupDownTimeOutput(char *outputFile, SDDS_DATASET *out_data);

int main(int argc, char **argv) {
  long i_arg, stations, startGiven, endGiven, i, j, row, computeSchedule, scheduleRows, schedulePages, scheduleRow, missedShifts, userMissedShifts, withdetail, totalShifts;
  SCANNED_ARG *s_arg;
  short startYear, startMonth, startDay, endYear, endMonth, endDay, year, month, day, julianDay, startJDay, endJDay, *scheduled;
  double startHour, endHour, startTime, endTime, hour, start, end, *schedule_start;
  STATION_INFO station_info;
  char *dataDir, *outputFile, **schedule_date, **schedule_str, scheduleFile[1024];
  char **station;
  unsigned long pipeFlags, dummyFlags;
  time_t longtime;
  SDDS_DATASET out_data, schedule_data, down_data;
  char *stamp = NULL, *stamp1 = NULL;
  double userDownTime, srDownTime, total_schedule_time, schedule_down, pssDownTime, totalDownTime, total_availableTime = 0;
  short not_active, is_absent, user_fault, *maintain = NULL;
  double *schedule_time = NULL;

  computeSchedule = 0;
  schedule_start = NULL;
  scheduled = NULL;
  schedule_date = schedule_str = NULL;
  stations = 0;
  start = end = 0;
  startGiven = endGiven = 0;
  pipeFlags = dummyFlags = 0;
  station = NULL;
  dataDir = outputFile = NULL;
  withdetail = 0;

  year = month = day = julianDay = 0;
  startYear = startMonth = startDay = endYear = endMonth = endDay = startJDay = endJDay = 0;
  startHour = endHour = hour = 0;
  startTime = endTime = 0;

  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);

  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, NULL))
          SDDS_Bomb("invalid -start syntax.");
        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, NULL))
          SDDS_Bomb("invalid -end syntax.");
        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_WITH_DETAIL:
        withdetail = 1;
        break;
      case CLO_STATION:
        if (s_arg[i_arg].n_items < 2)
          SDDS_Bomb("invalid -stations syntax");
        stations = s_arg[i_arg].n_items - 1;
        station = s_arg[i_arg].list + 1;
        break;
      case CLO_DATA_DIRECTORY:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("invalid -dataDirectory syntax");
        dataDir = s_arg[i_arg].list[1];
        break;
      case CLO_SCHEDULE_DATA:
        computeSchedule = 1;
        break;
      default:
        fprintf(stderr, "unknown option - %s given.\n", s_arg[i_arg].list[0]);
        break;
      }
    } else {
      if (!outputFile)
        outputFile = s_arg[i_arg].list[0];
      else
        SDDS_Bomb("too many filenames given");
    }
  }

  if (!station) {
    station = malloc(sizeof(*station));
    SDDS_CopyString(&station[0], "*");
    stations = 1;
  }
  if (!computeSchedule) {
    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 (!outputFile)
    SDDS_Bomb("output file has to be provided.");
  if (!dataDir)
    SDDS_Bomb("log data directory has to be provided.");
  sprintf(scheduleFile, "%s/schedule.sdds", dataDir);
  station = get_station_names(scheduleFile, station, &stations);

  SetupOutputFile(outputFile, &out_data, computeSchedule);
  if (!computeSchedule) {
    if (!SDDS_StartPage(&out_data, stations))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  } else {
    scheduleRows = 0;
    if (!SDDS_InitializeInput(&schedule_data, scheduleFile))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    if (SDDS_ReadPage(&schedule_data) <= 0)
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    if (!(scheduleRows = SDDS_CountRowsOfInterest(&schedule_data))) {
      fprintf(stderr, "No data found in the schedule file.");
      exit(1);
    }
    if (!(schedule_date = (char **)SDDS_GetColumn(&schedule_data, "Date")) ||
        !(schedule_time = (double *)SDDS_GetColumnInDoubles(&schedule_data, "StartTime")) ||
        !(schedule_str = (char **)SDDS_GetColumn(&schedule_data, "Schedule")) ||
        !(maintain = (short *)SDDS_GetColumnInShort(&schedule_data, "Maintenance")) ||
        !(schedule_start = (double *)SDDS_GetColumnInDoubles(&schedule_data, "StartTime")))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    for (j = 0; j < scheduleRows; j++)
      if (!maintain[j])
        total_availableTime += 8.0;
    startTime = schedule_time[0];
    endTime = schedule_time[scheduleRows - 1] + 8.0 * 3600;
    free(schedule_time);
  }
  row = 0;
  if (computeSchedule) {
    SetupDownTimeOutput(outputFile, &down_data);
    if (!SDDS_StartPage(&down_data, stations))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  }

  for (i = 0; i < stations; i++) {
    start = startTime;
    end = endTime;
    missedShifts = userMissedShifts = 0;
    /*missedshits only count when the total downtime within one schedule (8hour-schedule) is 8 hours*/
    Initialize_Station_Info(&station_info, station[i]);
    if (!get_station_info(dataDir, &station_info, start, end))
      continue;

    if (!computeSchedule) {
      compute_station_down_time(&station_info, &start, &end, computeSchedule);

      if (!SDDS_SetRowValues(&out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, row,
                             "Station", station[i],
                             "ScheduleTime", station_info.scheduleTime,
                             "BeamActiveTime", station_info.activeBeamTime,
                             "SRDownTime", station_info.srDownTime,
                             "UserDownTime", station_info.userDownTime,
                             "PSSDownTime", station_info.pssDownTime,
                             "APSDownTime", station_info.apsDownTime,
                             "TotalDownTime", station_info.totalDownTime, NULL))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      row++;
    } else {
      if (!(scheduled = SDDS_GetColumnInShort(&schedule_data, station[i])))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      scheduleRow = 0;
      userDownTime = srDownTime = total_schedule_time = schedule_down = pssDownTime = totalDownTime = 0;
      if (!SDDS_StartPage(&out_data, scheduleRows))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

      for (j = 0; j < scheduleRows; j++) {
        not_active = is_absent = user_fault = 0;
        start = schedule_start[j];
        end = schedule_start[j] + 8 * 3600.0;
        if (end <= startTime)
          continue;
        if (start >= endTime)
          break;
        if (end > endTime)
          end = endTime;
        is_absent = user_fault = 0;
        not_active = 1;
        station_info.srDownTime = station_info.scheduleTime = station_info.totalDownTime =
          station_info.userDownTime = station_info.srDownTime = station_info.apsDownTime =
          station_info.pssDownTime = 0;
        compute_station_down_time(&station_info, &start, &end, computeSchedule);
        if (station_info.absentTime >= 7.0)
          is_absent = 1;
        /* give user credit if active time is greater than 1 hour within 8 hour shift*/
        if (station_info.activeBeamTime >= 1.0)
          not_active = 0;
        if (scheduled[j])
          total_schedule_time += 8.0;
        if (station_info.totalDownTime >= 7.99)
          missedShifts++;
        if (station_info.userDownTime >= 8.0) {
          user_fault = 1;
          userMissedShifts++;
        }

        userDownTime += station_info.userDownTime;
        srDownTime += station_info.srDownTime;
        pssDownTime += station_info.pssDownTime;
        totalDownTime += station_info.totalDownTime;

        TimeEpochToBreakdown(&year, NULL, &month, &day, NULL, start);
        if (!SDDS_SetRowValues(&out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, scheduleRow,
                               "Date", schedule_date[j],
                               "Year", year,
                               "Month", month,
                               "Day", day,
                               "ScheduleHour", schedule_str[j],
                               "Scheduled", scheduled[j],
                               "BeamNotActive", not_active,
                               "IsAbsent", is_absent,
                               "UserFault", user_fault,
                               "UserDownTime", station_info.userDownTime,
                               "PSSDownTime", station_info.pssDownTime,
                               "SRDownTime", station_info.srDownTime,
                               "APSDownTime", station_info.apsDownTime,
                               "AllDownTime", station_info.totalDownTime,
                               "ActiveBeamTime", station_info.activeBeamTime,
                               "Maintenance", maintain[j],
                               NULL))
          SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
        scheduleRow++;
      }
      free(scheduled);
      scheduled = NULL;
      totalShifts = total_schedule_time / 8.0;

      SDDS_CopyString(&stamp, makeTimeStamp(startTime));
      SDDS_CopyString(&stamp1, makeTimeStamp(endTime));
      if (!SDDS_SetParameters(&out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                              "TotalAvailableTime", total_availableTime,
                              "TotalUserDownTime", userDownTime,
                              "TotalAPSDownTime", srDownTime + pssDownTime,
                              "TotalPSSDownTime", pssDownTime,
                              "TotalSRDownTime", srDownTime,
                              "TotalDownTime", totalDownTime,
                              "TotalScheduleTime", total_schedule_time,
                              "TotalScheduledShifts", totalShifts,
                              "TotalMissedShifts", missedShifts,
                              "UserMissedShifts", userMissedShifts,
                              "Station", station[i],
                              "StartTime", startTime,
                              "EndTime", endTime,
                              "StartTimeStamp", stamp,
                              "EndTimeStamp", stamp1, NULL))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      free(stamp);
      free(stamp1);
      if (!SDDS_WritePage(&out_data))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    }
    if (computeSchedule) {
      totalShifts = total_schedule_time / 8.0;
      if (!SDDS_SetRowValues(&down_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, i,
                             "Station", station[i],
                             "ScheduledHours", total_schedule_time,
                             "ScheduledShifts", totalShifts,
                             "TotalMissedShifts", missedShifts,
                             "UserMissedShifts", userMissedShifts,
                             NULL))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    }
  }
  if (maintain)
    free(maintain);

  SDDS_CopyString(&stamp, makeTimeStamp(startTime));
  SDDS_CopyString(&stamp1, makeTimeStamp(endTime));
  Free_Station_Info_Mem(&station_info);
  if (!computeSchedule) {
    if (!SDDS_SetParameters(&out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                            "StartTimeStamp", stamp,
                            "EndTimeStamp", stamp1,
                            "StartTime", startTime,
                            "EndTime", endTime, NULL))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    if (!SDDS_WritePage(&out_data) || !SDDS_Terminate(&out_data))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

  } else {
    if (!SDDS_SetParameters(&down_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                            "StartTimeStamp", stamp,
                            "EndTimeStamp", stamp1,
                            "StartTime", startTime,
                            "EndTime", endTime, NULL))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    if (!SDDS_WritePage(&down_data) || !SDDS_Terminate(&down_data))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

    if (!SDDS_Terminate(&out_data) || !SDDS_Terminate(&schedule_data))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    SDDS_FreeStringArray(schedule_date, scheduleRows);
    SDDS_FreeStringArray(schedule_str, scheduleRows);
    free(schedule_date);
    free(schedule_str);
    free(schedule_start);
  }
  free(stamp);
  free(stamp1);
  free_scanargs(&s_arg, argc);
  SDDS_FreeStringArray(station, stations);
  free(station);
  return 0;
}

void SetupOutputFile(char *outputFile, SDDS_DATASET *out_data, long schedule) {
  if (!SDDS_InitializeOutput(out_data, SDDS_BINARY, 0, NULL, NULL, outputFile)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  if (!schedule) {
    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_DefineSimpleColumn(out_data, "Station", NULL, SDDS_STRING) ||
        SDDS_DefineColumn(out_data, "ScheduleTime", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
        SDDS_DefineColumn(out_data, "BeamActiveTime", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
        SDDS_DefineColumn(out_data, "UserDownTime", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
        SDDS_DefineColumn(out_data, "APSDownTime", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
        SDDS_DefineColumn(out_data, "PSSDownTime", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
        SDDS_DefineColumn(out_data, "SRDownTime", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
        SDDS_DefineColumn(out_data, "TotalDownTime", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      exit(1);
    }
  } else {
    if (!SDDS_DefineSimpleParameter(out_data, "Station", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleParameter(out_data, "StartTimeStamp", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleParameter(out_data, "EndTimeStamp", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleParameter(out_data, "StartTime", "seconds", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "EndTime", "seconds", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalAvailableTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalUserDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalSRDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalPSSDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalAPSDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalScheduledShifts", NULL, SDDS_LONG) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalScheduleTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "TotalMissedShifts", NULL, SDDS_LONG) ||
        !SDDS_DefineSimpleParameter(out_data, "UserMissedShifts", NULL, SDDS_LONG) ||
        !SDDS_DefineSimpleColumn(out_data, "Date", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleColumn(out_data, "Year", NULL, SDDS_SHORT) ||
        !SDDS_DefineSimpleColumn(out_data, "Month", NULL, SDDS_SHORT) ||
        !SDDS_DefineSimpleColumn(out_data, "Day", NULL, SDDS_SHORT) ||
        !SDDS_DefineSimpleColumn(out_data, "ScheduleHour", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleColumn(out_data, "Scheduled", NULL, SDDS_SHORT) ||
        !SDDS_DefineSimpleColumn(out_data, "BeamNotActive", NULL, SDDS_SHORT) ||
        !SDDS_DefineSimpleColumn(out_data, "IsAbsent", NULL, SDDS_SHORT) ||
        !SDDS_DefineSimpleColumn(out_data, "UserFault", NULL, SDDS_SHORT) ||
        !SDDS_DefineSimpleColumn(out_data, "UserDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "PSSDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "SRDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "APSDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "AllDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "ActiveBeamTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "Maintenance", NULL, SDDS_SHORT)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_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 SetupDownTimeOutput(char *outputFile, SDDS_DATASET *out_data) {
  char buff[1024];
  sprintf(buff, "%s.downtime", outputFile);

  if (!SDDS_InitializeOutput(out_data, SDDS_BINARY, 1, NULL, NULL, buff)) {
    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_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_DefineSimpleColumn(out_data, "Station", NULL, SDDS_STRING) ||
      SDDS_DefineColumn(out_data, "ScheduledHours", NULL, "hour", NULL, NULL, SDDS_DOUBLE, 0) < 0 ||
      SDDS_DefineColumn(out_data, "ScheduledShifts", NULL, "hour", NULL, NULL, SDDS_LONG, 0) < 0 ||
      SDDS_DefineColumn(out_data, "TotalMissedShifts", NULL, "hour", NULL, NULL, SDDS_LONG, 0) < 0 ||
      SDDS_DefineColumn(out_data, "UserMissedShifts", NULL, "hour", NULL, NULL, SDDS_LONG, 0) < 0) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_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);
  }
}

/* this function compute the down time */
double computeDownTime(double *time, long startIndex, long endIndex, short *down) {
  long i;
  double downtime = 0, prevtime;
  short firstDown = 1, lastdown = 0;

  for (i = startIndex; i <= endIndex; i++) {
    /* if (i==endIndex && down[i-1]==1)
       lastdown = 1; */

    if (down[i] || lastdown) {
      /* record the time at the first down point */
      if (firstDown) {
        prevtime = time[i];
        firstDown = 0;
      } else if (i == endIndex) {
        /* if the last point is down and it is not the first down point, add up the down time*/
        downtime += time[i] - prevtime;
      }
      /* 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 (!firstDown) {
        /* now, need add up the downtime, since it is no longer down*/
        downtime += time[i] - prevtime;
        firstDown = 1;
      }
    }
  }
  return downtime;
}

void Initialize_Station_Info(STATION_INFO *station_info, char *station) {
  station_info->station = station;
  station_info->rows = 0;
  station_info->schedule = station_info->beamReady = station_info->beamActive = station_info->absent = station_info->srBeam = station_info->down =
    station_info->srDown = station_info->userDown = station_info->pssDown = NULL;
  station_info->time = NULL;
  station_info->userDownTime = station_info->srDownTime = station_info->apsDownTime = station_info->scheduleTime = station_info->totalDownTime = station_info->activeBeamTime = station_info->pssDownTime = 0;
}

void Free_Station_Info_Mem(STATION_INFO *station_info) {
  if (station_info->rows) {
    free(station_info->schedule);
    free(station_info->beamReady);
    free(station_info->beamActive);
    free(station_info->aps_fault);
    free(station_info->absent);
    free(station_info->srBeam);
    free(station_info->userDown);
    free(station_info->down);
    free(station_info->pssDown);
    free(station_info->srDown);
    free(station_info->time);
  }
  Initialize_Station_Info(station_info, NULL);
}

long get_station_info(char *dataDir, STATION_INFO *station_info, double startTime, double endTime) {
  char file[1024];
  long rows, i;
  SDDS_DATASET dataset;
  char *stamp, *stamp1;

  if (!dataDir || !(station_info->station))
    return 0;
  sprintf(file, "%s/%s.sdds", dataDir, station_info->station);
  if (!SDDS_InitializeInput(&dataset, file)) {
    SDDS_CopyString(&stamp, makeTimeStamp(startTime));
    SDDS_CopyString(&stamp1, makeTimeStamp(endTime));
    fprintf(stdout, "No log data found for %s from %s to %s.\n", station_info->station,
            stamp, stamp1);
    free(stamp);
    free(stamp1);
    return 0;
  }
  if (SDDS_ReadPage(&dataset) <= 0)
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  if (!(rows = SDDS_CountRowsOfInterest(&dataset)))
    return 0;
  if (!(station_info->time = SDDS_GetColumnInDoubles(&dataset, "Time")) ||
      !(station_info->schedule = SDDS_GetColumnInShort(&dataset, "Scheduled")) ||
      !(station_info->srBeam = SDDS_GetColumnInShort(&dataset, "S:ActualMode")) ||
      !(station_info->beamReady = SDDS_GetColumnInShort(&dataset, "BeamReady")) ||
      !(station_info->beamActive = SDDS_GetColumnInShort(&dataset, "BeamActive")) ||
      !(station_info->aps_fault = SDDS_GetColumnInShort(&dataset, "APS_Fault")) ||
      !(station_info->absent = SDDS_GetColumnInShort(&dataset, "IsAbsent")))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  if (!SDDS_Terminate(&dataset))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  if (!(station_info->down = calloc(sizeof(short), rows)) ||
      !(station_info->userDown = calloc(sizeof(short), rows)) ||
      !(station_info->pssDown = calloc(sizeof(short), rows)) ||
      !(station_info->srDown = calloc(sizeof(short), rows)))
    SDDS_Bomb("Memory allocation failure.");

  for (i = 0; i < rows; i++) {
    /* count as user down time only if it has stored beam in the ring + .... */
    if (station_info->schedule[i] && !(station_info->beamActive[i]) && station_info->absent[i] == 1)
      station_info->down[i] = 1;
    if (station_info->schedule[i] && !(station_info->beamActive[i]) &&
        station_info->absent[i] == 1 && station_info->srBeam[i] != 4)
      station_info->srDown[i] = 1;
    if (station_info->schedule[i] && !(station_info->beamActive[i]) &&
        station_info->absent[i] == 1 && station_info->srBeam[i] == 4 && !(station_info->aps_fault[i]))
      station_info->userDown[i] = 1;
    if (station_info->schedule[i] && !(station_info->beamActive[i]) &&
        station_info->absent[i] == 1 && station_info->srBeam[i] == 4 && station_info->aps_fault[i])
      station_info->pssDown[i] = 1;
  }
  station_info->rows = rows;
  return rows;
}

long compute_station_down_time(STATION_INFO *station_info, double *startTime, double *endTime, long schedule) {
  long startIndex, endIndex, rows, returnCode, i;
  double *time = NULL, start, end;
  rows = station_info->rows;
  time = malloc(sizeof(*time) * rows);

  memcpy(time, station_info->time, sizeof(double) * rows);

  if (*startTime <= time[0]) {
    *startTime = time[0];
    startIndex = 0;
  } else {
    start = interp_short(station_info->schedule, time, rows, *startTime, 0, -1, &returnCode, &startIndex);
    time[startIndex] = *startTime;
  }

  if (*endTime >= time[rows - 1]) {
    endIndex = rows - 1;
    *endTime = time[rows - 1];
  } else {
    end = interp_short(station_info->schedule, time, rows,
                       *endTime, 0, -1, &returnCode, &endIndex);
    if (endIndex == startIndex) {
      endIndex = startIndex + 1;
      if (endIndex > rows)
        endIndex = rows - 1;
    }
    time[endIndex] = *endTime;
  }

  station_info->srDownTime = computeDownTime(time, startIndex, endIndex, station_info->srDown) / 3600.0;

  station_info->activeBeamTime = computeDownTime(time, startIndex, endIndex, station_info->beamActive) / 3600.0;

  station_info->absentTime = computeDownTime(time, startIndex, endIndex, station_info->absent) / 3600.0;

  station_info->scheduleTime = computeDownTime(time, startIndex, endIndex, station_info->schedule) / 3600.0;

  station_info->totalDownTime = computeDownTime(time, startIndex, endIndex, station_info->down) / 3600.0;

  station_info->userDownTime = computeDownTime(time, startIndex, endIndex, station_info->userDown) / 3600.0;

  station_info->pssDownTime = computeDownTime(time, startIndex, endIndex,
                                              station_info->pssDown) /
    3600.0;
  station_info->apsDownTime = station_info->srDownTime + station_info->pssDownTime;

  free(time);
  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;
}

char **get_station_names(char *scheduleData, char **station, long *stations) {
  char **name, **columnName, **stationName, *ptr;
  long names, i, j, count;
  int32_t columns;

  SDDS_DATASET dataset;

  name = columnName = stationName = NULL;
  names = columns = 0;

  if (!SDDS_InitializeInput(&dataset, scheduleData)) {
    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 (!SDDS_Terminate(&dataset)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }
  names = 0;
  for (i = 0; i < columns; i++) {
    if (strstr(columnName[i], "PA:")) {
      name = SDDS_Realloc(name, sizeof(*name) * (names + 1));
      SDDS_CopyString(&name[names], columnName[i]);
      names++;
    }
  }
  count = 0;
  if (!(*stations)) {
    *stations = names;
    SDDS_FreeStringArray(columnName, columns);
    return name;
  } else {
    for (i = 0; i < *stations; i++) {
      if (has_wildcards(station[i])) {
        ptr = expand_ranges(station[i]);
        for (j = 0; j < names; j++) {
          if (wild_match(name[j], ptr)) {
            stationName = SDDS_Realloc(stationName, sizeof(*stationName) * (count + 1));
            SDDS_CopyString(&stationName[count], name[j]);
            count++;
          }
        }
        free(ptr);
      } else {
        if (match_string(station[i], name, names, EXACT_MATCH) < 0) {
          fprintf(stdout, "Warning, %s did not found in schedule file, will be ignored.\n", station[i]);
        } else {
          stationName = SDDS_Realloc(stationName, sizeof(*stationName) * (count + 1));
          SDDS_CopyString(&stationName[count], station[i]);
          count++;
        }
      }
    }

    *stations = count;
    SDDS_FreeStringArray(columnName, columns);
    free(columnName);
    SDDS_FreeStringArray(name, names);
    free(name);
    return stationName;
  }
}
