import { enforce_nonnull } from "vscript";

export function strip_header(sdp: string): string {
  const i = sdp.search(/^\s*m\s*=/m);
  return i === -1 ? "" : sdp.substring(i).trim();
}

export function extract_header(sdp: string): string {
  const i = sdp.search(/^\s*m\s*=/m);
  return (i === -1 ? sdp : sdp.substring(0, i)).trim();
}

export function sdp_is_empty(sdp: string) {
  return split_sdp(sdp).media_descriptions.length === 0;
}

//type MediaType = "audio" | "video" | "text" | "application" | "message";
export enum MediaType {
  audio = "audio",
  video = "video",
  text = "text",
  application = "application",
  message = "message",
}

const get_media_type = (str: string) => {
  switch (str) {
    case "audio":
      return MediaType.audio;
    case "video":
      return MediaType.video;
    case "text":
      return MediaType.text;
    case "application":
      return MediaType.application;
    case "message":
      return MediaType.message;
    default:
      throw new Error(`Unexpected media type ${str}`);
  }
};

export interface MediaDescription {
  dst: string;
  port: number;
  src: string;
  text: string;
  media_type: MediaType;
  rtpmap: { payload: number; type: string };
  fmtp?: string;
  mid?: string;
  ptime?: string;
}

function split_media_description(text: string): MediaDescription {
  const attribute_mid = text.match(/^\s*a=mid:(.*)$/m);
  const mid = attribute_mid?.[1].trim();
  const [dst, src] = enforce_nonnull(
    text.match(/^\s*a\s*=\s*source-filter\s*:\s*incl IN IP4 (.+) (.+)\s*$/m),
  ).slice(1, 3);
  const fmtp = text.match(/^\s*a\s*=\s*fmtp\s*\:(.*)$/m)?.[1]?.trim();
  const rtpmap = enforce_nonnull(text.match(/^\s*a\s*=\s*rtpmap\s*:\s*([0-9]+)\s+(.*)$/m));

  const m = text.match(/^m=([a-z]+) ([0-9]+) /);
  const stream_type_str = enforce_nonnull(m?.[1]?.trim());
  const port = parseInt(enforce_nonnull(m?.[2]?.trim()), 10);
  const attribute_ptime = text.match(/^\s*a\s*=\s*ptime:([0-9]+\.*\d*)$/m)?.[1];
  return {
    dst: enforce_nonnull(dst),
    fmtp,
    mid,
    port,
    media_type: get_media_type(stream_type_str),
    rtpmap: {
      payload: parseInt(enforce_nonnull(rtpmap[1]), 10),
      type: enforce_nonnull(rtpmap[2]).trim(),
    },
    src: enforce_nonnull(src),
    text,
    ptime: attribute_ptime,
  };
}

export interface SplitSDP {
  header: string;
  media_descriptions: MediaDescription[];
}

export function split_sdp(sdp: string, opts?: { permit_missing_sourcefilter: boolean }): SplitSDP {
  const permit_missing_sourcefilter = opts?.permit_missing_sourcefilter ?? false;
  const header = extract_header(sdp);
  let rest = strip_header(sdp);
  const media_descriptions: MediaDescription[] = [];
  while (rest.length !== 0) {
    const m = enforce_nonnull(rest.match(/^(m=[.\s\S]+?)(\n\s*m=[.\s\S]*)?\s*$/));
    try {
      media_descriptions.push(split_media_description(m[1]));
    } catch (e) {
      if (!permit_missing_sourcefilter) throw e;
    }
    rest = (m[2] ?? "").trim();
  }
  return { header, media_descriptions };
}

export function assemble_sdp(sdp: SplitSDP): string {
  return `${sdp.header}\n${sdp.media_descriptions.map((md) => md.text).join("\n")}`;
}

export function merge_sdps(sdps: string[]): string {
  let result = sdps[0]?.trimRight() ?? "";
  for (let i = 1; i < sdps.length; ++i) {
    result += "\n" + strip_header(enforce_nonnull(sdps[i]));
  }
  return result;
}

export function sprinkle_fmtps(sdp: string): string {
  let state: "pick-up" | "drop" = "drop";
  let cur_fmtp = "";
  let result = "";
  const smpte291_rtpmap_re = /^\s*a\s*=\s*rtpmap:([0-9]+) .*smpte291\/90000/;
  for (const line of sdp.split(/\s*\n/)) {
    result += line.trim() + "\r\n";
    // relies on rtpmap preceding fmtp; not good but compatible with what our transmitter emits;
    // should replace string processing by actual SDP parsing
    if (line.match(/^\s*a\s*=\s*fmtp:/) && state === "pick-up") {
      cur_fmtp = line.trim();
    } else if (line.match(smpte291_rtpmap_re) && cur_fmtp.length !== 0) {
      const payload = enforce_nonnull(line.match(smpte291_rtpmap_re))[1];
      result += cur_fmtp.replace(/fmtp:([0-9]+) /, `fmtp:${payload} `) + "\r\n";
    } else if (line.match(/^\s*a\s*=\s*rtpmap/)) {
      state = "pick-up";
      cur_fmtp = "";
    }
  }
  return result;
}

export function filter_mid(sdp: string, mid: string): string {
  const parts = split_sdp(sdp);
  let result = parts.header;
  for (const md of parts.media_descriptions) {
    if (md.mid === mid) {
      result += "\n";
      result += md.text;
    }
  }
  return result;
}
