import { parseValue } from './values';

export class Timeseries {
  constructor(id, endDate, days = 7, slots = 24) {
    this.id = id;
    this.endDate = endDate;
    this.startDate = this.getStartDate(endDate, days);
    this.days = this.getDays();
    this.slots = slots;
  }

  getStartDate(endDate, days) {
    const startDate = new Date();
    startDate.setDate(startDate.getDate() - days);
    startDate.setHours(0, 0, 0);
    return startDate;
  }

  getDays() {
    return Math.ceil((this.endDate - this.startDate) / (1000 * 60 * 60 * 24));
  }

  prepareSlots(defaultValue = 0) {
    const timeSlots = [];
    for (let i = 0; i < this.days; i++) {
      const daySlot = [];
      for (let ii = 0; ii < this.slots; ii++) {
        daySlot.push(defaultValue);
      }
      timeSlots.push(daySlot);
    }
    return timeSlots;
  }

  getDayLabels() {
    const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    const labels = [];
    const day = new Date(this.startDate.getTime());
    for (let i = 0; i < this.days; i++) {
      labels.push(`${day.getDate()} ${weekDays[day.getDay()]}`);
      day.setHours(day.getHours() + 24);
    }
    return labels;
  }

  getSlotLabels() {
    const labels = [];
    const day = new Date(this.startDate.getTime());
    const pad = (time) => {
      let val = `${time}`;
      if (val.length === 1) {
        val = `0${time}`;
      }
      return val;
    };

    day.setHours(0, 0, 0);
    for (let i = 0; i < this.slots; i++) {
      const from = day.getHours();
      day.setHours(day.getHours() + (24 / this.slots));
      let to = day.getHours();
      if (to === 0) {
        to = 24;
      }
      if (this.slots === 24) {
        labels.push(`${pad(from)}`);
      } else {
        labels.push(`${pad(from)}-${pad(to)}`);
      }
    }
    return labels;
  }

  flattenData(data, fromDay = 0, fromHour = 0) {
    const flattened = [];
    data.forEach((dayData, dayIdx) => {
      if (dayIdx < fromDay) {
        return;
      }
      dayData.forEach((point, hourIdx) => {
        if (dayIdx === fromDay && hourIdx <= fromHour) {
          return;
        }
        flattened.push(point);
      });
    });

    return flattened;
  }

  mapData(data, options) {
    const slots = this.prepareSlots(null);
    data.forEach((point) => {
      const pointDate = new Date(point.timestamp);
      const pointValue = parseValue(point.value);
      const daySlot = Math.floor((pointDate - this.startDate) / (1000 * 60 * 60 * 24));
      const timeSlot = Math.floor(pointDate.getHours() / (24 / this.slots));
      if (slots[daySlot][timeSlot] === null) {
        slots[daySlot][timeSlot] = 0;
      }
      if (options.accumulate) {
        slots[daySlot][timeSlot] += pointValue;
      } else {
        slots[daySlot][timeSlot] = Math.max(pointValue, slots[daySlot][timeSlot]);
      }
    });

    // Deal with slots without data
    let prevVal = 0;
    slots.forEach((dayData, dayIdx) => {
      let prevValIdx = 0;
      const dayVals = dayData;
      dayVals.forEach((val, idx) => {
        if (val !== null) {
          prevVal = val;
          prevValIdx = idx;
          return;
        }
        if (options.interpolate) {
          const flattened = this.flattenData(slots, dayIdx, idx);
          for (let nextSlot = 0; nextSlot < flattened.length; nextSlot++) {
            if (flattened[nextSlot] === null) {
              continue;
            }
            const nextVal = flattened[nextSlot];
            if (nextVal === prevVal) {
              dayVals[idx] = prevVal;
              return;
            }
            let difference = 0;
            if (nextVal > prevVal) {
              difference = nextVal - prevVal;
            } else {
              difference = prevVal - nextVal;
            }
            const distance = nextSlot;
            let totalDistance = (nextSlot + idx) - prevValIdx;
            if (totalDistance < 1) {
              totalDistance = 1;
            }
            dayVals[idx] = Math.max(((difference / totalDistance) * distance) + nextVal, 0);
            return;
          }
          dayVals[idx] = 0;
          return;
        }
        if (options.usePreviousValue) {
          dayVals[idx] = prevVal;
          return;
        }
        dayVals[idx] = 0;
      });
    });
    return slots;
  }

  getUrl() {
    return `http://openmct.cbrp3.c-base.org/telemetry/${this.id}?start=${this.startDate.getTime()}&end=${this.endDate.getTime()}`;
  }

  getData(options) {
    return fetch(this.getUrl())
      .then(data => data.json())
      .then(data => this.mapData(data, options));
  }
}

export default Timeseries;