/**
 * 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 {
  IAdaptationSetAttributes,
  IAdaptationSetChildren,
  ISegmentListIntermediateRepresentation,
} from "../../../node_parser_types";
import type { IAttributeParser, IChildrenParser } from "../parsers_stack";
import type ParsersStack from "../parsers_stack";
import { AttributeName, TagName } from "../types";
import { parseFloatOrBool, parseString } from "../utils";
import { generateBaseUrlAttrParser } from "./BaseURL";
import { generateContentComponentAttrParser } from "./ContentComponent";
import { generateContentProtectionAttrParser } from "./ContentProtection";
import { generateLabelElementParser } from "./Label";
import {
  generateRepresentationAttrParser,
  generateRepresentationChildrenParser,
} from "./Representation";
import { generateSchemeAttrParser } from "./Scheme";
import { generateSegmentBaseAttrParser } from "./SegmentBase";
import { generateSegmentListChildrenParser } from "./SegmentList";
import { generateSegmentTemplateAttrParser } from "./SegmentTemplate";

/**
 * Generate a "children parser" once inside a `AdaptationSet` node.
 * @param {Object} adaptationSetChildren
 * @param {WebAssembly.Memory} linearMemory
 * @param {ParsersStack} parsersStack
 * @returns {Function}
 */
