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

#define CLO_START 0
#define CLO_END 1
#define CLO_DATA_DIRECTORY 2
#define CLO_SCHEDULE_DATA 3
#define CLO_SR_FILE_NAME 4
#define CLO_OLD_OUTPUT 5
#define CLO_STATION 6
#define CLO_DEBUG 7
#define CLO_WITHOUT_SCHEDULE 8
#define OPTIONS 9

char *option[OPTIONS] = {
  "start",
  "end",
  "dataDirectory",
  "scheduledata",
  "srFilename",
  "oldOutput",
  "station",
  "debug",
  "withoutSchedule",
};

long debug = 0;

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\
 -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\
                        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\
oldOutput               provide the filename for output downtime in old format i.e. by schedule.\n\
withoutSchedule         compute beamline station downtime without beamline schedule. but a dummy schedule file is \n\
                        still needed to get the beamline and station names information.\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 *beamline_name, rootname[256], *type, *runName;
  char **station_name, **DOEBeamline, **stationName;
  long stations, sector, *rows, pss_rows;
  int32_t **activityID;
  char ***activityType;
  double **start_time, **end_time, *pss_time;
  short *sr_down, **pss_down, **user_down, *maintenance, **beam_active;
} BEAMLINE_INFO;

void get_schedule_data(char *scheduleFile, long *beamlines, BEAMLINE_INFO ***beamline_info);
void free_beamline_data(BEAMLINE_INFO **beamline, long beamlines);
void get_beamline_data(BEAMLINE_INFO *beamline, char **dataFileList, long dataFiles);
void SetupOutputFile(char *outputFile, SDDS_DATASET *out_data, long withoutSchedule);
void compute_station_downtime(BEAMLINE_INFO *beamline, long station,
                              double startTime, double endTime, SDDS_DATASET *out_data);
void compute_station_downtime_without_schedule(BEAMLINE_INFO *beamline,
                                               double startTime, double endTime, SDDS_DATASET *out_data);
double computeDownTime(double *time, long startIndex, long endIndex, short *down, double start, double end, long rows);
void process_absent_data(short *absent, double *time, long rows);

int main(int argc, char **argv) {
  SCANNED_ARG *s_arg;
  SDDS_DATASET out_data;
  long i_arg, i, j, beamlines = 0, startGiven = 0, endGiven = 0, dataFiles = 0, extensions = 0, withoutSchedule = 0;
  short startYear, startMonth, startDay, endYear, endMonth, endDay, year, month = 0, day = 0, julianDay = 0, startJDay = 0, endJDay = 0;
  char *outputFile = NULL, *scheduleFile = NULL, *dataDir = NULL, *srFilename = NULL;
  BEAMLINE_INFO **beamline = NULL;
  double startHour = 0, endHour = 24, startTime, endTime, hour = 0;
  unsigned long dummyFlags = 0;
  time_t longtime;
  char **dataFileList = NULL, **extensionList = NULL, *filter = NULL, *oldOutput = NULL;

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

  /* get today's date -- year, month and day */
  time(&longtime);
  TimeEpochToBreakdown(&year, &julianDay, &month, &day, &hour, longtime);

  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_DEBUG:
        debug = 1;
        break;
      case CLO_START:
        startGiven = 1;
        s_arg[i_arg].n_items--;
        dummyFlags = 0;
        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;
        dummyFlags = 0;
        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_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;
      case CLO_WITHOUT_SCHEDULE:
        withoutSchedule = 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 (!scheduleFile)
    SDDS_Bomb("Schedule file not provided.");

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

  get_schedule_data(scheduleFile, &beamlines, &beamline);

  extensions = 1;
  extensionList = (char **)malloc(sizeof(*extensionList) * extensions);
  SDDS_CopyString(&extensionList[0], ".sdds");
  cp_str(&filter, "-??\?-??\??");
  if (!startGiven)
    SDDS_Bomb("start time not provided.");
  if (!endGiven)
    SDDS_Bomb("end time not provided.");
  SetupOutputFile(outputFile, &out_data, withoutSchedule);

  for (i = 0; i < beamlines; i++) {
    dataFileList = find_files_between_dates(dataDir, beamline[i]->rootname, NULL, startYear, 0, 0, startJDay,
                                            endYear, 0, 0, endJDay, filter, extensionList, extensions, 0, &dataFiles, 1);
    get_beamline_data(beamline[i], dataFileList, dataFiles);
    if (!dataFiles) {
      fprintf(stdout, "no files found for %s\n", beamline[i]->rootname);
      continue;
    }
    if (!withoutSchedule) {
      for (j = 0; j < beamline[i]->stations; j++) {
        compute_station_downtime(beamline[i], j, startTime, endTime, &out_data);
      }
    } else {
      compute_station_downtime_without_schedule(beamline[i], startTime, endTime, &out_data);
    }

    for (j = 0; j < dataFiles; j++)
      free(dataFileList[j]);
    free(dataFileList);
  }
  if (!SDDS_Terminate(&out_data))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

  free_beamline_data(beamline, beamlines);
  free_scanargs(&s_arg, argc);
  return 0;
}

