/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "MediaMIMETypes.h"
#include "gtest/gtest.h"

using namespace mozilla;

TEST(MediaMIMETypes, DependentMIMEType)
{
  static const struct {
    const char* mString;
    DependentMediaMIMEType mDependentMediaMIMEType;
  } tests[] = {{"audio/mp4", MEDIAMIMETYPE("audio/mp4")},
               {"video/mp4", MEDIAMIMETYPE("video/mp4")},
               {"application/x-mp4", MEDIAMIMETYPE("application/x-mp4")}};
  for (const auto& test : tests) {
    EXPECT_TRUE(test.mDependentMediaMIMEType.AsDependentString().EqualsASCII(
        test.mString));
    MediaMIMEType mimetype(test.mDependentMediaMIMEType);
    EXPECT_TRUE(mimetype.AsString().Equals(
        test.mDependentMediaMIMEType.AsDependentString()));
    EXPECT_EQ(mimetype, test.mDependentMediaMIMEType);
    EXPECT_EQ(mimetype, MediaMIMEType(test.mDependentMediaMIMEType));
  }
}

TEST(MediaMIMETypes, MakeMediaMIMEType_bad)
{
  static const char* tests[] = {"",       " ",   "/",    "audio",
                                "audio/", "mp4", "/mp4", "a/b"};

  for (const auto& test : tests) {
    Maybe<MediaMIMEType> type = MakeMediaMIMEType(test);
    EXPECT_TRUE(type.isNothing())
        << "MakeMediaMIMEType(\"" << test << "\").isNothing()";
  }
}

TEST(MediaMIMETypes, MediaMIMEType)
{
  static const struct {
    const char* mTypeString;
    const char* mAsString;
    bool mApplication;
    bool mAudio;
    bool mVideo;
    bool mEqualsLiteralVideoSlashMp4;  // tests `== "video/mp4"`
  } tests[] = {
      // in                    AsString         app    audio  video  ==v/mp4
      {"video/mp4", "video/mp4", false, false, true, true},
      {"video/mp4; codecs=0", "video/mp4", false, false, true, true},
      {"VIDEO/MP4", "video/mp4", false, false, true, true},
      {"audio/mp4", "audio/mp4", false, true, false, false},
      {"application/x", "application/x", true, false, false, false}};

  for (const auto& test : tests) {
    Maybe<MediaMIMEType> type = MakeMediaMIMEType(test.mTypeString);
    EXPECT_TRUE(type.isSome())
        << "MakeMediaMIMEType(\"" << test.mTypeString << "\").isSome()";
    EXPECT_TRUE(type->AsString().EqualsASCII(test.mAsString))
        << "MakeMediaMIMEType(\"" << test.mTypeString << "\")->AsString() == \""
        << test.mAsString << "\"";
    EXPECT_EQ(test.mApplication, type->HasApplicationMajorType())
        << "MakeMediaMIMEType(\"" << test.mTypeString
        << "\")->HasApplicationMajorType() == "
        << (test.mApplication ? "true" : "false");
    EXPECT_EQ(test.mAudio, type->HasAudioMajorType())
        << "MakeMediaMIMEType(\"" << test.mTypeString
        << "\")->HasAudioMajorType() == " << (test.mAudio ? "true" : "false");
    EXPECT_EQ(test.mVideo, type->HasVideoMajorType())
        << "MakeMediaMIMEType(\"" << test.mTypeString
        << "\")->HasVideoMajorType() == " << (test.mVideo ? "true" : "false");
    EXPECT_EQ(test.mEqualsLiteralVideoSlashMp4,
              *type == MEDIAMIMETYPE("video/mp4"))
        << "*MakeMediaMIMEType(\"" << test.mTypeString
        << "\") == MEDIAMIMETYPE(\"video/mp4\")";
  }
}

