/**
 * Copyright 2015 CANAL+ Group
 *
 * 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.
 */

import isNullOrUndefined from "../../../../../../utils/is_null_or_undefined";
import type { ITNode } from "../../../../../../utils/xml-parser";
import type { IIndexSegment } from "../../../../utils/index_helpers";

/**
 * By comparing two timelines for the same content at different points in time,
 * retrieve the index in both timelines of the first segment having the same
 * starting time.
 * Returns `null` if not found.
 * @param {Array.<Object>} prevTimeline
 * @param {Array.<Object>} newElements
 * @returns {Object|null}
 */
export default function findFirstCommonStartTime(
  prevTimeline: IIndexSegment[],
  newElements: ITNode[] | HTMLCollection,
): {
  /** Index in `prevSegments` where the first common segment start is found. */
  prevSegmentsIdx: number;
  /** Index in `newElements` where the first common segment start is found. */
  newElementsIdx: number;
  /**
   * Value to set `repeatCount` to at `prevSegments[prevSegmentsIdx]` to
   * retrieve the segment with the first common start time
   */
  repeatNumberInPrevSegments: number;
  /**
   * Value to set `repeatCount` to at `newElements[newElementsIdx]` to
   * retrieve the segment with the first common start time
   */
  repeatNumberInNewElements: number;
} | null {
  if (prevTimeline.length === 0 || newElements.length === 0) {
    return null;
  }
  const prevInitialStart = prevTimeline[0].start;
  const newFirstTAttr = Array.isArray(newElements)
    ? newElements[0].attributes.t
    : newElements[0].getAttribute("t");
  const newInitialStart = isNullOrUndefined(newFirstTAttr)
    ? null
    : parseInt(newFirstTAttr, 10);
  if (newInitialStart === null || Number.isNaN(newInitialStart)) {
    return null;
  }

  if (prevInitialStart === newInitialStart) {
    return {
      prevSegmentsIdx: 0,
      newElementsIdx: 0,
      repeatNumberInPrevSegments: 0,
      repeatNumberInNewElements: 0,
    };
  } else if (prevInitialStart < newInitialStart) {
    let prevElt = prevTimeline[0];
    let prevElementIndex = 0;
    while (true) {
      if (prevElt.repeatCount > 0) {
        const diff = newInitialStart - prevElt.start;
        if (
          diff % prevElt.duration === 0 &&
          diff / prevElt.duration <= prevElt.repeatCount
        ) {
          const repeatNumberInPrevSegments = diff / prevElt.duration;
          return {
            repeatNumberInPrevSegments,
            prevSegmentsIdx: prevElementIndex,
            newElementsIdx: 0,
            repeatNumberInNewElements: 0,
          };
        }
      }
      prevElementIndex++;
      if (prevElementIndex >= prevTimeline.length) {
        return null;
      }
      prevElt = prevTimeline[prevElementIndex];
      if (prevElt.start === newInitialStart) {
        return {
          prevSegmentsIdx: prevElementIndex,
          newElementsIdx: 0,
          repeatNumberInPrevSegments: 0,
          repeatNumberInNewElements: 0,
        };
      } else if (prevElt.start > newInitialStart) {
        return null;
      }
    }
  } else {
    let newElementsIdx = 0;
    let newNodeElt: ITNode | null = Array.isArray(newElements) ? newElements[0] : null;
    let newDomElt: Element | null = Array.isArray(newElements) ? null : newElements[0];
    let currentTimeOffset = newInitialStart;
    while (true) {
      const dAttr =
        newNodeElt !== null ? newNodeElt.attributes.d : newDomElt?.getAttribute("d");
      const duration = isNullOrUndefined(dAttr) ? null : parseInt(dAttr, 10);
      if (duration === null || Number.isNaN(duration)) {
        return null;
      }
      const rAttr =
        newNodeElt !== null ? newNodeElt.attributes.r : newDomElt?.getAttribute("r");
      const repeatCount = isNullOrUndefined(rAttr) ? null : parseInt(rAttr, 10);

      if (repeatCount !== null) {
        if (Number.isNaN(repeatCount) || repeatCount < 0) {
          return null;
        }
        if (repeatCount > 0) {
          const diff = prevInitialStart - currentTimeOffset;
          if (diff % duration === 0 && diff / duration <= repeatCount) {
            const repeatNumberInNewElements = diff / duration;
            return {
              repeatNumberInPrevSegments: 0,
              repeatNumberInNewElements,
              prevSegmentsIdx: 0,
              newElementsIdx,
            };
          }
        }
        currentTimeOffset += duration * (repeatCount + 1);
      } else {
        currentTimeOffset += duration;
      }
      newElementsIdx++;
      if (newElementsIdx >= newElements.length) {
        return null;
      }
      if (Array.isArray(newElements)) {
        newNodeElt = newElements[newElementsIdx];
      } else {
        newDomElt = newElements[newElementsIdx];
      }
      const tAttr =
        newNodeElt !== null ? newNodeElt.attributes.t : newDomElt?.getAttribute("t");
      const time = isNullOrUndefined(tAttr) ? null : parseInt(tAttr, 10);
      if (time !== null) {
        if (Number.isNaN(time)) {
          return null;
        }
        currentTimeOffset = time;
      }
      if (currentTimeOffset === prevInitialStart) {
        return {
          newElementsIdx,
          prevSegmentsIdx: 0,
          repeatNumberInPrevSegments: 0,
          repeatNumberInNewElements: 0,
        };
      } else if (currentTimeOffset > newInitialStart) {
        return null;
      }
    }
  }
}
