package freenet.client.filter;

import static org.junit.Assert.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

import org.junit.Test;

import freenet.support.api.Bucket;
import freenet.support.io.ArrayBucket;
import freenet.support.io.BucketTools;

public class MP3FilterTest {
    private static final String RESOURCE_PATH = "mp3/";

    /** Known good files, should pass filter unaltered. */
    private static final String[] GOOD = {
        // MPEG ADTS, layer III, v2.5, 8 kbps, 8 kHz, Stereo
        "8khz-8kbps-cbr-stereo.mp3",
        // MPEG ADTS, layer III, v2.5, 48 kbps, 11.025 kHz, Stereo
        "11khz-48kbps-cbr-stereo.mp3",
        // MPEG ADTS, layer III, v2, 56 kbps, 16 kHz, Stereo
        "16khz-56kbps-cbr-stereo.mp3",
        // MPEG ADTS, layer III, v2, 96 kbps, 22.05 kHz, Stereo
        "22khz-96kbps-cbr-stereo.mp3",
        // MPEG ADTS, layer III, v1, 96 kbps, 32 kHz, Stereo
        "32khz-96kbps-cbr-stereo.mp3",
        // MPEG ADTS, layer III, v1, 96 kbps, 44.1 kHz, Stereo
        "44khz-96kbps-cbr-stereo.mp3",
        // MPEG ADTS, layer III, v1, 128 kbps, 48 kHz, Stereo
        "48khz-64kbps-vbr-stereo.mp3",
        // MPEG ADTS, layer III, v1, 128 kbps, 48 kHz, JntStereo
        "48khz-96kbps-vbr-joint.mp3",
        // MPEG ADTS, layer III, v1, 128 kbps, 48 kHz, Stereo
        "48khz-128kbps-cbr-stereo.mp3",
        // MPEG ADTS, layer III, v1, 320 kbps, 48 kHz, JntStereo
        "48khz-320kbps-cbr-joint.mp3"
    };

    /** Pairs of unfiltered file and their expected output file. */
    private static final String[][] FILTER_PAIRS = {
        // random + 48khz-96kbps-vbr-joint + random + 48khz-96kbps-vbr-joint + random
        // Random data is to be removed, leaving file with duplicate 48khz-96kbps-vbr-joint
        new String[] {
            "48khz-96kbps-vbr-joint-randompadding-unfiltered.mp3",
            "48khz-96kbps-vbr-joint-randompadding-expected.mp3"
        },
        // 48khz-128kbps-cbr-stereo with ID3v2 tags to be stripped
        new String[] {
            "48khz-128kbps-cbr-stereo-id3v2.mp3",
            "48khz-128kbps-cbr-stereo.mp3"
        }
    };

    @Test
    public void testKnownGood() {
        for (String good : GOOD) {
            assertEqualAfterFilter(good, good);
        }
    }

    @Test
    public void testFilterPairs() {
        for (String[] pair : FILTER_PAIRS) {
            assertEqualAfterFilter(pair[0], pair[1]);
        }
    }

    /**
     * Asserts that the test file in the first argument, after passing through the content filter,
     * is equal to the reference file in the second argument.
     *
     * @param fileUnfiltered  the test file
     * @param fileExpected    the reference file
     */
    private static void assertEqualAfterFilter(String fileUnfiltered, String fileExpected) {
        Bucket input = resourceToBucket(fileUnfiltered);
        Bucket expected = resourceToBucket(fileExpected);
        Bucket filtered = filterMP3(input);
        assertTrue("Filtered and expected output are not identical. " +
            "Input = " + fileUnfiltered + ", expected = " + fileExpected,
            equalBuckets(filtered, expected)
        );
    }

    /**
     * Checks for equality of Bucket contents.
     */
    private static boolean equalBuckets(Bucket a, Bucket b) {
        try {
            return Arrays.equals(BucketTools.toByteArray(a), BucketTools.toByteArray(b));
        } catch (IOException e) {
            throw new AssertionError(e);
        }
    }

    /**
     * Runs a Bucket through the content filter.
     *
     * @throws AssertionError on failure
     */
    private static Bucket filterMP3(Bucket input) {
        ContentDataFilter filter = new MP3Filter();
        Bucket output = new ArrayBucket();

        try (
            InputStream inStream = input.getInputStream();
            OutputStream outStream = output.getOutputStream()
        ) {
            filter.readFilter(inStream, outStream, "", null, null, null);
        } catch (Exception e) {
            throw new AssertionError("Unexpected exception in the content filter.", e);
        }

        return output;
    }

    /**
     * Loads a resource relative to the resource path into a Bucket.
     *
     * @throws AssertionError on failure
     */
    private static Bucket resourceToBucket(String filename) {
        try  {
            return ResourceFileUtil.resourceToBucket(RESOURCE_PATH + filename);
        } catch (IOException e) {
            throw new AssertionError(e);
        }
    }
}
