import moment from 'moment';
import { hash, all } from 'rsvp';
import { isDef, StartEndDuration, log } from './util.js';
import './stationxml.js';
import './seismogram.js';
import { seismogramPerChannel, parseDataRecords } from './miniseed.js';

/*
 * Philip Crotwell
 * University of South Carolina, 2019
 * http://www.seis.sc.edu
 */
const Allowed_Flags = ['n', 's', 'l', 'c', 'Y', 'j', 'H'];
class MSeedArchive {
  constructor(rootUrl, pattern) {
    this._rootUrl = rootUrl;
    this._pattern = pattern;
    this._recordSize = 512;
    this.checkPattern(this._pattern);
  }

  get rootUrl() {
    return this._rootUrl;
  }

  get pattern() {
    return this._pattern;
  }

  get recordSize() {
    return this._recordSize;
  }
  /** checks pattern for allowed flags as not all that are supported
    * by ringserver are supported here. Must only include:
    * * n network code, white space removed
    * * s station code, white space removed
    * * l  location code, white space removed
    * * c  channel code, white space removed
    * * Y  year, 4 digits
    * * j  day of year, 3 digits zero padded
    * * H  hour, 2 digits zero padded
    */


  checkPattern(p) {
    let regexp = /%[a-zA-Z]/g;
    let allFlags = p.match(regexp);

    if (!allFlags) {
      return false;
    } else {
      for (let f of allFlags) {
        if (Allowed_Flags.indexOf(f.slice(1)) === -1) {
          throw new Error("".concat(f, " not allowed in pattern"));
        }
      }
    }

    return true;
  }

  loadSeismograms(channelTimeList) {
    let promiseArray = channelTimeList.map(ct => {
      if (isDef(ct.channel)) {
        const channel = ct.channel;
        return hash({
          "request": ct,
          "dataRecords": this.loadDataForChannel(channel, ct.startTime, ct.endTime)
        });
      } else {
        throw new Error("channel is missing in loadSeismograms ");
      }
    });
    return all(promiseArray).then(pArray => {
      let out = [];
      pArray.forEach(p => {
        let seisArray = seismogramPerChannel(p.dataRecords); // should only be one

        for (let seis of seisArray) {
          let cutSeis = seis.cut(new StartEndDuration(p.request.startTime, p.request.endTime));
          p.request.seismogram = cutSeis;
          out.push(p.request);
        }
      });
      return out;
    });
  }

  loadDataForChannel(channel, startTime, endTime) {
    return this.loadData(channel.station.network.networkCode, channel.station.stationCode, channel.locationCode, channel.channelCode, startTime, endTime, channel.sampleRate);
  }

  loadData(net, sta, loc, chan, startTime, endTime, sampleRate) {
    let basePattern = this.fillBasePattern(net, sta, loc, chan);

    if (!isDef(sampleRate)) {
      sampleRate = minSampleRate(chan);
    }

    let recordTime = maxTimeForRecord(this._recordSize, sampleRate);
    let t = moment.utc(startTime).subtract(recordTime, 'seconds');
    let promiseArray = [];

    while (t.isBefore(endTime)) {
      let url = this.rootUrl + '/' + this.fillTimePattern(basePattern, t);
      promiseArray.push(fetch(url));
      t.add(1, 'hour');
    }

    if (moment.utc(t).add(recordTime, 'seconds').isAfter(endTime)) {
      let url = this.rootUrl + '/' + this.fillTimePattern(basePattern, t);
      promiseArray.push(fetch(url));
    }

    promiseArray = promiseArray.map(p => {
      return p.then(fetchResponse => {
        if (fetchResponse.ok) {
          if (fetchResponse.status === 200 || fetchResponse.status === 304) {
            return fetchResponse.arrayBuffer().then(ab => {
              let dataRecords = [];

              if (ab.byteLength > 0) {
                dataRecords = parseDataRecords(ab);
              }

              return dataRecords;
            });
          } else if (fetchResponse.status === 404) {
            return []; // empty array means no data
          } else {
            log("no data: status=" + fetchResponse.status + " " + fetchResponse.url);
            return [];
          }
        } else if (fetchResponse.status === 404) {
          return []; // empty array means no data
        } else {
          // $FlowFixMe
          throw new Error("fetch error: " + fetchResponse.ok + " " + fetchResponse.status + " " + fetchResponse.url);
        }
      }).catch(err => {
        log("caught fetch err, continuing with empty: " + err);
        return [];
      });
    });
    return all(promiseArray).then(pArray => {
      let dataRecords = [];
      pArray.forEach(p => {
        dataRecords = dataRecords.concat(p);
      });
      return dataRecords;
    }).then(dataRecords => {
      if (dataRecords) {
        dataRecords = dataRecords.filter(dr => dr.header.endTime.isSameOrAfter(startTime) && dr.header.startTime.isSameOrBefore(endTime));
      } else {
        dataRecords = []; // for flow
      }

      return dataRecords;
    });
  }

  fillBasePattern(net, sta, loc, chan) {
    return this.pattern.replace(/%n/g, net).replace(/%s/g, sta).replace(/%l/g, loc).replace(/%c/g, chan);
  }

  fillTimePattern(basePattern, t) {
    return basePattern.replace(/%Y/g, t.format('YYYY')).replace(/%j/g, t.format('DDDD')).replace(/%H/g, t.format('HH'));
  }

}
/** Gives the maximum sample rate for the channel, based on the
  * band code, first char, of the channel code. */

function maxSampleRate(chan) {
  let f = chan.slice(0, 1);

  switch (f) {
    case 'F':
    case 'G':
      return 5000;

    case 'D':
    case 'C':
      return 1000;

    case 'E':
    case 'H':
      return 250;

    case 'S':
    case 'B':
      return 80;

    case 'M':
      return 10;

    case 'L':
      return 1;

    case 'V':
      return .1;

    case 'U':
      return .01;

    case 'R':
      return .001;

    case 'P':
      return .0001;

    case 'Q':
      return .000001;

    default:
      throw new Error("Unknown band code " + f + " in " + chan);
  }
}
/** Gives the minimum sample rate for the channel, based on the
  * band code, first char, of the channel code. */

function minSampleRate(chan) {
  let f = chan.slice(0, 1);

  switch (f) {
    case 'F':
    case 'G':
      return 1000;

    case 'D':
    case 'C':
      return 2500;

    case 'E':
    case 'H':
      return 80;

    case 'S':
    case 'B':
      return 10;

    case 'M':
      return 1;

    case 'L':
      return 1;
    // maybe wrong, seed manual not clear

    case 'V':
      return .1;
    // maybe wrong, seed manual not clear

    case 'U':
      return .01;
    // maybe wrong, seed manual not clear

    case 'R':
      return .0001;

    case 'P':
      return .00001;

    case 'Q':
      return .0000001;

    default:
      throw new Error("Unknown band code " + f + " in " + chan);
  }
}
/** Calculates the maximum time coverage for a single miniseed record
  * given the record size (usually 512 or 4096) and the sample rate (Hertz).
  * This assumes 40 bytes of header and maximum compression of 2 samples
  * per byte (4 bit per sample) which is the best Steim2.
  */

function maxTimeForRecord(recordSize, sampleRate) {
  return (recordSize - 40) * 2 / sampleRate;
}

export { Allowed_Flags, MSeedArchive, maxSampleRate, maxTimeForRecord, minSampleRate };
//# sourceMappingURL=mseedarchive.js.map
