import {Component, ElementRef, HostListener, OnDestroy, OnInit, QueryList, Renderer2, ViewChildren} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {map, tap} from 'rxjs/operators';
import * as moment from 'moment-timezone';
import {GraphLoader} from '../../shared/graph-loading/graphloader';
import {SensorsService} from '../../shared/api/endpoints/services/sensors.service';
import {HttpResponse} from '@angular/common/http';
import {ToastService} from '../../shared/services/toast.service';
import {Sensor} from '../../shared/api/endpoints/models/sensor';
import {ObservationService} from '../../shared/api/endpoints/services/observation.service';
import {SensorType} from '../../shared/api/endpoints/models/sensor-type';
import {Subscription} from 'rxjs';
import type {View} from 'vega';

@Component({
  selector: 'app-unit',
  templateUrl: './unit.component.html',
  styleUrls: ['./unit.component.scss']
})
export class UnitComponent implements OnInit, OnDestroy {

  preselectedSensors: string;
  unitId: number;
  viewCount = 0;
  data = [];
  time = [];
  from: Date = moment().hour(0).minutes(0).subtract(7, 'days').toDate();
  to: Date = moment().toDate();
  today: Date = moment().toDate();
  observationsData: any[] = [];
  sensorGroups = [];
  selectedSensors: string[] = [];
  sensors: Sensor[];
  dateChanged = false;
  sensorTypes: SensorType[];
  unitDescription: string;
  subscription: Subscription[] = [];
  showIntervalError = false;

  // One flag per group
  showVega: Record<string, boolean> = {};
  showVegaCounter: Record<string, number> = {};
  graphViews: Record<string, View> = {};

  // Grab *all* containers that match the template ref vegaContainer
  @ViewChildren('vegaContainer') vegaContainers!: QueryList<ElementRef>;

  constructor(
    private activatedRoute: ActivatedRoute,
    private sensorService: SensorsService,
    private toastService: ToastService,
    private observationService: ObservationService,
    private route: ActivatedRoute,
    private renderer: Renderer2
  ) {
    this.getInitData();
    // get unit sensors and prepare them for view
    this.sensorService.getUnitSensors({ unit_id: this.unitId }).pipe(
      tap(sens => {
        this.sensors = sens;
        this.sensors.sort((a, b) => a.sensorId - b.sensorId);
      }),
      tap(() => {
        if (this.sensors && this.sensors.length > 0) {
          this.sensors.forEach(sensor => {
            const sensorType = sensor.sensorId.toString().slice(0, 5);
            if (!this.sensorGroups.some(group => group === sensorType)) { // create sensor groups only for unit sensors
              this.sensorGroups.push(sensorType);
              setTimeout(() => {
                GraphLoader.getGraph(null, null, null, '#vega_container_' + sensor.sensorId.toString().slice(0, 5), null);
              }, 0);
            }
          });
        }
      })
    ).toPromise().then();
  }

  /**
   * Fires on every resize event
   */
  @HostListener('window:resize', ['$event'])
  public onWindowResize(event: UIEvent): void {
    this.onResize();
  }

  private onResize() {
    const itemCount = Object.keys(this.graphViews).length;
    console.log('Number of entries:', itemCount);

    Object.entries(this.graphViews).forEach(([key, view]) => {
      if (view) {
        const box = document.getElementById('vega_container_' + key);
        const newWidth = box.getBoundingClientRect().width - 50;
        GraphLoader.ResizeGraph(view, newWidth);
      }
    })
  }

  /**
   * Unsubscribe after leaving
   */
  ngOnDestroy(): void {
    this.subscription.forEach(subs => subs.unsubscribe());
  }

  /**
   * Sets up default data
   */
  getInitData() {
    this.route.queryParams.subscribe(params => {
      if (params.unitDescription) {
        this.unitDescription = params.unitDescription;
      }
    });
    this.sensorService.getSensorTypes().toPromise().then(types => this.sensorTypes = types);
    this.unitId = parseInt(this.activatedRoute.snapshot.paramMap.get('unitId'), 10);
  }

  ngOnInit(): void {
  }

  /**
   * Shows get data button
   */
  onDateChanged() {
    if (moment(this.to).diff(moment(this.from), 'months') > 6){
      this.dateChanged = false;
      this.showIntervalError = true;
    }
    else if (this.to < this.from) {
      this.dateChanged = false;
      this.showIntervalError = true;
    }
    else{
      this.dateChanged = true;
      this.showIntervalError = false;
    }
  }

  /**
   * Gets data based on selected time range
   */
  showGraph(changedDate: boolean = true, changedSensor: string = null) {
    const range: Date[] = [this.from, this.to];
    this.getObservations(range, changedDate, changedSensor);
  }


