/*
 * 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_SR_FILE_NAME 5
#define OPTIONS 6

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

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>|file=<file_name>] -dataDirectory=<pss data directory> [-withdetail] \n\
 [-scheduleData] [-srFilename]  \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\
station                 give the list of stations separated by comma or the file name that contains the stations.\n\
                        through station=file=<file_name>. \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\
scheduleData            provide the schedule info file. \n\
                        if provided, the down time within each schedule is computed. \n\
srFilename              provide the filename for storage ring down time.\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
#define TODAY_GIVEN 0x0040U

typedef struct {
  char *station;
  long rows;
  short *schedule, *beamReady, *beamActive, *aps_fault, *absent, *srBeam, *srDesired;
  short *userDown, *srDown, *pssDown, *down, *sr_down;
  double userDownTime, srDownTime, apsDownTime, scheduleTime, activeBeamTime, pssDownTime, totalDownTime, absentTime, sr_downtime;
  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 compute_sr);

char **get_station_names(char *scheduleFile, char *stationFile, char **station, long *stations);
void SetupOutputFile(char *outputfile, SDDS_DATASET *out_data);
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);
void Setup_SR_DownTimeFile(char *srFilename, SDDS_DATASET *sr_data, long scheduleRows);

int main(int argc, char **argv) {
  long i_arg, stations, startGiven, endGiven, i, j, row, scheduleRows, schedulePages, scheduleRow, missedShifts, userMissedShifts, totalShifts;
  SCANNED_ARG *s_arg;
  short startYear, startMonth, startDay, endYear, endMonth, endDay, year, month, day, julianDay, startJDay, endJDay, has_schedule, *scheduled;
  double startHour, endHour, startTime, endTime, hour, start, end, *schedule_start;
  STATION_INFO station_info;
  char *dataDir, *outputFile, **schedule_date, **schedule_str, *scheduleFile = NULL;
  char **station;
  unsigned long pipeFlags, dummyFlags;
  time_t longtime;
  SDDS_DATASET out_data, schedule_data, down_data, sr_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, compute_sr;
  char *srFilename = "srStatus.sdds", *stationFile = NULL;
  double sr_down_time = 0, total_sr_down_time = 0, total_delivered_time;

  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;

  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,
                          "today", -1, NULL, 0, TODAY_GIVEN,
                          NULL))
          SDDS_Bomb("invalid -end syntax.");
        if (dummyFlags & TODAY_GIVEN) {
          endTime = getTimeInSecs();
        } 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_STATION:
        if (s_arg[i_arg].n_items < 2)
          SDDS_Bomb("invalid -stations syntax");
        if (wild_match("*file=*", s_arg[i_arg].list[1])) {
          s_arg[i_arg].n_items--;
          if (!scanItemList(&dummyFlags, s_arg[i_arg].list + 1, &s_arg[i_arg].n_items, 0,
                            "file", SDDS_STRING, &stationFile, 1, 0, NULL))
            SDDS_Bomb("Invalide -station syntax");
          s_arg[i_arg].n_items++;
        } else {
          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:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("Invalid -scheduleData syntax, the schedule file has to be provided!");
        scheduleFile = s_arg[i_arg].list[1];
        break;
      case CLO_SR_FILE_NAME:
        if (s_arg[i_arg].n_items != 2)
          SDDS_Bomb("invalid -srFilename syntax");
        srFilename = s_arg[i_arg].list[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 (!scheduleFile)
    SDDS_CopyString(&scheduleFile, "schedule.sdds");

  if (!outputFile)
    SDDS_Bomb("output file has to be provided.");
  if (!dataDir)
    SDDS_Bomb("log data directory has to be provided.");

  station = get_station_names(scheduleFile, stationFile, station, &stations);

  SetupOutputFile(outputFile, &out_data);
  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_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);
    exit(1);
  }
  for (j = 0; j < scheduleRows; j++)
    if (!maintain[j]) {
      if (j < scheduleRows - 1)
        total_availableTime += (schedule_start[j + 1] - schedule_start[j]) / 3600.0;
      else
        total_availableTime += 8.0;
    }
  if (srFilename)
    Setup_SR_DownTimeFile(srFilename, &sr_data, scheduleRows);
  compute_sr = 1;

  row = 0;
  SDDS_CopyString(&stamp, makeTimeStamp(startTime));
  SDDS_CopyString(&stamp1, makeTimeStamp(endTime));
  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 (!(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);
    sr_down_time = total_sr_down_time = 0;
    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 (j < scheduleRows - 1)
        end = schedule_start[j + 1];
      else
        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 = station_info.sr_downtime = 0;
      if (!(maintain[j]) && scheduled[j])
        compute_station_down_time(&station_info, &start, &end, 0);
      if (compute_sr) {
        if (maintain[j])
          sr_down_time = 0;
        else {
          compute_station_down_time(&station_info, &start, &end, 1);
          sr_down_time = station_info.sr_downtime;
        }
        total_sr_down_time += sr_down_time;
      }
      if (station_info.absentTime >= 8.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;
      has_schedule = scheduled[j];
      if (scheduled[j] && !(maintain[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", has_schedule,
                             "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);
      if (compute_sr) {
        if (!SDDS_SetRowValues(&sr_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, scheduleRow,
                               "Date", schedule_date[j],
                               "Year", year,
                               "Month", month,
                               "Day", day,
                               "ScheduleHour", schedule_str[j],
                               "Maintenance", maintain[j],
                               "SRDownTime", sr_down_time,
                               "SRDownTimeInMinutes", sr_down_time * 60,
                               NULL))
          SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      }
      scheduleRow++;
    }
    if (scheduled)
      free(scheduled);
    scheduled = NULL;
    totalShifts = total_schedule_time / 8.0;

    if (compute_sr)
      total_delivered_time = total_availableTime - total_sr_down_time;

    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,
                            "TotalDeliveredTime", total_delivered_time,
                            "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);
    if (compute_sr) {
      if (!SDDS_SetParameters(&sr_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                              "SRModePV", "S:ActualMode",
                              "TotalSRDownTime", total_sr_down_time,
                              "TotalScheduleTime", total_availableTime,
                              "StartTime", startTime,
                              "EndTime", endTime,
                              "StartTimeStamp", stamp,
                              "EndTimeStamp", stamp1, NULL))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      if (!SDDS_WritePage(&sr_data) || !SDDS_Terminate(&sr_data))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      compute_sr = 0;
    }
    if (!SDDS_WritePage(&out_data))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    Free_Station_Info_Mem(&station_info);
  }
  if (!SDDS_Terminate(&out_data))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  free(stamp);
  free(stamp1);
  if (!SDDS_Terminate(&schedule_data))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  if (maintain)
    free(maintain);
  free(schedule_date);
  free(schedule_str);
  free(schedule_start);
  free_scanargs(&s_arg, argc);
  SDDS_FreeStringArray(station, stations);
  free(station);
  return 0;
}

void SetupOutputFile(char *outputFile, SDDS_DATASET *out_data) {
  if (!SDDS_InitializeOutput(out_data, SDDS_BINARY, 0, NULL, NULL, outputFile)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  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, "TotalDeliveredTime", "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->absentTime = 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);
    free(station_info->sr_down);
    free(station_info->srDesired);
  }
  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, i1, i2, j;
  SDDS_DATASET dataset;
  char *stamp, *stamp1;
  double prevTime, upTime;

  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_GetColumnInDoubles(&dataset, "Scheduled")) ||
      !(station_info->srBeam = SDDS_GetColumnInShort(&dataset, "S:ActualMode")) ||
      !(station_info->srDesired = SDDS_GetColumnInShort(&dataset, "S:DesiredMode")) ||
      !(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->sr_down = calloc(sizeof(short), rows)) ||
      !(station_info->srDown = calloc(sizeof(short), rows)))
    SDDS_Bomb("Memory allocation failure.");
  /* 2007 FY got 4753.1 delivered hours, while Randy got 4750.5 hours, because Randy takes the up-time less than 1 hour as downtime,
     this was not done in this code. Therefore modify the code to make it consistent with Randy's calculation */
  for (i = 0; i < rows; i++) {
    if (station_info->srBeam[i] != 4)
      station_info->sr_down[i] = 1;
    else
      station_info->sr_down[i] = 0;
  }
  /* check if there is up time less than 1 one hour between two SR downs */

  for (i = 0; i < rows; i++) {
    prevTime = 0;
    if (station_info->sr_down[i]) {
      for (j = i + 1; j < rows; j++) {
        if (station_info->sr_down[j]) {
          if (prevTime) {
            upTime = (station_info->time[j] - prevTime) / 3600.0;
            if (upTime < 1.0) {
              for (i2 = i1; i2 < j; i2++)
                station_info->sr_down[i2] = 1;
            }
            i = j - 1;
            break;
          }
        } else {
          if (!prevTime) {
            i1 = j;
            prevTime = station_info->time[j];
          }
        }
      }
    }
    if (j >= rows - 1)
      break;
  }
  for (i = 0; i < rows; i++) {
    /* count as user down time only if it has stored beam in the ring + .... */
    if (!(station_info->beamActive[i]) && station_info->absent[i] == 1)
      station_info->down[i] = 1;
    if (station_info->sr_down[i])
      station_info->srDown[i] = 1;
    if (!(station_info->beamActive[i]) &&
        station_info->absent[i] == 1 && !(station_info->sr_down[i]) && !(station_info->aps_fault[i]))
      station_info->userDown[i] = 1;
    if (!(station_info->beamActive[i]) && !(station_info->sr_down[i]) && 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 compute_sr) {
  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;
  }
  if (!compute_sr) {
    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;
  } else {
    station_info->sr_downtime = computeDownTime(time, startIndex, endIndex, station_info->sr_down) / 3600.0;
  }
  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 *stationFile, 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 (stationFile) {
    if (!SDDS_InitializeInput(&dataset, stationFile)) {
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      exit(1);
    }
    if (SDDS_ReadPage(&dataset) < 0)
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    *stations = SDDS_CountRowsOfInterest(&dataset);
    if (!(stationName = (char **)SDDS_GetColumn(&dataset, "Station")))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    if (!SDDS_Terminate(&dataset))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    return stationName;
  }

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

void Setup_SR_DownTimeFile(char *srFilename, SDDS_DATASET *sr_data, long scheduleRows) {

  if (!SDDS_InitializeOutput(sr_data, SDDS_BINARY, 0, NULL, NULL, srFilename)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  if (!SDDS_DefineSimpleParameter(sr_data, "SRModePV", NULL, SDDS_STRING) ||
      !SDDS_DefineSimpleParameter(sr_data, "StartTimeStamp", NULL, SDDS_STRING) ||
      !SDDS_DefineSimpleParameter(sr_data, "EndTimeStamp", NULL, SDDS_STRING) ||
      !SDDS_DefineSimpleParameter(sr_data, "StartTime", "seconds", SDDS_DOUBLE) ||
      !SDDS_DefineSimpleParameter(sr_data, "EndTime", "seconds", SDDS_DOUBLE) ||
      !SDDS_DefineSimpleParameter(sr_data, "TotalSRDownTime", "hour", SDDS_DOUBLE) ||
      !SDDS_DefineSimpleParameter(sr_data, "TotalScheduleTime", "hour", SDDS_DOUBLE) ||
      !SDDS_DefineSimpleColumn(sr_data, "Date", NULL, SDDS_STRING) ||
      !SDDS_DefineSimpleColumn(sr_data, "Year", NULL, SDDS_SHORT) ||
      !SDDS_DefineSimpleColumn(sr_data, "Month", NULL, SDDS_SHORT) ||
      !SDDS_DefineSimpleColumn(sr_data, "Day", NULL, SDDS_SHORT) ||
      !SDDS_DefineSimpleColumn(sr_data, "ScheduleHour", NULL, SDDS_STRING) ||
      !SDDS_DefineSimpleColumn(sr_data, "Maintenance", NULL, SDDS_SHORT) ||
      !SDDS_DefineSimpleColumn(sr_data, "SRDownTime", NULL, SDDS_DOUBLE) ||
      !SDDS_DefineSimpleColumn(sr_data, "SRDownTimeInMinutes", NULL, SDDS_DOUBLE)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    exit(1);
  }
  if (!SDDS_WriteLayout(sr_data)) {
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors);
    exit(1);
  }
  if (!SDDS_StartPage(sr_data, scheduleRows))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
}