export function generateAdaptationSetChildrenParser(
  adaptationSetChildren: IAdaptationSetChildren,
  linearMemory: WebAssembly.Memory,
  parsersStack: ParsersStack,
): IChildrenParser {
  return function onRootChildren(nodeId: number) {
    switch (nodeId) {
      case TagName.Accessibility: {
        const accessibility = {};
        if (adaptationSetChildren.accessibilities === undefined) {
          adaptationSetChildren.accessibilities = [];
        }
        adaptationSetChildren.accessibilities.push(accessibility);
        const schemeAttrParser = generateSchemeAttrParser(accessibility, linearMemory);
        parsersStack.pushParsers(nodeId, noop, schemeAttrParser);
        break;
      }

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

      case TagName.ContentComponent: {
        const contentComponent = {};
        adaptationSetChildren.contentComponent = contentComponent;
        parsersStack.pushParsers(
          nodeId,
          noop,
          generateContentComponentAttrParser(contentComponent, linearMemory),
        );
        break;
      }

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

      case TagName.EssentialProperty: {
        const essentialProperty = {};
        if (adaptationSetChildren.essentialProperties === undefined) {
          adaptationSetChildren.essentialProperties = [];
        }
        adaptationSetChildren.essentialProperties.push(essentialProperty);

        const childrenParser = noop; // EssentialProperty have no sub-element
        const attributeParser = generateSchemeAttrParser(essentialProperty, linearMemory);
        parsersStack.pushParsers(nodeId, childrenParser, attributeParser);
        break;
      }

      case TagName.InbandEventStream: {
        const inbandEvent = {};
        if (adaptationSetChildren.inbandEventStreams === undefined) {
          adaptationSetChildren.inbandEventStreams = [];
        }
        adaptationSetChildren.inbandEventStreams.push(inbandEvent);

        const childrenParser = noop; // InbandEventStream have no sub-element
        const attributeParser = generateSchemeAttrParser(inbandEvent, linearMemory);
        parsersStack.pushParsers(nodeId, childrenParser, attributeParser);
        break;
      }

      case TagName.Representation: {
        const representationObj = {
          children: { baseURLs: [] },
          attributes: {},
        };
        adaptationSetChildren.representations.push(representationObj);
        const childrenParser = generateRepresentationChildrenParser(
          representationObj.children,
          linearMemory,
          parsersStack,
        );
        const attributeParser = generateRepresentationAttrParser(
          representationObj.attributes,
          linearMemory,
        );
        parsersStack.pushParsers(nodeId, childrenParser, attributeParser);
        break;
      }

      case TagName.Role: {
        const role = {};
        if (adaptationSetChildren.roles === undefined) {
          adaptationSetChildren.roles = [];
        }
        adaptationSetChildren.roles.push(role);
        const attributeParser = generateSchemeAttrParser(role, linearMemory);
        parsersStack.pushParsers(nodeId, noop, attributeParser);
        break;
      }

      case TagName.SupplementalProperty: {
        const supplementalProperty = {};
        if (adaptationSetChildren.supplementalProperties === undefined) {
          adaptationSetChildren.supplementalProperties = [];
        }
        adaptationSetChildren.supplementalProperties.push(supplementalProperty);
        const attributeParser = generateSchemeAttrParser(
          supplementalProperty,
          linearMemory,
        );
        parsersStack.pushParsers(nodeId, noop, attributeParser);
        break;
      }

      case TagName.SegmentBase: {
        const segmentBaseObj = {};
        adaptationSetChildren.segmentBase = segmentBaseObj;
        const attributeParser = generateSegmentBaseAttrParser(
          segmentBaseObj,
          linearMemory,
        );
        parsersStack.pushParsers(nodeId, noop, attributeParser);
        break;
      }

      case TagName.SegmentList: {
        const segmentListObj: ISegmentListIntermediateRepresentation = {
          list: [],
        };
        adaptationSetChildren.segmentList = segmentListObj;
        const childrenParser = generateSegmentListChildrenParser(
          segmentListObj,
          linearMemory,
          parsersStack,
        );

        // Re-use SegmentBase attribute parse as we should have the same attributes
        const attributeParser = generateSegmentBaseAttrParser(
          segmentListObj,
          linearMemory,
        );
        parsersStack.pushParsers(nodeId, childrenParser, attributeParser);
        break;
      }

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

      case TagName.Label: {
        parsersStack.pushParsers(
          nodeId,
          noop, // Label as treated like an attribute
          generateLabelElementParser(adaptationSetChildren, linearMemory),
        );
        break;
      }

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

/**
 * @param {Object} adaptationAttrs
 * @param {WebAssembly.Memory} linearMemory
 * @returns {Function}
 */
export function generateAdaptationSetAttrParser(
  adaptationAttrs: IAdaptationSetAttributes,
  linearMemory: WebAssembly.Memory,
): IAttributeParser {
  const textDecoder = new TextDecoder();
  return function onAdaptationSetAttribute(attr: number, ptr: number, len: number) {
    const dataView = new DataView(linearMemory.buffer);
    switch (attr) {
      case AttributeName.Id:
        adaptationAttrs.id = parseString(textDecoder, linearMemory.buffer, ptr, len);
        break;
      case AttributeName.Group:
        adaptationAttrs.group = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.Language:
        adaptationAttrs.language = parseString(
          textDecoder,
          linearMemory.buffer,
          ptr,
          len,
        );
        break;
      case AttributeName.ContentType:
        adaptationAttrs.contentType = parseString(
          textDecoder,
          linearMemory.buffer,
          ptr,
          len,
        );
        break;
      case AttributeName.Par:
        adaptationAttrs.par = parseString(textDecoder, linearMemory.buffer, ptr, len);
        break;
      case AttributeName.MinBandwidth:
        adaptationAttrs.minBitrate = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MaxBandwidth:
        adaptationAttrs.maxBitrate = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MinWidth:
        adaptationAttrs.minWidth = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MaxWidth:
        adaptationAttrs.maxWidth = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MinHeight:
        adaptationAttrs.minHeight = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MaxHeight:
        adaptationAttrs.maxHeight = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MinFrameRate:
        adaptationAttrs.minFrameRate = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MaxFrameRate:
        adaptationAttrs.maxFrameRate = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.SelectionPriority:
        adaptationAttrs.selectionPriority = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.SegmentAlignment:
        adaptationAttrs.segmentAlignment = parseFloatOrBool(
          dataView.getFloat64(ptr, true),
        );
        break;
      case AttributeName.SubsegmentAlignment:
        adaptationAttrs.subsegmentAlignment = parseFloatOrBool(
          dataView.getFloat64(ptr, true),
        );
        break;
      case AttributeName.BitstreamSwitching:
        adaptationAttrs.bitstreamSwitching = dataView.getFloat64(ptr, true) !== 0;
        break;
      case AttributeName.AudioSamplingRate:
        adaptationAttrs.audioSamplingRate = parseString(
          textDecoder,
          linearMemory.buffer,
          ptr,
          len,
        );
        break;
      case AttributeName.Codecs:
        adaptationAttrs.codecs = parseString(textDecoder, linearMemory.buffer, ptr, len);
        break;
      case AttributeName.SupplementalCodecs:
        adaptationAttrs.supplementalCodecs = parseString(
          textDecoder,
          linearMemory.buffer,
          ptr,
          len,
        );
        break;
      case AttributeName.Profiles:
        adaptationAttrs.profiles = parseString(
          textDecoder,
          linearMemory.buffer,
          ptr,
          len,
        );
        break;
      case AttributeName.SegmentProfiles:
        adaptationAttrs.segmentProfiles = parseString(
          textDecoder,
          linearMemory.buffer,
          ptr,
          len,
        );
        break;
      case AttributeName.MimeType:
        adaptationAttrs.mimeType = parseString(
          textDecoder,
          linearMemory.buffer,
          ptr,
          len,
        );
        break;
      case AttributeName.CodingDependency:
        adaptationAttrs.codingDependency = dataView.getFloat64(ptr, true) !== 0;
        break;
      case AttributeName.FrameRate:
        adaptationAttrs.frameRate = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.Height:
        adaptationAttrs.height = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.Width:
        adaptationAttrs.width = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MaxPlayoutRate:
        adaptationAttrs.maxPlayoutRate = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.MaxSAPPeriod:
        adaptationAttrs.maximumSAPPeriod = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.AvailabilityTimeOffset:
        adaptationAttrs.availabilityTimeOffset = dataView.getFloat64(ptr, true);
        break;
      case AttributeName.AvailabilityTimeComplete:
        adaptationAttrs.availabilityTimeComplete = dataView.getUint8(0) === 0;
        break;

      // TODO
      // case AttributeName.StartsWithSap:
      //   adaptationAttrs.startsWithSap = dataView.getFloat64(ptr, true);
    }
  };
}