void get_schedule_data(char *scheduleFile, long *beamlines, BEAMLINE_INFO ***beamline_info) {
  SDDS_DATASET schedule_data;
  char **beamline_names = NULL, *tmp_name = NULL, *st_name = NULL, *stName = NULL, *runName, *DOEBeamline;
  long beamlines0 = 0, rows = 0, count = 0, index = 0, stations, sector;
  int32_t *activityID = NULL;
  char **activityType = NULL;
  double *start = NULL, *end = NULL;
  BEAMLINE_INFO *beam = NULL;

  if (!SDDS_InitializeInput(&schedule_data, scheduleFile))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

  *beamline_info = calloc(sizeof(**beamline_info), 80);
  beamline_names = malloc(sizeof(*beamline_names) * 80);
  while (SDDS_ReadPage(&schedule_data) > 0) {
    if (!(rows = SDDS_CountRowsOfInterest(&schedule_data)))
      continue;
    count = 0;
    if (!(SDDS_GetParameter(&schedule_data, "Beamline", &tmp_name)) ||
        !(SDDS_GetParameter(&schedule_data, "Station", &st_name)) ||
        !(SDDS_GetParameter(&schedule_data, "StationName", &stName)) ||
        !(SDDS_GetParameter(&schedule_data, "RunName", &runName)) ||
        !(SDDS_GetParameter(&schedule_data, "DOEBeamline", &DOEBeamline)) ||
        !(activityID = (int32_t *)SDDS_GetColumnInLong(&schedule_data, "ActivityId")) ||
        !(activityType = SDDS_GetColumn(&schedule_data, "ActivityType")) ||
        !(start = (double *)SDDS_GetColumn(&schedule_data, "StartTime")) ||
        !(end = (double *)SDDS_GetColumn(&schedule_data, "EndTime"))) {
      fprintf(stderr, "error in reading schedule data.\n");
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    }
    if (beamlines0) {
      index = match_string(tmp_name, beamline_names, beamlines0, EXACT_MATCH);
      if (index < 0) {
        count = 1;
        index = beamlines0;
      }
    } else {
      index = 0;
      count = 1;
    }
    if (count) {
      /*new beamlines found, allocate memory for new beamlines */
      beam = calloc(sizeof(*beam), 1);
      beam->stations = 0;
      beam->beamline_name = tmp_name;
      beam->runName = runName;
      if (sscanf(tmp_name, "%ld", &sector) != 1) {
        fprintf(stderr, "Invalid beamline - %s.\n", tmp_name);
        exit(1);
      }
      if (strstr(tmp_name, "ID"))
        SDDS_CopyString(&(beam->type), "ID");
      else if (strstr(tmp_name, "BM"))
        SDDS_CopyString(&(beam->type), "BM");
      else
        SDDS_Bomb("Invalid beamline name provided in schedule file.");
      beam->sector = sector;
      sprintf(beam->rootname, "S%02ld%s-", sector, beam->type);
      beamline_names[beamlines0] = tmp_name;
      (*beamline_info)[beamlines0] = beam;
      beamlines0++;
    } else {
      free(tmp_name);
      free(runName);
    }
    beam = (*beamline_info)[index];
    stations = beam->stations;
    beam->station_name = SDDS_Realloc(beam->station_name, sizeof(char *) * (stations + 1));
    beam->stationName = SDDS_Realloc(beam->stationName, sizeof(char *) * (stations + 1));
    beam->DOEBeamline = SDDS_Realloc(beam->DOEBeamline, sizeof(char *) * (stations + 1));
    beam->rows = SDDS_Realloc(beam->rows, sizeof(*beam->rows) * (stations + 1));
    beam->start_time = SDDS_Realloc(beam->start_time, sizeof(*beam->start_time) * (stations + 1));
    beam->end_time = SDDS_Realloc(beam->end_time, sizeof(*beam->end_time) * (stations + 1));
    beam->activityID = SDDS_Realloc(beam->activityID, sizeof(*beam->activityID) * (stations + 1));
    beam->activityType = SDDS_Realloc(beam->activityType, sizeof(*beam->activityType) * (stations + 1));
    beam->station_name[stations] = st_name;
    beam->stationName[stations] = stName;
    beam->DOEBeamline[stations] = DOEBeamline;
    beam->rows[stations] = rows;
    beam->start_time[stations] = start;
    beam->end_time[stations] = end;
    beam->activityID[stations] = activityID;
    beam->activityType[stations] = activityType;
    beam->stations++;
  }
  *beamlines = beamlines0;
  free(beamline_names);
  if (!SDDS_Terminate(&schedule_data))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
}