TEST(MediaMIMETypes, MediaCodecs)
{
  MediaCodecs empty("");
  EXPECT_TRUE(empty.IsEmpty());
  EXPECT_TRUE(empty.AsString().EqualsLiteral(""));
  EXPECT_FALSE(empty.Contains(u""_ns));
  EXPECT_FALSE(empty.Contains(u"c1"_ns));
  EXPECT_FALSE(empty.ContainsPrefix(u""_ns));
  EXPECT_FALSE(empty.ContainsPrefix(u"c1"_ns));
  int iterations = 0;
  for (const auto& codec : empty.Range()) {
    ++iterations;
    (void)codec;
  }
  EXPECT_EQ(0, iterations);

  MediaCodecs space(" ");
  EXPECT_FALSE(space.IsEmpty());
  EXPECT_TRUE(space.AsString().EqualsLiteral(" "));
  EXPECT_TRUE(space.Contains(u""_ns));
  EXPECT_FALSE(space.Contains(u"c1"_ns));
  EXPECT_TRUE(space.ContainsPrefix(u""_ns));
  EXPECT_FALSE(space.ContainsPrefix(u"c"_ns));
  EXPECT_FALSE(space.ContainsPrefix(u"c1"_ns));
  iterations = 0;
  for (const auto& codec : space.Range()) {
    ++iterations;
    EXPECT_TRUE(codec.IsEmpty());
  }
  EXPECT_EQ(1, iterations);

  MediaCodecs one(" c1 ");
  EXPECT_FALSE(one.IsEmpty());
  EXPECT_TRUE(one.AsString().EqualsLiteral(" c1 "));
  EXPECT_FALSE(one.Contains(u""_ns));
  EXPECT_TRUE(one.Contains(u"c1"_ns));
  EXPECT_TRUE(one.ContainsPrefix(u""_ns));
  EXPECT_TRUE(one.ContainsPrefix(u"c"_ns));
  EXPECT_TRUE(one.ContainsPrefix(u"c1"_ns));
  EXPECT_FALSE(one.ContainsPrefix(u"c1x"_ns));
  EXPECT_FALSE(one.ContainsPrefix(u"c1 "_ns));
  iterations = 0;
  for (const auto& codec : one.Range()) {
    ++iterations;
    EXPECT_TRUE(codec.EqualsLiteral("c1"));
  }
  EXPECT_EQ(1, iterations);

  MediaCodecs two(" c1 , c2 ");
  EXPECT_FALSE(two.IsEmpty());
  EXPECT_TRUE(two.AsString().EqualsLiteral(" c1 , c2 "));
  EXPECT_FALSE(two.Contains(u""_ns));
  EXPECT_TRUE(two.Contains(u"c1"_ns));
  EXPECT_TRUE(two.Contains(u"c2"_ns));
  EXPECT_TRUE(two.ContainsPrefix(u""_ns));
  EXPECT_TRUE(two.ContainsPrefix(u"c"_ns));
  EXPECT_FALSE(two.ContainsPrefix(u"1"_ns));
  EXPECT_TRUE(two.ContainsPrefix(u"c1"_ns));
  EXPECT_TRUE(two.ContainsPrefix(u"c2"_ns));
  EXPECT_FALSE(two.ContainsPrefix(u"c1x"_ns));
  EXPECT_FALSE(two.ContainsPrefix(u"c2x"_ns));
  iterations = 0;
  for (const auto& codec : two.Range()) {
    ++iterations;
    char buffer[] = "c0";
    buffer[1] += iterations;
    EXPECT_TRUE(codec.EqualsASCII(buffer));
  }
  EXPECT_EQ(2, iterations);

  EXPECT_TRUE(two.ContainsAll(two));
  EXPECT_TRUE(two.ContainsAll(one));
  EXPECT_FALSE(one.ContainsAll(two));

  // Check wide char case where both octets/bytes are relevant. Note we don't
  // use `EqualsLiteral` here because at the time of writing it will place the
  // literal into a narrow string which then doesn't compare correctly with
  // the wide representation from MediaCodecs.
  MediaCodecs euroSign(" € ");  // U+20AC
  EXPECT_FALSE(euroSign.IsEmpty());
  EXPECT_TRUE(euroSign.AsString().Equals(u" € "_ns));
  EXPECT_FALSE(euroSign.Contains(u""_ns));
  EXPECT_TRUE(euroSign.Contains(u"€"_ns));
  EXPECT_FALSE(euroSign.Contains(u"€€"_ns));
  EXPECT_TRUE(euroSign.ContainsPrefix(u""_ns));
  EXPECT_TRUE(euroSign.ContainsPrefix(u"€"_ns));
  EXPECT_FALSE(euroSign.ContainsPrefix(
      u"₭"_ns));  // U+20AD -- ensure second octet is compared
  EXPECT_FALSE(euroSign.ContainsPrefix(
      u"↬"_ns));  // U+21AC -- ensure first octet is compared
  EXPECT_FALSE(euroSign.ContainsPrefix(u"€ "_ns));
  iterations = 0;
  for (const auto& codec : euroSign.Range()) {
    ++iterations;
    EXPECT_TRUE(codec.Equals(u"€"_ns));
  }
  EXPECT_EQ(1, iterations);
}

