/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.
 */
package androidx.media3.extractor.text.webvtt;

import static androidx.media3.extractor.text.webvtt.WebvttCssParser.parseNextToken;
import static com.google.common.truth.Truth.assertThat;

import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.Util;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/** Unit test for {@link WebvttCssParser}. */
@RunWith(AndroidJUnit4.class)
public final class WebvttCssParserTest {

  private WebvttCssParser parser;

  @Before
  public void setUp() {
    parser = new WebvttCssParser();
  }

  @Test
  public void skipWhitespacesAndComments() {
    // Skip only whitespaces
    String skipOnlyWhitespaces = " \t\r\n\f End of skip\n /*  */";
    assertSkipsToEndOfSkip("End of skip", skipOnlyWhitespaces);

    // Skip only comments.
    String skipOnlyComments = "/*A comment***//*/It even has spaces in it*/End of skip";
    assertSkipsToEndOfSkip("End of skip", skipOnlyComments);

    // Skip interleaved.
    String skipInterleaved = " /* We have comments and */\t\n/* whitespaces*/ End of skip";
    assertSkipsToEndOfSkip("End of skip", skipInterleaved);

    // Skip nothing.
    String skipNothing = "End of skip\n \t \r";
    assertSkipsToEndOfSkip("End of skip", skipNothing);

    // Skip everything.
    String skipEverything = "\t/* Comment */\n\r/* And another */";
    assertSkipsToEndOfSkip(null, skipEverything);
  }

  @Test
  public void getInputLimit() {
    // \r After 3 lines.
    String threeLinesThen3Cr = "One Line\nThen other\rAnd finally\r\r\r";
    assertInputLimit("", threeLinesThen3Cr);

    // \r\r After 3 lines
    String threeLinesThen2Cr = "One Line\nThen other\r\nAnd finally\r\r";
    assertInputLimit(null, threeLinesThen2Cr);

    // \n\n After 3 lines.
    String threeLinesThen2Lf = "One Line\nThen other\r\nAnd finally\n\nFinal\n\n\nLine";
    assertInputLimit("Final", threeLinesThen2Lf);

    // \r\n\n After 3 lines.
    String threeLinesThenCr2Lf = " \n \r\n \r\n\nFinal\n\n\nLine";
    assertInputLimit("Final", threeLinesThenCr2Lf);

    // Limit immediately.
    String immediateEmptyLine = "\nLine\nEnd";
    assertInputLimit("Line", immediateEmptyLine);

    // Empty string.
    assertInputLimit(null, "");
  }

  @Test
  public void parseMethodSimpleInput() {
    WebvttCssStyle expectedStyle = new WebvttCssStyle();
    String styleBlock1 = " ::cue { color : black; background-color: PapayaWhip }";
    expectedStyle.setFontColor(0xFF000000);
    expectedStyle.setBackgroundColor(0xFFFFEFD5);
    assertParserProduces(styleBlock1, expectedStyle);

    String styleBlock2 = " ::cue { color : black }\n\n::cue { color : invalid }";
    expectedStyle = new WebvttCssStyle();
    expectedStyle.setFontColor(0xFF000000);
    assertParserProduces(styleBlock2, expectedStyle);

    String styleBlock3 = "::cue {\n background-color\n:#00fFFe}";
    expectedStyle = new WebvttCssStyle();
    expectedStyle.setBackgroundColor(0xFF00FFFE);
    assertParserProduces(styleBlock3, expectedStyle);
  }

  @Test
  public void parseMethodMultipleRulesInBlockInput() {
    String styleBlock =
        "::cue {\n background-color\n:#00fFFe}      \n::cue {\n background-color\n:#00000000}\n";
    WebvttCssStyle expectedStyle = new WebvttCssStyle();
    expectedStyle.setBackgroundColor(0xFF00FFFE);
    WebvttCssStyle secondExpectedStyle = new WebvttCssStyle();
    secondExpectedStyle.setBackgroundColor(0x000000);
    assertParserProduces(styleBlock, expectedStyle, secondExpectedStyle);
  }

  @Test
  public void multiplePropertiesInBlock() {
    String styleBlock =
        "::cue(#id){text-decoration:underline; background-color:green;"
            + "color:red; font-family:Courier; font-weight:bold}";
    WebvttCssStyle expectedStyle = new WebvttCssStyle();
    expectedStyle.setTargetId("id");
    expectedStyle.setUnderline(true);
    expectedStyle.setBackgroundColor(0xFF008000);
    expectedStyle.setFontColor(0xFFFF0000);
    expectedStyle.setFontFamily("courier");
    expectedStyle.setBold(true);

    assertParserProduces(styleBlock, expectedStyle);
  }

  @Test
  public void rgbaColorExpression() {
    String styleBlock =
        "::cue(#rgb){background-color: rgba(\n10/* Ugly color */,11\t, 12\n,.1);"
            + "color:rgb(1,1,\n1)}";
    WebvttCssStyle expectedStyle = new WebvttCssStyle();
    expectedStyle.setTargetId("rgb");
    expectedStyle.setBackgroundColor(0x190A0B0C);
    expectedStyle.setFontColor(0xFF010101);

    assertParserProduces(styleBlock, expectedStyle);
  }

