package cn.i2edu.speech_plugin.audioUtils.audiotransfer;

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

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import cn.i2edu.speech_plugin.audioUtils.audiotransfer.vavi.sound.pcm.resampling.ssrc.SSRC;


/**
 * Created by 郑童宇 on 2016/03/04.
 */
public class DecodeEngine {
    private static final String TAG = DecodeEngine.class.getName();
    private static DecodeEngine instance;
    private DecodeEngine() {
    }

    public static DecodeEngine getInstance(Context context) {
        if (instance == null) {
            synchronized (DecodeEngine.class) {
                if (instance == null) {
                    instance = new DecodeEngine();
                }
            }
        }

        return instance;
    }

    public void beginDecodeMusicFile(String musicFileUrl, String decodeFileUrl, int startSecond,
                                     int desireSampleRate,
                                     int desireChannel,
                                     int desireBitwidth,
                                     final DecodeOperateInterface decodeOperateInterface) {

        final boolean decodeResult =
                decodeMusicFile(musicFileUrl, decodeFileUrl, startSecond, desireSampleRate, desireChannel, desireBitwidth,
                        decodeOperateInterface);

//        Handler handler = new Handler(Looper.getMainLooper());
//        handler.post(new Runnable() {
//            @Override
//            public void run() {
                if (decodeResult) {
                    decodeOperateInterface.decodeSuccess();
                } else {
                    decodeOperateInterface.decodeFail();
                }
                // 手动gc一下
                System.gc();
//            }
//        });
    }

    private boolean decodeMusicFile(String musicFileUrl, String decodeFileUrl, int startSecond,
                                    int desireSampleRate,
                                    int desireChannel,
                                    int desireBitwidth,
//                                    Handler handler,
                                    DecodeOperateInterface decodeOperateInterface) {
        int sampleRate;
        int channelCount;

        long duration;

        String mime;

        MediaExtractor mediaExtractor = new MediaExtractor();
        MediaFormat mediaFormat;
        MediaCodec mediaCodec;

        try {
            mediaExtractor.setDataSource(musicFileUrl);
        } catch (IOException e) {
            LogFunction.error("设置解码音频文件路径错误", e);
//            CustomBugReporter.reportError(DecodeEngine.class.getName(), "set decode audio file path error", e);
            return false;
        }

        mediaFormat = mediaExtractor.getTrackFormat(0);
        sampleRate = mediaFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ?
                mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) : 44100;
        channelCount = mediaFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ?
                mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1;
        duration = mediaFormat.containsKey(MediaFormat.KEY_DURATION) ? mediaFormat.getLong
                (MediaFormat.KEY_DURATION)
                : 0;
        mime = mediaFormat.containsKey(MediaFormat.KEY_MIME) ? mediaFormat.getString(MediaFormat
                .KEY_MIME) : "";

        LogFunction.log("歌曲信息",
                "Track info: mime:" + mime + " 采样率sampleRate:" + sampleRate + " channels:" +
                        channelCount + " duration:" + duration);
        Log.d("audio_info", "Track info: mime:" + mime + " 采样率sampleRate:" + sampleRate + " channels:" +
                channelCount + " duration:" + duration);
        if (CommonFunction.isEmpty(mime) || !mime.startsWith("audio/")) {
            LogFunction.error("解码文件不是音频文件", "mime:" + mime);
            return false;
        }

        if (mime.equals("audio/ffmpeg")) {
            mime = "audio/mpeg";
            mediaFormat.setString(MediaFormat.KEY_MIME, mime);
        }

        try {
            mediaCodec = MediaCodec.createDecoderByType(mime);

            mediaCodec.configure(mediaFormat, null, null, 0);
            mediaCodec.start();
        } catch (Exception e) {
            LogFunction.error("解码器configure出错", e);
//            CustomBugReporter.reportError(DecodeEngine.class.getName(), "configure decoder error", e);
            return false;
        }