void free_beamline_data(BEAMLINE_INFO **beamline, long beamlines) {
  long i, j;
  for (i = 0; i < beamlines; i++) {
    for (j = 0; j < beamline[i]->stations; j++) {
      if (beamline[i]->station_name[j])
        free(beamline[i]->station_name[j]);
      if (beamline[i]->stationName[j])
        free(beamline[i]->stationName[j]);
      if (beamline[i]->start_time[j])
        free(beamline[i]->start_time[j]);
      if (beamline[i]->end_time[j])
        free(beamline[i]->end_time[j]);
      if (beamline[i]->pss_down[j])
        free(beamline[i]->pss_down[j]);
      if (beamline[i]->user_down[j])
        free(beamline[i]->user_down[j]);
      if (beamline[i]->activityID[j])
        free(beamline[i]->activityID[j]);
      if (beamline[i]->activityType[j])
        free(beamline[i]->activityType[j]);
    }
    free(beamline[i]->beamline_name);
    free(beamline[i]->type);
    free(beamline[i]->station_name);
    free(beamline[i]->stationName);
    free(beamline[i]->runName);
    free(beamline[i]->DOEBeamline);
    free(beamline[i]->rows);
    free(beamline[i]->start_time);
    free(beamline[i]->end_time);
    free(beamline[i]->pss_down);
    free(beamline[i]->user_down);
    free(beamline[i]->maintenance);
    free(beamline[i]->sr_down);
    free(beamline[i]->activityID);
    free(beamline[i]->activityType);
    free(beamline[i]->pss_time);
    free(beamline[i]);
  }
  free(beamline);
}