  @Test
  public void getNextToken() {
    String stringInput = " lorem:ipsum\n{dolor}#sit,amet;lorem:ipsum\r\t\f\ndolor(())\n";
    ParsableByteArray input = new ParsableByteArray(Util.getUtf8Bytes(stringInput));
    StringBuilder builder = new StringBuilder();
    assertThat(parseNextToken(input, builder)).isEqualTo("lorem");
    assertThat(parseNextToken(input, builder)).isEqualTo(":");
    assertThat(parseNextToken(input, builder)).isEqualTo("ipsum");
    assertThat(parseNextToken(input, builder)).isEqualTo("{");
    assertThat(parseNextToken(input, builder)).isEqualTo("dolor");
    assertThat(parseNextToken(input, builder)).isEqualTo("}");
    assertThat(parseNextToken(input, builder)).isEqualTo("#sit");
    assertThat(parseNextToken(input, builder)).isEqualTo(",");
    assertThat(parseNextToken(input, builder)).isEqualTo("amet");
    assertThat(parseNextToken(input, builder)).isEqualTo(";");
    assertThat(parseNextToken(input, builder)).isEqualTo("lorem");
    assertThat(parseNextToken(input, builder)).isEqualTo(":");
    assertThat(parseNextToken(input, builder)).isEqualTo("ipsum");
    assertThat(parseNextToken(input, builder)).isEqualTo("dolor");
    assertThat(parseNextToken(input, builder)).isEqualTo("(");
    assertThat(parseNextToken(input, builder)).isEqualTo("(");
    assertThat(parseNextToken(input, builder)).isEqualTo(")");
    assertThat(parseNextToken(input, builder)).isEqualTo(")");
    assertThat(parseNextToken(input, builder)).isNull();
  }

  @Test
  public void styleScoreSystem() {
    WebvttCssStyle style = new WebvttCssStyle();
    // Universal selector.
    assertThat(style.getSpecificityScore("", "", Collections.emptySet(), "")).isEqualTo(1);
    // Class match without tag match.
    style.setTargetClasses(new String[] {"class1", "class2"});
    assertThat(
            style.getSpecificityScore(
                "", "", new HashSet<>(Arrays.asList("class1", "class2", "class3")), ""))
        .isEqualTo(8);
    // Class and tag match
    style.setTargetTagName("b");
    assertThat(
            style.getSpecificityScore(
                "", "b", new HashSet<>(Arrays.asList("class1", "class2", "class3")), ""))
        .isEqualTo(10);
    // Class insufficiency.
    assertThat(
            style.getSpecificityScore("", "b", new HashSet<>(Arrays.asList("class1", "class")), ""))
        .isEqualTo(0);
    // Voice, classes and tag match.
    style.setTargetVoice("Manuel Cráneo");
    assertThat(
            style.getSpecificityScore(
                "",
                "b",
                new HashSet<>(Arrays.asList("class1", "class2", "class3")),
                "Manuel Cráneo"))
        .isEqualTo(14);
    // Voice mismatch.
    assertThat(
            style.getSpecificityScore(
                null,
                "b",
                new HashSet<>(Arrays.asList("class1", "class2", "class3")),
                "Manuel Craneo"))
        .isEqualTo(0);
    // Id, voice, classes and tag match.
    style.setTargetId("id");
    assertThat(
            style.getSpecificityScore(
                "id",
                "b",
                new HashSet<>(Arrays.asList("class1", "class2", "class3")),
                "Manuel Cráneo"))
        .isEqualTo(0x40000000 + 14);
    // Id mismatch.
    assertThat(
            style.getSpecificityScore(
                "id1", "b", new HashSet<>(Arrays.asList("class1", "class2", "class3")), ""))
        .isEqualTo(0);
  }

  // Utility methods.

  private void assertSkipsToEndOfSkip(String expectedLine, String s) {
    ParsableByteArray input = new ParsableByteArray(Util.getUtf8Bytes(s));
    WebvttCssParser.skipWhitespaceAndComments(input);
    assertThat(input.readLine()).isEqualTo(expectedLine);
  }

  private void assertInputLimit(String expectedLine, String s) {
    ParsableByteArray input = new ParsableByteArray(Util.getUtf8Bytes(s));
    WebvttCssParser.skipStyleBlock(input);
    assertThat(input.readLine()).isEqualTo(expectedLine);
  }

  private void assertParserProduces(String styleBlock, WebvttCssStyle... expectedStyles) {
    ParsableByteArray input = new ParsableByteArray(Util.getUtf8Bytes(styleBlock));
    List<WebvttCssStyle> styles = parser.parseBlock(input);
    assertThat(styles.size()).isEqualTo(expectedStyles.length);
    for (int i = 0; i < expectedStyles.length; i++) {
      WebvttCssStyle expected = expectedStyles[i];
      WebvttCssStyle actualElem = styles.get(i);
      assertThat(actualElem.hasBackgroundColor()).isEqualTo(expected.hasBackgroundColor());
      if (expected.hasBackgroundColor()) {
        assertThat(actualElem.getBackgroundColor()).isEqualTo(expected.getBackgroundColor());
      }
      assertThat(actualElem.hasFontColor()).isEqualTo(expected.hasFontColor());
      if (expected.hasFontColor()) {
        assertThat(actualElem.getFontColor()).isEqualTo(expected.getFontColor());
      }
      assertThat(actualElem.getFontFamily()).isEqualTo(expected.getFontFamily());
      assertThat(actualElem.getFontSize()).isEqualTo(expected.getFontSize());
      assertThat(actualElem.getFontSizeUnit()).isEqualTo(expected.getFontSizeUnit());
      assertThat(actualElem.getStyle()).isEqualTo(expected.getStyle());
      assertThat(actualElem.isLinethrough()).isEqualTo(expected.isLinethrough());
      assertThat(actualElem.isUnderline()).isEqualTo(expected.isUnderline());
    }
  }
}
