/*
 * Copyright (C) 2019 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.android.media.benchmark.tests;

import com.android.media.benchmark.R;
import com.android.media.benchmark.library.Extractor;
import com.android.media.benchmark.library.Muxer;
import com.android.media.benchmark.library.Native;
import com.android.media.benchmark.library.Stats;

import androidx.test.platform.app.InstrumentationRegistry;

import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;

import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

import static org.junit.Assert.assertTrue;

@RunWith(Parameterized.class)
public class MuxerTest {
    private static Context mContext =
            InstrumentationRegistry.getInstrumentation().getTargetContext();
    private static final String mInputFilePath = mContext.getString(R.string.input_file_path);
    private static final String mStatsFile =
            mContext.getExternalFilesDir(null) + "/Muxer." + System.currentTimeMillis() + ".csv";
    private static final String TAG = "MuxerTest";
    private static final Map<String, Integer> mMapFormat = Map.of(
            "mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
            "webm", MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM,
            "3gpp", MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP,
            "ogg", MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG);

    private String mInputFileName;
    private String mFormat;

    @Parameterized.Parameters
    public static Collection<Object[]> inputFiles() {
        return Arrays.asList(new Object[][]{
                /* Parameters: filename, format */
                {"crowd_1920x1080_25fps_4000kbps_vp8.webm", "webm"},
                {"crowd_1920x1080_25fps_4000kbps_vp9.webm", "webm"},
                {"crowd_1920x1080_25fps_6000kbps_mpeg4.mp4", "mp4"},
                {"crowd_352x288_25fps_6000kbps_h263.3gp", "mp4"},
                {"crowd_1920x1080_25fps_6700kbps_h264.ts", "mp4"},
                {"crowd_1920x1080_25fps_4000kbps_h265.mkv", "mp4"},
                {"crowd_1920x1080_25fps_6000kbps_mpeg4.mp4", "3gpp"},
                {"crowd_352x288_25fps_6000kbps_h263.3gp", "3gpp"},
                {"crowd_1920x1080_25fps_6700kbps_h264.ts", "3gpp"},
                {"crowd_1920x1080_25fps_4000kbps_h265.mkv", "3gpp"},
                {"bbb_48000hz_2ch_100kbps_opus_5mins.webm", "ogg"},
                {"bbb_44100hz_2ch_80kbps_vorbis_5mins.webm", "webm"},
                {"bbb_48000hz_2ch_100kbps_opus_5mins.webm", "webm"},
                {"bbb_44100hz_2ch_128kbps_aac_5mins.mp4", "mp4"},
                {"bbb_8000hz_1ch_8kbps_amrnb_5mins.3gp", "mp4"},
                {"bbb_16000hz_1ch_9kbps_amrwb_5mins.3gp", "mp4"},
                {"bbb_44100hz_2ch_128kbps_aac_5mins.mp4", "3gpp"},
                {"bbb_8000hz_1ch_8kbps_amrnb_5mins.3gp", "3gpp"},
                {"bbb_16000hz_1ch_9kbps_amrwb_5mins.3gp", "3gpp"}});
    }

    public MuxerTest(String filename, String outputFormat) {
        this.mInputFileName = filename;
        this.mFormat = outputFormat;
    }

    @BeforeClass
    public static void writeStatsHeaderToFile() throws IOException {
        Stats mStats = new Stats();
        boolean status = mStats.writeStatsHeader(mStatsFile);
        assertTrue("Unable to open stats file for writing!", status);
        Log.d(TAG, "Saving Benchmark results in: " + mStatsFile);
    }

    @Test
    public void testMuxer() throws IOException {
        File inputFile = new File(mInputFilePath + mInputFileName);
        assertTrue("Cannot find " + mInputFileName + " in directory " + mInputFilePath,
                inputFile.exists());
        FileInputStream fileInput = new FileInputStream(inputFile);
        FileDescriptor fileDescriptor = fileInput.getFD();
        ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
        ArrayList<MediaCodec.BufferInfo> inputBufferInfo = new ArrayList<>();
        Extractor extractor = new Extractor();
        int trackCount = extractor.setUpExtractor(fileDescriptor);
        for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
            extractor.selectExtractorTrack(currentTrack);
            while (true) {
                int sampleSize = extractor.getFrameSample();
                MediaCodec.BufferInfo bufferInfo = extractor.getBufferInfo();
                MediaCodec.BufferInfo tempBufferInfo = new MediaCodec.BufferInfo();
                tempBufferInfo
                        .set(bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs,
                                bufferInfo.flags);
                inputBufferInfo.add(tempBufferInfo);
                ByteBuffer tempSampleBuffer = ByteBuffer.allocate(tempBufferInfo.size);
                tempSampleBuffer.put(extractor.getFrameBuffer().array(), 0, bufferInfo.size);
                inputBuffer.add(tempSampleBuffer);
                if (sampleSize < 0) {
                    break;
                }
            }
            MediaFormat format = extractor.getFormat(currentTrack);
            int outputFormat = mMapFormat.getOrDefault(mFormat, -1);
            assertNotEquals("Test failed for " + mInputFileName + ". Returned invalid " +
                    "output format for given " + mFormat + " format.", -1, outputFormat);
            Muxer muxer = new Muxer();
            int trackIndex = muxer.setUpMuxer(mContext, outputFormat, format);
            int status = muxer.mux(trackIndex, inputBuffer, inputBufferInfo);
            assertEquals("Cannot perform write operation for " + mInputFileName, 0, status);
            Log.i(TAG, "Muxed " + mInputFileName + " successfully.");
            muxer.deInitMuxer();
            muxer.dumpStatistics(mInputFileName, mFormat, extractor.getClipDuration(), mStatsFile);
            muxer.resetMuxer();
            extractor.unselectExtractorTrack(currentTrack);
            inputBufferInfo.clear();
            inputBuffer.clear();

        }
        extractor.deinitExtractor();
        fileInput.close();
    }

    @Test
    public void testNativeMuxer() {
        Native nativeMuxer = new Native();
        File inputFile = new File(mInputFilePath + mInputFileName);
        assertTrue("Cannot find " + mInputFileName + " in directory " + mInputFilePath,
                inputFile.exists());
        int tid = android.os.Process.myTid();
        String mMuxOutputFile = (mContext.getFilesDir() + "/mux_" + tid + ".out");
        int status = nativeMuxer.Mux(
                mInputFilePath, mInputFileName, mMuxOutputFile, mStatsFile, mFormat);
        assertEquals("Cannot perform write operation for " + mInputFileName, 0, status);
        Log.i(TAG, "Muxed " + mInputFileName + " successfully.");
        File muxedFile = new File(mMuxOutputFile);
        // Cleanup temporary output file
        if (muxedFile.exists()) {
            assertTrue("Unable to delete" + mMuxOutputFile + " file.",
                    muxedFile.delete());
        }
    }
}