void get_beamline_data(BEAMLINE_INFO *beamline, char **dataFileList, long dataFiles) {
  long rows = 0, i, j, i2, i1;
  char col[256];
  SDDS_DATASET pss_data;
  double *time = NULL, prevTime, upTime;
  short *short_data, *sr = NULL, *maintenance = NULL;
  short **is_absent, **aps_fault, **beam_active, **beam_ready, *sr_data = NULL;

  beamline->pss_rows = 0;
  is_absent = malloc(sizeof(*is_absent) * beamline->stations);
  aps_fault = malloc(sizeof(*aps_fault) * beamline->stations);
  beam_active = malloc(sizeof(*beam_active) * beamline->stations);
  beam_ready = malloc(sizeof(*beam_ready) * beamline->stations);
  for (j = 0; j < beamline->stations; j++)
    is_absent[j] = aps_fault[j] = beam_active[j] = beam_ready[j] = NULL;

  for (i = 0; i < dataFiles; i++) {
    if (!SDDS_InitializeInput(&pss_data, dataFileList[i]))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    if (SDDS_ReadPage(&pss_data) < 0)
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    if (!(rows = SDDS_CountRowsOfInterest(&pss_data))) {
      if (!SDDS_Terminate(&pss_data))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      continue;
    }

    if (!(time = SDDS_GetColumn(&pss_data, "Time")) ||
        !(maintenance = SDDS_GetColumnInShort(&pss_data, "Maintenance")) ||
        !(sr = SDDS_GetColumnInShort(&pss_data, "S:ActualMode"))) {
      fprintf(stderr, "error in reading pss data from file %s\n", dataFileList[i]);
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    }
    beamline->pss_time = SDDS_Realloc(beamline->pss_time, sizeof(*beamline->pss_time) * (beamline->pss_rows + rows));
    beamline->maintenance = SDDS_Realloc(beamline->maintenance, sizeof(*beamline->maintenance) * (beamline->pss_rows + rows));
    sr_data = SDDS_Realloc(sr_data, sizeof(*sr_data) * (beamline->pss_rows + rows));
    memcpy(beamline->pss_time + beamline->pss_rows, time, sizeof(*time) * rows);
    memcpy(beamline->maintenance + beamline->pss_rows, maintenance, sizeof(*maintenance) * rows);
    memcpy(sr_data + beamline->pss_rows, sr, sizeof(*sr) * rows);

    free(maintenance);
    free(sr);
    maintenance = sr = NULL;
    for (j = 0; j < beamline->stations; j++) {
      beam_active[j] = SDDS_Realloc(beam_active[j], sizeof(**beam_active) * (beamline->pss_rows + rows));
      beam_ready[j] = SDDS_Realloc(beam_ready[j], sizeof(**beam_ready) * (beamline->pss_rows + rows));
      is_absent[j] = SDDS_Realloc(is_absent[j], sizeof(**is_absent) * (beamline->pss_rows + rows));
      aps_fault[j] = SDDS_Realloc(aps_fault[j], sizeof(**aps_fault) * (beamline->pss_rows + rows));

      /*8ID I station named by F */
      if (strcmp(beamline->beamline_name, "8ID") == 0 && strcmp(beamline->station_name[j], "I") == 0)
        sprintf(col, "F_BEAM_ACTIVE");
      else
        sprintf(col, "%s_BEAM_ACTIVE", beamline->station_name[j]);
      if (!(short_data = SDDS_GetColumnInShort(&pss_data, col))) {
        fprintf(stderr, "error reading %s data1. station %s, beamline %s\n", col, beamline->station_name[j], beamline->beamline_name);
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      }

      memcpy(beam_active[j] + beamline->pss_rows, short_data, sizeof(*short_data) * rows);
      free(short_data);
      short_data = NULL;

      if (strcmp(beamline->beamline_name, "8ID") == 0 && strcmp(beamline->station_name[j], "I") == 0)
        sprintf(col, "F_BEAM_READY");
      else
        sprintf(col, "%s_BEAM_READY", beamline->station_name[j]);
      if (!(short_data = SDDS_GetColumnInShort(&pss_data, col))) {
        fprintf(stderr, "Error in reading %s data2.\n", col);
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      }
      memcpy(beam_ready[j] + beamline->pss_rows, short_data, sizeof(*short_data) * rows);
      free(short_data);
      short_data = NULL;

      /*8ID I attendance PV uses I not F */
      sprintf(col, "%s_Is_Absent", beamline->station_name[j]);
      if (strcmp(beamline->beamline_name, "21ID") == 0 && (strcmp(col, "F_Is_Absent") == 0 || strcmp(col, "G_Is_Absent") == 0)) {
        /*21ID F absent pv not exist */
        short_data = calloc(sizeof(*short_data), rows);
      } else {
        if (!(short_data = SDDS_GetColumnInShort(&pss_data, col))) {
          fprintf(stderr, "error in reading %s data3\n", col);
          SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
        }
      }
      process_absent_data(short_data, time, rows);
      memcpy(is_absent[j] + beamline->pss_rows, short_data, sizeof(*short_data) * rows);
      free(short_data);
      short_data = NULL;

      if (strcmp(beamline->beamline_name, "8ID") == 0 && strcmp(beamline->station_name[j], "I") == 0)
        sprintf(col, "F_APS_Fault");
      else
        sprintf(col, "%s_APS_Fault", beamline->station_name[j]);
      if (!(short_data = SDDS_GetColumnInShort(&pss_data, col))) {
        fprintf(stderr, "Error inr reading %s data4.\n", col);
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      }

      memcpy(aps_fault[j] + beamline->pss_rows, short_data, sizeof(*short_data) * rows);
      free(short_data);
      short_data = NULL;
    }
    beamline->pss_rows += rows;
    if (!SDDS_Terminate(&pss_data))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
    free(time);
    time = NULL;
  }
  /* check if there is up time less than 1 one hour between two SR downs */

  for (i = 0; i < beamline->pss_rows; i++)
    sr_data[i] = sr_data[i] ? 0 : 1;
  for (i = 0; i < beamline->pss_rows; i++) {
    prevTime = 0;
    if (sr_data[i]) {
      for (j = i + 1; j < beamline->pss_rows; j++) {
        if (sr_data[j]) {
          if (prevTime) {
            upTime = (beamline->pss_time[j] - prevTime) / 3600.0;
            if (upTime < 1.0) {
              for (i2 = i1; i2 < j; i2++)
                sr_data[i2] = 1;
            }
            i = j - 1;
            break;
          }
        } else {
          if (!prevTime) {
            i1 = j;
            prevTime = beamline->pss_time[j];
          }
        }
      }
    }
    if (j >= beamline->pss_rows - 1)
      break;
  }
  beamline->sr_down = sr_data;
  beamline->pss_down = malloc(sizeof(*beamline->pss_down) * beamline->stations);
  beamline->user_down = malloc(sizeof(*beamline->user_down) * beamline->stations);
  for (j = 0; j < beamline->stations; j++) {
    beamline->pss_down[j] = beamline->user_down[j] = NULL;
    beamline->pss_down[j] = (short *)calloc(sizeof(short), beamline->pss_rows);
    beamline->user_down[j] = (short *)calloc(sizeof(short), beamline->pss_rows);
  }

  for (j = 0; j < beamline->stations; j++) {
    /* count as user down time only if it has stored beam in the ring + .... */
    for (i = 0; i < beamline->pss_rows; i++) {
      if (beamline->maintenance[i]) {
        beamline->sr_down[i] = 0;
        beam_active[j][i] = 0;
        continue;
      }
      if (!beam_active[j][i] && !sr_data[i] && is_absent[j][i] && !aps_fault[j][i])
        beamline->user_down[j][i] = 1;
      if (!beam_active[j][i] && !sr_data[i] && aps_fault[j][i])
        beamline->pss_down[j][i] = 1;
      /*do not count down time if user is present */
    }
    free(beam_ready[j]);
    free(aps_fault[j]);
    free(is_absent[j]);
  }
  beamline->beam_active = beam_active;
  free(beam_ready);
  free(aps_fault);
  free(is_absent);
  return;
}