        getDecodeData(mediaExtractor,
                mediaCodec,
                decodeFileUrl,
                sampleRate,
                channelCount,
                startSecond,
                desireSampleRate,
                desireChannel,
                desireBitwidth,
//                handler,
                decodeOperateInterface);
        return true;
    }

    private void getDecodeData(MediaExtractor mediaExtractor,
                               MediaCodec mediaCodec,
                               String decodeFileUrl,
                               int sampleRate,
                               int channelCount,
                               int startSecond,
                               int desireSampleRate,
                               int desireChannel,
                               int desireBitwidth,
//                               Handler handler,
                               final DecodeOperateInterface decodeOperateInterface) {
        boolean decodeInputEnd = false;
        boolean decodeOutputEnd = false;

        int sampleDataSize;
        int inputBufferIndex;
        int outputBufferIndex;
        int byteNumber;

        long decodeNoticeTime = System.currentTimeMillis();
        long decodeTime;
        long presentationTimeUs = 0;

        final long timeOutUs = 100;
//        final long startMicroseconds = startSecond * 1000 * 1000;
//        final long endMicroseconds = endSecond * 1000 * 1000;
        final long startMicroseconds = startSecond * 1000;
//        final long endMicroseconds = endSecond * 1000;

        ByteBuffer[] inputBuffers;
        ByteBuffer[] outputBuffers;

        ByteBuffer sourceBuffer;
        ByteBuffer targetBuffer;

//        MediaFormat outputFormat = mediaCodec.getOutputFormat();
        MediaFormat outputFormat;

        MediaCodec.BufferInfo bufferInfo;

//        byteNumber =
//                (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") :
//                        0) / 8;
        byteNumber = 2;



        inputBuffers = mediaCodec.getInputBuffers();
        outputBuffers = mediaCodec.getOutputBuffers();

        mediaExtractor.selectTrack(0);

        bufferInfo = new MediaCodec.BufferInfo();

        BufferedOutputStream bufferedOutputStream = FileFunction
                .GetBufferedOutputStreamFromFile(decodeFileUrl);

        while (!decodeOutputEnd) {
            if (decodeInputEnd) {
                break;
            }

//            decodeTime = System.currentTimeMillis();

//            if (decodeTime - decodeNoticeTime > Constant.OneSecond) {
//                final int decodeProgress =
//                        (int) ((presentationTimeUs - startMicroseconds) * Constant
//                                .NormalMaxProgress /
//                                endMicroseconds);
//
//                if (decodeProgress > 0) {
//                    handler.post(new Runnable() {
//                        @Override
//                        public void run() {
//                            decodeOperateInterface.updateDecodeProgress(decodeProgress);
//                        }
//                    });
//                }
//
//                decodeNoticeTime = decodeTime;
//            }

            try {
                inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs);

                if (inputBufferIndex >= 0) {
                    sourceBuffer = inputBuffers[inputBufferIndex];

                    sampleDataSize = mediaExtractor.readSampleData(sourceBuffer, 0);

                    if (sampleDataSize < 0) {
                        decodeInputEnd = true;
                        sampleDataSize = 0;
                    } else {
                        presentationTimeUs = mediaExtractor.getSampleTime();
                    }

                    mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleDataSize,
                            presentationTimeUs,
                            decodeInputEnd ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);

                    if (!decodeInputEnd) {
                        mediaExtractor.advance();
                    }
                } else {
                    LogFunction.error("inputBufferIndex", "" + inputBufferIndex);
                }

                // decode to PCM and push it to the AudioTrack player
                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeOutUs);

                if (outputBufferIndex < 0) {
                    switch (outputBufferIndex) {
                        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                            outputBuffers = mediaCodec.getOutputBuffers();
                            LogFunction.error("MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED",
                                    "[AudioDecoder]output buffers have changed.");
                            break;
                        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                            outputFormat = mediaCodec.getOutputFormat();

                            sampleRate = outputFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ?
                                    outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) :
                                    sampleRate;
                            channelCount = outputFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ?
                                    outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) :
                                    channelCount;
                            byteNumber = (outputFormat.containsKey("bit-width") ? outputFormat
                                    .getInteger
                                            ("bit-width") : 16) / 8;

                            LogFunction.error("MediaCodec.INFO_OUTPUT_FORMAT_CHANGED",
                                    "[AudioDecoder]output format has changed to " +
                                            mediaCodec.getOutputFormat());
                            break;
                        default:
                            LogFunction.error("error",
                                    "[AudioDecoder] dequeueOutputBuffer returned " +
                                            outputBufferIndex);
                            break;
                    }
                    continue;
                }

                targetBuffer = outputBuffers[outputBufferIndex];

                byte[] sourceByteArray = new byte[bufferInfo.size];

                targetBuffer.get(sourceByteArray);
                targetBuffer.clear();

                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);

                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    decodeOutputEnd = true;
                }

                if (sourceByteArray.length > 0 && bufferedOutputStream != null) {
                    if (presentationTimeUs < startMicroseconds) {
                        continue;
                    }

//                    byte[] convertByteNumberByteArray = ConvertByteNumber(byteNumber, Constant
//                                    .RecordByteNumber,
//                            sourceByteArray);
                        byte[] convertByteNumberByteArray = ConvertByteNumber(byteNumber, desireBitwidth,
                            sourceByteArray);

//                    byte[] resultByteArray =
//                            ConvertChannelNumber(channelCount, Constant.RecordChannelNumber,
//                                    Constant.RecordByteNumber,
//                                    convertByteNumberByteArray);
                    byte[] resultByteArray =
                            ConvertChannelNumber(channelCount, desireChannel,
                                    desireBitwidth,
                                    convertByteNumberByteArray);
                    Log.d("tetetetet","finished");
                    try {
                        bufferedOutputStream.write(resultByteArray);
                    } catch (Exception e) {
                        LogFunction.error("输出解压音频数据异常", e);
//                        CustomBugReporter.reportError(DecodeEngine.class.getName(), "output decoded audio data exception", e);
                    }
                }

