/**
 * 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 noop from "../../../../../../utils/noop";
import type {
  IEventStreamIntermediateRepresentation,
  IPeriodAttributes,
  IPeriodChildren,
} from "../../../node_parser_types";
import type { IAttributeParser, IChildrenParser } from "../parsers_stack";
import type ParsersStack from "../parsers_stack";
import { AttributeName, TagName } from "../types";
import { parseString } from "../utils";
import {
  generateAdaptationSetAttrParser,
  generateAdaptationSetChildrenParser,
} from "./AdaptationSet";
import { generateBaseUrlAttrParser } from "./BaseURL";
import { generateContentProtectionAttrParser } from "./ContentProtection";
import {
  generateEventStreamAttrParser,
  generateEventStreamChildrenParser,
} from "./EventStream";
import { generateSegmentTemplateAttrParser } from "./SegmentTemplate";

/**
 * Generate a "children parser" once inside a `Perod` node.
 * @param {Object} periodChildren
 * @param {WebAssembly.Memory} linearMemory
 * @param {ParsersStack} parsersStack
 * @param {ArrayBuffer} fullMpd
 * @returns {Function}
 */
export function generatePeriodChildrenParser(
  periodChildren: IPeriodChildren,
  linearMemory: WebAssembly.Memory,
  parsersStack: ParsersStack,
  fullMpd: ArrayBuffer,
): IChildrenParser {
  return function onRootChildren(nodeId: number) {
    switch (nodeId) {
      case TagName.AdaptationSet: {
        const adaptationObj = {
          children: { baseURLs: [], representations: [] },
          attributes: {},
        };
        periodChildren.adaptations.push(adaptationObj);
        const childrenParser = generateAdaptationSetChildrenParser(
          adaptationObj.children,
          linearMemory,
          parsersStack,
        );
        const attributeParser = generateAdaptationSetAttrParser(
          adaptationObj.attributes,
          linearMemory,
        );
        parsersStack.pushParsers(nodeId, childrenParser, attributeParser);
        break;
      }

      case TagName.BaseURL: {
        const baseUrl = { value: "", attributes: {} };
        periodChildren.baseURLs.push(baseUrl);
        const childrenParser = noop;
        const attributeParser = generateBaseUrlAttrParser(baseUrl, linearMemory);
        parsersStack.pushParsers(nodeId, childrenParser, attributeParser);
        break;
      }

      case TagName.EventStream: {
        const eventStream: IEventStreamIntermediateRepresentation = {
          children: { events: [] },
          attributes: {},
        };
        periodChildren.eventStreams.push(eventStream);
        const childrenParser = generateEventStreamChildrenParser(
          eventStream.children,
          linearMemory,
          parsersStack,
          fullMpd,
        );
        const attrParser = generateEventStreamAttrParser(
          eventStream.attributes,
          linearMemory,
        );
        parsersStack.pushParsers(nodeId, childrenParser, attrParser);
        break;
      }

      case TagName.SegmentTemplate: {
        const stObj = {};
        periodChildren.segmentTemplate = stObj;
        parsersStack.pushParsers(
          nodeId,
          noop, // SegmentTimeline as treated like an attribute
          generateSegmentTemplateAttrParser(stObj, linearMemory),
        );
        break;
      }

      case TagName.ContentProtection: {
        const contentProtection = {
          children: { cencPssh: [] },
          attributes: {},
        };
        if (periodChildren.contentProtections === undefined) {
          periodChildren.contentProtections = [];
        }
        periodChildren.contentProtections.push(contentProtection);
        const contentProtAttrParser = generateContentProtectionAttrParser(
          contentProtection,
          linearMemory,
        );
        parsersStack.pushParsers(nodeId, noop, contentProtAttrParser);
        break;
      }

      default:
        // Allows to make sure we're not mistakenly closing a re-opened
        // tag.
        parsersStack.pushParsers(nodeId, noop, noop);
        break;
    }
  };
}

/**
 * @param {Object} periodAttrs
 * @param {WebAssembly.Memory} linearMemory
 * @returns {Function}
 */
export function generatePeriodAttrParser(
  periodAttrs: IPeriodAttributes,
  linearMemory: WebAssembly.Memory,
): IAttributeParser {
  const textDecoder = new TextDecoder();
  return function onPeriodAttribute(attr, ptr, len) {
    switch (attr) {
      case AttributeName.Id:
        periodAttrs.id = parseString(textDecoder, linearMemory.buffer, ptr, len);
        break;
      case AttributeName.Start:
        periodAttrs.start = new DataView(linearMemory.buffer).getFloat64(ptr, true);
        break;
      case AttributeName.Duration:
        periodAttrs.duration = new DataView(linearMemory.buffer).getFloat64(ptr, true);
        break;
      case AttributeName.BitstreamSwitching:
        periodAttrs.bitstreamSwitching =
          new DataView(linearMemory.buffer).getUint8(0) === 0;
        break;
      case AttributeName.XLinkHref:
        periodAttrs.xlinkHref = parseString(textDecoder, linearMemory.buffer, ptr, len);
        break;
      case AttributeName.XLinkActuate:
        periodAttrs.xlinkActuate = parseString(
          textDecoder,
          linearMemory.buffer,
          ptr,
          len,
        );
        break;
      case AttributeName.AvailabilityTimeOffset:
        periodAttrs.availabilityTimeOffset = new DataView(linearMemory.buffer).getFloat64(
          ptr,
          true,
        );
        break;
      case AttributeName.AvailabilityTimeComplete:
        periodAttrs.availabilityTimeComplete =
          new DataView(linearMemory.buffer).getUint8(0) === 0;
        break;
      case AttributeName.Namespace: {
        const xmlNs = { key: "", value: "" };
        const dataView = new DataView(linearMemory.buffer);
        let offset = ptr;
        const keySize = dataView.getUint32(offset);
        offset += 4;

        xmlNs.key = parseString(textDecoder, linearMemory.buffer, offset, keySize);
        offset += keySize;

        const valSize = dataView.getUint32(offset);
        offset += 4;
        xmlNs.value = parseString(textDecoder, linearMemory.buffer, offset, valSize);

        if (periodAttrs.namespaces === undefined) {
          periodAttrs.namespaces = [xmlNs];
        } else {
          periodAttrs.namespaces.push(xmlNs);
        }
        break;
      }
    }
  };
}