TEST(MediaMIMETypes, MakeMediaExtendedMIMEType_bad)
{
  static const char* tests[] = {"",       " ",   "/",    "audio",
                                "audio/", "mp4", "/mp4", "a/b"};

  for (const auto& test : tests) {
    Maybe<MediaExtendedMIMEType> type = MakeMediaExtendedMIMEType(test);
    EXPECT_TRUE(type.isNothing())
        << "MakeMediaExtendedMIMEType(\"" << test << "\").isNothing()";
  }
}

TEST(MediaMIMETypes, MediaExtendedMIMEType)
{
  // Some generic tests first.
  static const struct {
    const char* mTypeString;
    const char* mTypeAsString;
    bool mApplication;
    bool mAudio;
    bool mVideo;
    bool mEqualsLiteralVideoSlashMp4;  // tests `== "video/mp4"`
    bool mHaveCodecs;
  } tests[] = {
      // in                    Type().AsString  app    audio  video ==v/mp4
      // codecs
      {"video/mp4", "video/mp4", false, false, true, true, false},
      {"video/mp4; codecs=0", "video/mp4", false, false, true, true, true},
      {"VIDEO/MP4", "video/mp4", false, false, true, true, false},
      {"audio/mp4", "audio/mp4", false, true, false, false, false},
      {"video/webm", "video/webm", false, false, true, false, false},
      {"audio/webm", "audio/webm", false, true, false, false, false},
      {"application/x", "application/x", true, false, false, false, false}};

  for (const auto& test : tests) {
    Maybe<MediaExtendedMIMEType> type =
        MakeMediaExtendedMIMEType(test.mTypeString);
    EXPECT_TRUE(type.isSome())
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString << "\").isSome()";
    EXPECT_TRUE(type->OriginalString().EqualsASCII(test.mTypeString))
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->AsString() == \"" << test.mTypeAsString << "\"";
    EXPECT_TRUE(type->Type().AsString().EqualsASCII(test.mTypeAsString))
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->AsString() == \"" << test.mTypeAsString << "\"";
    EXPECT_EQ(test.mApplication, type->Type().HasApplicationMajorType())
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->Type().HasApplicationMajorType() == "
        << (test.mApplication ? "true" : "false");
    EXPECT_EQ(test.mAudio, type->Type().HasAudioMajorType())
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->Type().HasAudioMajorType() == "
        << (test.mAudio ? "true" : "false");
    EXPECT_EQ(test.mVideo, type->Type().HasVideoMajorType())
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->Type().HasVideoMajorType() == "
        << (test.mVideo ? "true" : "false");
    EXPECT_EQ(test.mEqualsLiteralVideoSlashMp4,
              type->Type() == MEDIAMIMETYPE("video/mp4"))
        << "*MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->Type() == MEDIAMIMETYPE(\"video/mp4\")";
    EXPECT_EQ(test.mHaveCodecs, type->HaveCodecs())
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->HaveCodecs() == " << (test.mHaveCodecs ? "true" : "false");
    EXPECT_NE(test.mHaveCodecs, type->Codecs().IsEmpty())
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->Codecs.IsEmpty() != " << (test.mHaveCodecs ? "true" : "false");
    EXPECT_FALSE(type->GetWidth()) << "MakeMediaExtendedMIMEType(\""
                                   << test.mTypeString << "\")->GetWidth()";
    EXPECT_FALSE(type->GetHeight()) << "MakeMediaExtendedMIMEType(\""
                                    << test.mTypeString << "\")->GetHeight()";
    EXPECT_FALSE(type->GetFramerate())
        << "MakeMediaExtendedMIMEType(\"" << test.mTypeString
        << "\")->GetFramerate()";
    EXPECT_FALSE(type->GetBitrate()) << "MakeMediaExtendedMIMEType(\""
                                     << test.mTypeString << "\")->GetBitrate()";
  }

  // Test all extra parameters.
  Maybe<MediaExtendedMIMEType> type = MakeMediaExtendedMIMEType(
      "video/mp4; codecs=\"a,b\"; width=1024; Height=768; FrameRate=60; "
      "BITRATE=100000");
  EXPECT_TRUE(type->HaveCodecs());
  EXPECT_FALSE(type->Codecs().IsEmpty());
  EXPECT_TRUE(type->Codecs().AsString().EqualsASCII("a,b"));
  EXPECT_TRUE(type->Codecs() == "a,b");
  EXPECT_TRUE(type->Codecs().Contains(u"a"_ns));
  EXPECT_TRUE(type->Codecs().Contains(u"b"_ns));
  EXPECT_TRUE(type->Codecs().ContainsPrefix(u"a"_ns));
  EXPECT_TRUE(type->Codecs().ContainsPrefix(u"b"_ns));
  EXPECT_FALSE(type->Codecs().ContainsPrefix(u"ab"_ns));
  EXPECT_FALSE(type->Codecs().ContainsPrefix(u"ba"_ns));
  EXPECT_FALSE(type->Codecs().ContainsPrefix(u"a,b"_ns));
  EXPECT_TRUE(!!type->GetWidth());
  EXPECT_EQ(1024, *type->GetWidth());
  EXPECT_TRUE(!!type->GetHeight());
  EXPECT_EQ(768, *type->GetHeight());
  EXPECT_TRUE(!!type->GetFramerate());
  EXPECT_EQ(60, *type->GetFramerate());
  EXPECT_TRUE(!!type->GetBitrate());
  EXPECT_EQ(100000, *type->GetBitrate());
  EXPECT_EQ(5ul, type->GetParameterCount());

  // Test parameter count variations
  type = MakeMediaExtendedMIMEType("video/mp4");
  EXPECT_EQ(0ul, type->GetParameterCount());

  type = MakeMediaExtendedMIMEType("video/mp4; codecs=\"a,b\"");
  EXPECT_EQ(1ul, type->GetParameterCount());

  type = MakeMediaExtendedMIMEType("video/mp4; codecs=\"a,b\"; width=1024");
  EXPECT_EQ(2ul, type->GetParameterCount());

  type = MakeMediaExtendedMIMEType(
      "video/mp4; codecs=\"a,b\"; width=1024; Height=768");
  EXPECT_EQ(3ul, type->GetParameterCount());

  type = MakeMediaExtendedMIMEType(
      "video/mp4; codecs=\"a,b\"; width=1024; Height=768; FrameRate=60");
  EXPECT_EQ(4ul, type->GetParameterCount());

  // Test subtype extraction
  type = MakeMediaExtendedMIMEType("audio/mp3"_ns);
  EXPECT_EQ("mp3"_ns, type->Subtype());
  type = MakeMediaExtendedMIMEType("ViDeO/Mp4;codecs=\"a,b\""_ns);
  EXPECT_EQ("mp4"_ns, type->Subtype());
  type = MakeMediaExtendedMIMEType("video/av1    "_ns);
  EXPECT_EQ("av1"_ns, type->Subtype());
  type = MakeMediaExtendedMIMEType("video/vp8       ;codecs=\"a,b\""_ns);
  EXPECT_EQ("vp8"_ns, type->Subtype());
  // These strings should fail before subtype is available
  type = MakeMediaExtendedMIMEType("video/        av1    "_ns);
  EXPECT_EQ(Nothing(), type);
  type = MakeMediaExtendedMIMEType("/Something"_ns);
  EXPECT_EQ(Nothing(), type);
  type = MakeMediaExtendedMIMEType("ViDeO/h2字64;codecs=\"a,b\""_ns);
  EXPECT_EQ(Nothing(), type);
}