//                if (presentationTimeUs > endMicroseconds) {
//                    break;
//                }
            } catch (Exception e) {
                LogFunction.error("getDecodeData异常", e);
//                CustomBugReporter.reportError(TAG, "getDecodedData exception", e);
            }
        }

        if (bufferedOutputStream != null) {
            try {
                bufferedOutputStream.close();
            } catch (IOException e) {
                LogFunction.error("关闭bufferedOutputStream异常", e);
//                CustomBugReporter.reportError(TAG, "close bufferedOutputStream exception 1",e);
            }
        }

        if (sampleRate != desireSampleRate) {
            Log.d("tetetetet1","finished");
            Resample(sampleRate, desireSampleRate, byteNumber, desireBitwidth,decodeFileUrl);
//            Resample(sampleRate, desireSampleRate, desireChannel, decodeFileUrl);
            Log.d("tetetetet2","finished");
        }

        if (mediaCodec != null) {
            mediaCodec.stop();
            mediaCodec.release();
        }

        if (mediaExtractor != null) {
            mediaExtractor.release();
        }
    }

    private static void Resample(int srcSampleRate, int desSampleRate,int srcByteNumber, int desByteNumber,String decodeFileUrl) {
        String newDecodeFileUrl = decodeFileUrl + "new";

        try {
            FileInputStream fileInputStream =
                    new FileInputStream(new File(decodeFileUrl));
            FileOutputStream fileOutputStream =
                    new FileOutputStream(new File(newDecodeFileUrl));

            new SSRC(fileInputStream, fileOutputStream, srcSampleRate, desSampleRate,
                    srcByteNumber, desByteNumber, 1, Integer.MAX_VALUE,
                    0, 0, true);
            fileInputStream.close();
            fileOutputStream.close();

            FileFunction.RenameFile(newDecodeFileUrl, decodeFileUrl);
        } catch (IOException e) {
            LogFunction.error("关闭bufferedOutputStream异常", e);
//            CustomBugReporter.reportError(TAG, "close bufferedOoutputStream exception 2", e);
        }
    }



    private static byte[] ConvertByteNumber(int sourceByteNumber, int outputByteNumber, byte[]
            sourceByteArray) {
        Log.d("audio_info","sourceByteNumber:" + sourceByteNumber + "   outputByteNumber:" + outputByteNumber);
        if (sourceByteNumber == outputByteNumber) {
            return sourceByteArray;
        }

        int sourceByteArrayLength = sourceByteArray.length;

        byte[] byteArray;

        switch (sourceByteNumber) {
            case 1:
                switch (outputByteNumber) {
                    case 2:
                        byteArray = new byte[sourceByteArrayLength * 2];

                        byte resultByte[];

                        for (int index = 0; index < sourceByteArrayLength; index += 1) {
                            resultByte = CommonFunction.GetBytes((short) (sourceByteArray[index]
                                    * 256), Variable
                                    .isBigEnding);

                            byteArray[2 * index] = resultByte[0];
                            byteArray[2 * index + 1] = resultByte[1];
                        }

                        return byteArray;
                }
                break;
            case 2:
                switch (outputByteNumber) {
                    case 1:
                        int outputByteArrayLength = sourceByteArrayLength / 2;

                        byteArray = new byte[outputByteArrayLength];

                        for (int index = 0; index < outputByteArrayLength; index += 1) {
                            byteArray[index] = (byte) (CommonFunction.GetShort(sourceByteArray[2
                                            * index],
                                    sourceByteArray[2 * index + 1], Variable.isBigEnding) / 256);
                        }

                        return byteArray;
                }
                break;
        }

        return sourceByteArray;
    }

    public static byte[] ConvertChannelNumber(int sourceChannelCount, int outputChannelCount,
                                               int byteNumber,
                                               byte[] sourceByteArray) {
        Log.d("audio_info","srcChannel:" + sourceChannelCount + "   desChannel:" + outputChannelCount);
        if (sourceChannelCount == outputChannelCount) {
            return sourceByteArray;
        }

        switch (byteNumber) {
            case 1:
            case 2:
                break;
            default:
                return sourceByteArray;
        }

        int sourceByteArrayLength = sourceByteArray.length;

        byte[] byteArray;

        switch (sourceChannelCount) {
            case 1:
                switch (outputChannelCount) {
                    case 2:
                        byteArray = new byte[sourceByteArrayLength * 2];

                        byte firstByte;
                        byte secondByte;

                        switch (byteNumber) {
                            case 1:
                                for (int index = 0; index < sourceByteArrayLength; index += 1) {
                                    firstByte = sourceByteArray[index];

                                    byteArray[2 * index] = firstByte;
                                    byteArray[2 * index + 1] = firstByte;
                                }
                                break;
                            case 2:
                                for (int index = 0; index < sourceByteArrayLength; index += 2) {
                                    firstByte = sourceByteArray[index];
                                    secondByte = sourceByteArray[index + 1];

                                    byteArray[2 * index] = firstByte;
                                    byteArray[2 * index + 1] = secondByte;
                                    byteArray[2 * index + 2] = firstByte;
                                    byteArray[2 * index + 3] = secondByte;
                                }
                                break;
                        }

                        return byteArray;
                }
                break;
            case 2:
                switch (outputChannelCount) {
                    case 1:
                        int outputByteArrayLength = sourceByteArrayLength / 2;

                        byteArray = new byte[outputByteArrayLength];

                        switch (byteNumber) {
                            case 1:
                                for (int index = 0; index < outputByteArrayLength; index += 2) {
                                    short averageNumber =
                                            (short) ((short) sourceByteArray[2 * index] + (short)
                                                    sourceByteArray[2 *
                                                            index + 1]);
                                    byteArray[index] = (byte) (averageNumber >> 1);
                                }
                                break;
                            case 2:
                                for (int index = 0; index < outputByteArrayLength; index += 2) {
                                    byte resultByte[] = CommonFunction.AverageShortByteArray
                                            (sourceByteArray[2 * index],
                                                    sourceByteArray[2 * index + 1],
                                                    sourceByteArray[2 *
                                                            index + 2],
                                                    sourceByteArray[2 * index + 3], Variable
                                                            .isBigEnding);

                                    byteArray[index] = resultByte[0];
                                    byteArray[index + 1] = resultByte[1];
                                }
                                break;
                        }

                        return byteArray;
                }
                break;
        }

        return sourceByteArray;
    }
}
