/*
 * 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 com.google.android.exoplayer2.testutil;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import android.content.Context;
import android.util.SparseArray;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.SeekMap;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * A fake {@link ExtractorOutput}.
 */
public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpable {

  /**
   * If true, makes {@link #assertOutput(Context, String)} method write the output to the dump file,
   * rather than validating that the output matches what the dump file already contains.
   *
   * <p>Enabling this option works when tests are run in Android Studio. It may not work when the
   * tests are run in another environment.
   */
  private static final boolean WRITE_DUMP = false;

  public final SparseArray<FakeTrackOutput> trackOutputs;

  public int numberOfTracks;
  public boolean tracksEnded;
  public SeekMap seekMap;

  public FakeExtractorOutput() {
    trackOutputs = new SparseArray<>();
  }

  @Override
  public FakeTrackOutput track(int id, int type) {
    FakeTrackOutput output = trackOutputs.get(id);
    if (output == null) {
      assertThat(tracksEnded).isFalse();
      numberOfTracks++;
      output = new FakeTrackOutput();
      trackOutputs.put(id, output);
    }
    return output;
  }

  @Override
  public void endTracks() {
    tracksEnded = true;
  }

  @Override
  public void seekMap(SeekMap seekMap) {
    this.seekMap = seekMap;
  }

  public void clearTrackOutputs() {
    for (int i = 0; i < numberOfTracks; i++) {
      trackOutputs.valueAt(i).clear();
    }
  }

  public void assertEquals(FakeExtractorOutput expected) {
    assertThat(numberOfTracks).isEqualTo(expected.numberOfTracks);
    assertThat(tracksEnded).isEqualTo(expected.tracksEnded);
    if (expected.seekMap == null) {
      assertThat(seekMap).isNull();
    } else {
      // TODO: Bulk up this check if possible.
      assertThat(seekMap).isNotNull();
      assertThat(seekMap.getClass()).isEqualTo(expected.seekMap.getClass());
      assertThat(seekMap.isSeekable()).isEqualTo(expected.seekMap.isSeekable());
      assertThat(seekMap.getSeekPoints(0)).isEqualTo(expected.seekMap.getSeekPoints(0));
    }
    for (int i = 0; i < numberOfTracks; i++) {
      assertThat(trackOutputs.keyAt(i)).isEqualTo(expected.trackOutputs.keyAt(i));
      trackOutputs.valueAt(i).assertEquals(expected.trackOutputs.valueAt(i));
    }
  }

  /**
   * Asserts that dump of this {@link FakeExtractorOutput} is equal to expected dump which is read
   * from {@code dumpFile}.
   *
   * <p>If assertion fails because of an intended change in the output or a new dump file needs to
   * be created, set {@link #WRITE_DUMP} flag to true and run the test again. Instead of assertion,
   * actual dump will be written to {@code dumpFile}. This new dump file needs to be copied to the
   * project, {@code library/src/androidTest/assets} folder manually.
   */
  public void assertOutput(Context context, String dumpFile) throws IOException {
    String actual = new Dumper().add(this).toString();

    if (WRITE_DUMP) {
      File file = new File(System.getProperty("user.dir"), "src/test/assets");
      file = new File(file, dumpFile);
      file.getParentFile().mkdirs();
      PrintWriter out = new PrintWriter(file);
      out.print(actual);
      out.close();
    } else {
      String expected = TestUtil.getString(context, dumpFile);
      assertWithMessage(dumpFile).that(actual).isEqualTo(expected);
    }
  }

  @Override
  public void dump(Dumper dumper) {
    if (seekMap != null) {
      dumper
          .startBlock("seekMap")
          .add("isSeekable", seekMap.isSeekable())
          .addTime("duration", seekMap.getDurationUs())
          .add("getPosition(0)", seekMap.getSeekPoints(0))
          .endBlock();
    }
    dumper.add("numberOfTracks", numberOfTracks);
    for (int i = 0; i < numberOfTracks; i++) {
      dumper.startBlock("track " + trackOutputs.keyAt(i))
          .add(trackOutputs.valueAt(i))
          .endBlock();
    }
    dumper.add("tracksEnded", tracksEnded);
  }

}

@@ -847,16 +847,56 @@
 eArray;%0A
+import com.google.android.exoplayer2.C;%0A
 import c
@@ -2264,24 +2264,203 @@
  seekMap) %7B%0A
+    if (seekMap.isSeekable() && seekMap.getDurationUs() == C.TIME_UNSET) %7B%0A      throw new IllegalStateException(%22SeekMap cannot be seekable and have an unknown duration%22);%0A    %7D%0A
     this.see

Add an additional sanity check to FakeExtractorOutput