  /**
   * Check button handler.
   * @param sensorId checked sensorId
   * @param event event for getting if checked or unchecked
   */
  async addSensorToGraph(sensorId: string, event) {
    const groupId = sensorId.toString().slice(0, 5);
    const sensorGroupElement = '#vega_container_' + groupId;

    // if the checkmark is checked then the graph will be displayed
    this.graphViews[groupId] = null;
    this.showVega[groupId] = false;
    if (event.checked.includes(sensorId.toString()))
      this.showVega[groupId] = true;

    const box = document.getElementById('vega_container_' + groupId);
    const boxWidth = box.getBoundingClientRect().width;
    const boxHeight = box.getBoundingClientRect().height;

    GraphLoader.setSize(boxWidth - 50, 300);

    if (!this.selectedSensors.find(sensId => sensId.toString().slice(0, 5) === groupId)) { // if group of sensors is empty show empty graph
      // GraphLoader.getAnalyticsGraph(null, null, null, sensorGroupElement);
      this.graphViews[groupId] = await GraphLoader.getGraph(null, null, null, sensorGroupElement, null);
    } else {
      // use observations data
      if (event.checked) { // if checked > add to graph
        if (this.observationsData.some(sens => sens.sensorId.toString() === sensorId)) { // if already data for selected sensor in memory
          this.graphViews[groupId] = await GraphLoader.getGraph(this.filteredSelectedSensors(groupId),
              this.filteredObservationData(groupId), this.filteredSensorsInfos(groupId), sensorGroupElement, false);
        } else { // get data from server for added sensor and show graph for selected sensors
          this.showGraph(false, sensorId);
        }
      } else { // remove sensor from graph
        this.graphViews[groupId] = await GraphLoader.getGraph(this.filteredSelectedSensors(groupId), this.filteredObservationData(groupId),
            this.filteredSensorsInfos(groupId), sensorGroupElement, false);
      }
    }

    // no data in graph -> the graph element will be destroyed
    if (!this.showVega[groupId]) {
      const container = document.getElementById(`vega_container_${groupId}`);
      if (container) {
        container.innerHTML = '';
        this.graphViews[groupId] = null;
      }
      console.log(`Cleared children of #vega_container_${groupId}`);
      }
  }

  /**
   * Filter observations data only fro selected sensors.
   * @param sensorGroupId id of changed sensor group
   */
  filteredObservationData(sensorGroupId: string): any {
    return this.observationsData.filter(sen => this.selectedSensors.includes(sen.sensorId.toString()) &&
      sen.sensorId.toString().slice(0, 5) === sensorGroupId);
  }

  /**
   * Filter only selected sensors for group of sensors
   * @param sensorGroupId group of sensors
   */
  filteredSelectedSensors(sensorGroupId: string): any {
    return this.selectedSensors.filter(sen => sen.toString().slice(0, 5) === sensorGroupId);
  }

  /**
   * Get sensors only for group
   * @param sensorGroupId group id
   */
  filteredSensorsInfos(sensorGroupId: string): any {
    return this.sensors.filter(sen => this.selectedSensors.includes(sen.sensorId.toString()) &&
      sen.sensorId.toString().slice(0, 5) === sensorGroupId);
  }

  /**
   * Gets data from observation endpoint
   * @param range from and to interval
   * @param changedDate determines if dates changed so we need refresh all data
   * @param changedSensorId if selecting sensor only fetch data for this server
   */
  getObservations(range: Date[], changedDate: boolean, changedSensorId: string) {
    if (changedDate) { // if changed date we need new data for all sensors
      this.observationsData = []; // empty observation data
      this.selectedSensors.forEach(selectSens => {
        this.observationEndpointRequest(selectSens, range);
      });
    } else { // add data for added sensor
      this.observationEndpointRequest(changedSensorId, range);
    }
  }

  /**
   * Endpoint request to get observation data for sensor
   * @param sensorId sensor id to get data
   * @param range from and to interval
   */
  observationEndpointRequest(sensorId: string, range: Date[]) {
    this.observationService.getObservation$Response({
      unit_id: this.unitId,
      sensor_id: parseInt(sensorId, 10),
      from: moment(range[0]).format('yyyy-MM-DD HH:mm:ssZ').slice(0, -3),
      to: moment(range[1]).format('yyyy-MM-DD HH:mm:ssZ').slice(0, -3)
    }).pipe(
      map((response: HttpResponse<any>) => {
        if (response.status === 200) {
          return response.body;
        } else if (response.status === 204) {
          this.toastService.showWarningNoData();
          return response.body;
        } else {
          return false;
        }
      })
    ).subscribe(
        async observations => {
        if (observations) {
          const groupId = sensorId.toString().slice(0, 5);
          this.observationsData.push({
            sensorId, sensor:
              this.sensors.find(sens => sens.sensorId.toString() === sensorId.toString()), data: observations
          });
          const view = '#vega_container_' + sensorId.toString().slice(0, 5);
          this.graphViews[groupId] = await GraphLoader.getGraph(this.filteredSelectedSensors(groupId),
              this.filteredObservationData(groupId), this.filteredSensorsInfos(groupId), view, false);
        }
      }, err => this.toastService.showError(err.error.message));
  }
}