void SetupOutputFile(char *outputFile, SDDS_DATASET *out_data, long withoutSchedule) {
  char units[25];
  if (withoutSchedule)
    sprintf(units, "hour");
  else
    sprintf(units, "s");
  if (!SDDS_InitializeOutput(out_data, SDDS_ASCII, 1, NULL, NULL, outputFile))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  if (!SDDS_DefineSimpleParameter(out_data, "RunName", NULL, SDDS_STRING) ||
      !SDDS_DefineSimpleParameter(out_data, "Beamline", NULL, SDDS_STRING) ||
      !SDDS_DefineSimpleParameter(out_data, "TotalAvailableTime", units, SDDS_DOUBLE) ||
      !SDDS_DefineSimpleParameter(out_data, "TotalDeliveredTime", units, SDDS_DOUBLE) ||
      !SDDS_DefineSimpleParameter(out_data, "StartTime", "s", SDDS_DOUBLE) ||
      !SDDS_DefineSimpleParameter(out_data, "EndTime", "s", SDDS_DOUBLE) ||
      !SDDS_DefineSimpleColumn(out_data, "UserDownTime", units, SDDS_DOUBLE) ||
      !SDDS_DefineSimpleColumn(out_data, "PSSDownTime", units, SDDS_DOUBLE) ||
      !SDDS_DefineSimpleColumn(out_data, "ActiveBeamTime", units, SDDS_DOUBLE))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  if (!withoutSchedule) {
    if (!SDDS_DefineSimpleColumn(out_data, "SRDownTime", "s", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "MaintenanceTime", "s", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "StartTime", "s", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "EndTime", "s", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "DOEBeamline", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleParameter(out_data, "Station", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleParameter(out_data, "StationName", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleColumn(out_data, "ActivityId", NULL, SDDS_LONG) ||
        !SDDS_DefineSimpleColumn(out_data, "ActivityType", NULL, SDDS_STRING))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  } else {
    /*compute up time and downtime without beamline schedule */
    if (!SDDS_DefineSimpleParameter(out_data, "StartTimeStamp", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleParameter(out_data, "EndTimeStamp", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleColumn(out_data, "DOEBeamline", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleColumn(out_data, "Station", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleColumn(out_data, "StationName", NULL, SDDS_STRING) ||
        !SDDS_DefineSimpleParameter(out_data, "SRDownTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleParameter(out_data, "MaintenanceTime", "hour", SDDS_DOUBLE) ||
        !SDDS_DefineSimpleColumn(out_data, "UpPercentage", NULL, SDDS_DOUBLE))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  }
  if (!SDDS_WriteLayout(out_data))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  return;
}

/*startTime is the input startTime, endTime is the input endTime */
void compute_station_downtime(BEAMLINE_INFO *beamline, long station,
                              double startTime, double endTime, SDDS_DATASET *out_data) {
  long startIndex, endIndex, rows, returnCode, pss_rows, row, i;
  double *time = NULL, start, end;
  double *start_time, *end_time;
  double sr_downtime, pss_downtime, user_downtime, maintenance_time, active_time, total_available_time, total_delivered_time, scheduleTime;
  short *sr_down, *pss_down, *maintenance, *user_down, *active;

  rows = beamline->rows[station];
  start_time = beamline->start_time[station];
  end_time = beamline->end_time[station];
  sr_down = beamline->sr_down;
  maintenance = beamline->maintenance;
  user_down = beamline->user_down[station];
  pss_down = beamline->pss_down[station];
  pss_rows = beamline->pss_rows;
  active = beamline->beam_active[station];

  time = malloc(sizeof(*time) * pss_rows);
  memcpy(time, beamline->pss_time, sizeof(double) * pss_rows);
  if (!SDDS_StartPage(out_data, rows))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  if (!SDDS_SetParameters(out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                          "StartTime", startTime,
                          "EndTime", endTime,
                          "Beamline", beamline->beamline_name,
                          "Station", beamline->station_name[station],
                          "StationName", beamline->stationName[station],
                          "DOEBeamline", beamline->DOEBeamline[station],
                          "RunName", beamline->runName,
                          "StationName", beamline->stationName[station],
                          NULL))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  if (!SDDS_SetColumn(out_data, SDDS_SET_BY_NAME, beamline->activityID[station], rows, "ActivityId") ||
      !SDDS_SetColumn(out_data, SDDS_SET_BY_NAME, beamline->activityType[station], rows, "ActivityType") ||
      !SDDS_SetColumn(out_data, SDDS_SET_BY_NAME, beamline->start_time[station], rows, "StartTime") ||
      !SDDS_SetColumn(out_data, SDDS_SET_BY_NAME, beamline->end_time[station], rows, "EndTime"))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  total_available_time = 0.0;
  total_delivered_time = 0.0;
  for (row = 0; row < rows; row++) {
    sr_downtime = pss_downtime = user_downtime = maintenance_time = active_time = 0;
    start = start_time[row];
    end = end_time[row];
    if (end < startTime || start > endTime) {
      if (!SDDS_SetRowValues(out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, row,
                             "UserDownTime", user_downtime,
                             "PSSDownTime", pss_downtime,
                             "SRDownTime", sr_downtime,
                             "MaintenanceTime", maintenance_time,
                             "ActiveBeamTime", active_time,
                             NULL))
        SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
      continue;
    }
    if (start < startTime)
      start = startTime;
    if (end > endTime)
      end = endTime;
    if (start < endTime) {
      /*found the start and end range */
      if (start <= beamline->pss_time[0]) {
        startIndex = 0;
      } else {
        interp_short(maintenance, beamline->pss_time, pss_rows, start, 0, -1, &returnCode, &startIndex);
      }
      time[startIndex] = start;
      if (end >= beamline->pss_time[pss_rows - 1]) {
        endIndex = pss_rows - 1;
      } else {
        interp_short(maintenance, beamline->pss_time, pss_rows, end, 0, -1, &returnCode, &endIndex);

        if (endIndex == startIndex)
          endIndex = startIndex + 1;
        if (endIndex > pss_rows)
          endIndex -= pss_rows - 1;
      }
      /* time[endIndex] = end; */
      pss_downtime = computeDownTime(time, startIndex, endIndex, pss_down, start, end, pss_rows);
      maintenance_time = computeDownTime(time, startIndex, endIndex, maintenance, start, end, pss_rows);
      user_downtime = computeDownTime(time, startIndex, endIndex, user_down, start, end, pss_rows);
      if (debug)
        fprintf(stdout, "sr downtime.\n");
      sr_downtime = computeDownTime(time, startIndex, endIndex, sr_down, start, end, pss_rows);
      if (debug)
        fprintf(stdout, "sr downtime=%lf\n", sr_downtime);
      active_time = computeDownTime(time, startIndex, endIndex, active, start, end, pss_rows);
      /* if (active_time>=sr_downtime)
         active_time -= sr_downtime; */
      /*active does not consider stored beam */
    }
    scheduleTime = end - start - maintenance_time;
    total_available_time += scheduleTime;
    total_delivered_time += scheduleTime - sr_downtime;
    if (!SDDS_SetRowValues(out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, row,
                           "UserDownTime", user_downtime,
                           "PSSDownTime", pss_downtime,
                           "SRDownTime", sr_downtime,
                           "MaintenanceTime", maintenance_time,
                           "ActiveBeamTime", active_time,
                           NULL))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  }

  if (!SDDS_SetParameters(out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                          "TotalAvailableTime", total_available_time,
                          "TotalDeliveredTime", total_delivered_time,
                          NULL))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

  free(time);
  if (!SDDS_WritePage(out_data))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  return;
}

/* this function compute the down time */
double computeDownTime(double *time, long startIndex, long endIndex, short *down, double start, double end, long rows) {
  long i;
  double downtime = 0, prevtime;
  short firstDown = 1, lastdown = 0;
  char text[30];
  for (i = startIndex; i <= endIndex; i++) {
    TimeEpochToText(text, time[i]);
    /* if (i==endIndex && down[i-1]==1)
       lastdown = 1; */
    if (debug)
      fprintf(stdout, "i=%d time=%s\n", i, text);
    if (down[i] || lastdown) {
      if (debug)
        fprintf(stdout, "down %d\n", i);
      /* 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;
      }
    }
  }
  if (debug)
    fprintf(stdout, "downtime=%lf\n", downtime);
  if (endIndex < rows - 1 && endIndex < rows - 1 && down[endIndex] && down[endIndex + 1] && end > down[endIndex])
    downtime += end - time[endIndex];
  if (debug)
    fprintf(stdout, "downtime now=%lf\n", downtime);
  return downtime;
}

/* the one time within one 8-hour shift is marked as absent, then
   the whole shift is consider to be absent. */
void process_absent_data(short *absent, double *time, long rows) {
  int i, j, k;
  double startTime = 0, endTime, hour = 0;
  short absent0 = 0;

  for (i = 0; i < rows; i++) {
    TimeEpochToBreakdown(NULL, NULL, NULL, NULL, &hour, time[i]);
    if (hour == 0. || hour == 8.0 || hour == 16.0 || hour == 24.0) {
      startTime = time[i];
      endTime = startTime + 8.0 * 3600;
      j = i;
      absent0 = 0;
      while (j < rows && time[j] < endTime) {
        if (absent[j]) {
          absent0 = 1;
          break;
        }
        j++;
      }
      if (absent0) {
        for (k = i; k < j; k++)
          absent[k] = 1;
      }
      i = j;
    }
  }
}

void compute_station_downtime_without_schedule(BEAMLINE_INFO *beamline, double startTime, double endTime, SDDS_DATASET *out_data) {
  long j;
  long startIndex, endIndex, rows, returnCode, pss_rows, row, i;
  double *time = NULL, start, end;
  double *start_time, *end_time;
  double sr_downtime, pss_downtime, user_downtime, maintenance_time, active_time, total_available_time, total_delivered_time, scheduleTime;
  short *sr_down, *pss_down, *maintenance, *user_down, *active;
  char *timestamps, *timestampe;

  if (!SDDS_StartPage(out_data, beamline->stations))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);

  total_available_time = 0.0;
  total_delivered_time = 0.0;
  sr_down = beamline->sr_down;
  pss_rows = beamline->pss_rows;
  time = malloc(sizeof(*time) * pss_rows);
  memcpy(time, beamline->pss_time, sizeof(double) * pss_rows);
  maintenance = beamline->maintenance;

  if (startTime <= beamline->pss_time[0]) {
    startIndex = 0;
  } else {
    interp_short(maintenance, beamline->pss_time, pss_rows, startTime, 0, -1, &returnCode, &startIndex);
  }
  time[startIndex] = startTime;
  if (end >= beamline->pss_time[pss_rows - 1]) {
    endIndex = pss_rows - 1;
  } else {
    interp_short(maintenance, beamline->pss_time, pss_rows, endTime, 0, -1, &returnCode, &endIndex);
    if (endIndex == startIndex)
      endIndex = startIndex + 1;
    if (endIndex > pss_rows)
      endIndex -= pss_rows - 1;
  }

  maintenance_time = computeDownTime(time, startIndex, endIndex, maintenance, start, end, pss_rows);
  sr_downtime = computeDownTime(time, startIndex, endIndex, sr_down, start, end, pss_rows);
  scheduleTime = endTime - startTime - maintenance_time;
  total_available_time = scheduleTime;
  total_delivered_time = scheduleTime - sr_downtime;
  SDDS_CopyString(&timestamps, makeTimeStamp(startTime));
  SDDS_CopyString(&timestampe, makeTimeStamp(endTime));
  if (!SDDS_SetParameters(out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE,
                          "StartTime", startTime,
                          "EndTime", endTime,
                          "StartTimeStamp", timestamps,
                          "EndTimeStamp", timestampe,
                          "Beamline", beamline->beamline_name,
                          "RunName", beamline->runName,
                          "TotalAvailableTime", total_available_time / 3600,
                          "TotalDeliveredTime", total_delivered_time / 3600,
                          "MaintenanceTime", maintenance_time / 3600,
                          "SRDownTime", sr_downtime / 3600,
                          NULL))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  for (j = 0; j < beamline->stations; j++) {
    user_down = beamline->user_down[j];
    pss_down = beamline->pss_down[j];
    active = beamline->beam_active[j];
    pss_downtime = computeDownTime(time, startIndex, endIndex, pss_down, start, end, pss_rows);
    user_downtime = computeDownTime(time, startIndex, endIndex, user_down, start, end, pss_rows);
    active_time = computeDownTime(time, startIndex, endIndex, active, start, end, pss_rows);
    if (!SDDS_SetRowValues(out_data, SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, j,
                           "Station", beamline->station_name[j],
                           "StationName", beamline->stationName[j],
                           "DOEBeamline", beamline->DOEBeamline[j],
                           "StationName", beamline->stationName[j],
                           "UserDownTime", user_downtime / 3600,
                           "PSSDownTime", pss_downtime / 3600,
                           "ActiveBeamTime", active_time / 3600,
                           "UpPercentage", active_time / total_delivered_time * 100,
                           NULL))
      SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  }

  free(time);
  if (!SDDS_WritePage(out_data))
    SDDS_PrintErrors(stderr, SDDS_VERBOSE_PrintErrors | SDDS_EXIT_PrintErrors);
  return;
}
