/*****************************************************************************
 * Open MCT, Copyright (c) 2014-2024, United States Government
 * as represented by the Administrator of the National Aeronautics and Space
 * Administration. All rights reserved.
 *
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 * Open MCT includes source code licensed under additional open source
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 * this source code distribution or the Licensing information page available
 * at runtime from the About dialog for additional information.
 *****************************************************************************/

import { EventEmitter } from 'eventemitter3';

export default class StyleRuleManager extends EventEmitter {
  constructor(styleConfiguration, openmct, callback, suppressSubscriptionOnEdit) {
    super();
    this.openmct = openmct;
    this.callback = callback;
    this.refreshData = this.refreshData.bind(this);
    this.toggleSubscription = this.toggleSubscription.bind(this);
    if (suppressSubscriptionOnEdit) {
      this.openmct.editor.on('isEditing', this.toggleSubscription);
      this.isEditing = this.openmct.editor.editing;
    }

    if (styleConfiguration) {
      // We don't set the selectedConditionId here because we want condition set computation to happen before we apply any selected style
      const styleConfigurationWithNoSelection = Object.assign(styleConfiguration, {
        selectedConditionId: ''
      });
      this.initialize(styleConfigurationWithNoSelection);
      if (styleConfiguration.conditionSetIdentifier) {
        this.openmct.time.on('boundsChanged', this.refreshData);
        this.subscribeToConditionSet();
      } else {
        this.applyStaticStyle();
      }
    }
  }

  toggleSubscription(isEditing) {
    this.isEditing = isEditing;
    if (this.isEditing) {
      if (this.stopProvidingTelemetry) {
        this.stopProvidingTelemetry();
        delete this.stopProvidingTelemetry;
      }

      if (this.conditionSetIdentifier) {
        this.applySelectedConditionStyle();
      }
    } else if (this.conditionSetIdentifier) {
      //reset the selected style and let the condition set output determine what it should be
      this.selectedConditionId = undefined;
      this.currentStyle = undefined;
      this.updateDomainObjectStyle();
      this.subscribeToConditionSet();
    }
  }

  initialize(styleConfiguration) {
    this.conditionSetIdentifier = styleConfiguration.conditionSetIdentifier;
    this.selectedConditionId = styleConfiguration.selectedConditionId;
    this.staticStyle = styleConfiguration.staticStyle;
    this.defaultConditionId = styleConfiguration.defaultConditionId;
    this.updateConditionStylesMap(styleConfiguration.styles || []);
  }

  subscribeToConditionSet() {
    if (this.stopProvidingTelemetry) {
      this.stopProvidingTelemetry();
      delete this.stopProvidingTelemetry;
    }

    this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
      this.openmct.telemetry.request(conditionSetDomainObject).then((output) => {
        if (
          output &&
          output.length &&
          this.conditionSetIdentifier &&
          this.openmct.objects.areIdsEqual(
            conditionSetDomainObject.identifier,
            this.conditionSetIdentifier
          )
        ) {
          this.handleConditionSetResultUpdated(output[0]);
        }
      });
      if (
        this.conditionSetIdentifier &&
        this.openmct.objects.areIdsEqual(
          conditionSetDomainObject.identifier,
          this.conditionSetIdentifier
        )
      ) {
        this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(
          conditionSetDomainObject,
          this.handleConditionSetResultUpdated.bind(this)
        );
      }
    });
  }

  refreshData(bounds, isTick) {
    if (!isTick) {
      let options = {
        start: bounds.start,
        end: bounds.end,
        size: 1,
        strategy: 'latest'
      };
      this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
        this.openmct.telemetry.request(conditionSetDomainObject, options).then((output) => {
          if (output && output.length) {
            this.handleConditionSetResultUpdated(output[0]);
          }
        });
      });
    }
  }

  updateObjectStyleConfig(styleConfiguration) {
    if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
      this.initialize(styleConfiguration || {});
      this.applyStaticStyle();
      this.destroy(true);
    } else {
      let isNewConditionSet =
        !this.conditionSetIdentifier ||
        !this.openmct.objects.areIdsEqual(
          this.conditionSetIdentifier,
          styleConfiguration.conditionSetIdentifier
        );
      this.initialize(styleConfiguration);
      if (this.isEditing) {
        this.applySelectedConditionStyle();
      } else {
        //Only resubscribe if the conditionSet has changed.
        if (isNewConditionSet) {
          this.subscribeToConditionSet();
        }
      }
    }
  }

  updateConditionStylesMap(conditionStyles) {
    let conditionStyleMap = {};
    conditionStyles.forEach((conditionStyle) => {
      if (conditionStyle.conditionId) {
        conditionStyleMap[conditionStyle.conditionId] = conditionStyle.style;
      } else {
        conditionStyleMap.static = conditionStyle.style;
      }
    });
    this.conditionalStyleMap = conditionStyleMap;
  }

  handleConditionSetResultUpdated(resultData) {
    let foundStyle = this.conditionalStyleMap[resultData.conditionId];
    if (foundStyle) {
      if (foundStyle !== this.currentStyle) {
        this.currentStyle = foundStyle;
      }

      this.updateDomainObjectStyle();
    } else {
      this.applyStaticStyle();
    }
  }

  updateDomainObjectStyle() {
    if (this.callback) {
      this.callback(Object.assign({}, this.currentStyle));
    }
  }

  applySelectedConditionStyle() {
    const conditionId = this.selectedConditionId || this.defaultConditionId;
    if (!conditionId) {
      this.applyStaticStyle();
    } else if (this.conditionalStyleMap[conditionId]) {
      this.currentStyle = this.conditionalStyleMap[conditionId];
      this.updateDomainObjectStyle();
    }
  }

  applyStaticStyle() {
    if (this.staticStyle) {
      this.currentStyle = this.staticStyle.style;
    } else {
      if (this.currentStyle) {
        Object.keys(this.currentStyle).forEach((key) => {
          this.currentStyle[key] = '__no_value';
        });
      }
    }

    this.updateDomainObjectStyle();
  }

  destroy(skipEventListeners) {
    if (this.stopProvidingTelemetry) {
      this.stopProvidingTelemetry();
      delete this.stopProvidingTelemetry;
    }

    if (!skipEventListeners) {
      this.openmct.time.off('boundsChanged', this.refreshData);
      this.openmct.editor.off('isEditing', this.toggleSubscription);
    }

    this.conditionSetIdentifier = undefined;
  }
}
