package kr.co.icontrols.ilp300_700.call;

import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;

import kr.co.icontrols.ilp300_700.MainActivity;
import kr.co.icontrols.ilp300_700.global.DataClasses;
import milsip.stack.sip.SIPStack;
import milsip.stack.sip.videoEnc;

public class AvcEncoder {

    static String TAG = "AvcEncoder";

    //private EncodedFrameListener frameListener;
    private MediaCodec mediaCodec;
    private BufferedOutputStream outputStream;

    private byte[] sps;
    private byte[] pps;

    DatagramSocket mSocket;
    InetAddress targetAddr;
    videoEnc videoenc = new videoEnc();
    //private ParameterSetsListener parameterSetsListener;

    public AvcEncoder() throws IOException {
        mediaCodec = MediaCodec.createEncoderByType("video/avc");
        /*MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 640, 480);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mediaCodec.start();*/
    }

    public void requestKeyFrameDelayed() {
        Log.d("AvcEncoder", "[requestKeyFrameDelayed]");
        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            if (mediaCodec != null) {
                Bundle params = new Bundle();
                params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
                mediaCodec.setParameters(params);
                Log.d("AvcEncoder", "[requestKeyFrameDelayed] **** I-Frame ****");
            }
        }, 200);
    }


    public void Init(String strIP, boolean isM30) throws IOException {
        File f = new File(Environment.getExternalStorageDirectory(), "Download/video_encoded.264");
        //touch (f);
        try {
            outputStream = new BufferedOutputStream(new FileOutputStream(f));
        } catch (Exception e){
            e.printStackTrace();
        }
        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240);
        //mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 320);
        //mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 240);
        //mediaFormat.setInteger(MediaFormat.KEY_ROTATION, 240);

//        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1800000);
//        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
//        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
//        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); // 단위 second. 0인 경우, 간혹 frame size가 50000을 넘는 경우가 생김.

        if (isM30) {
            Log.d("AvcEncoder", "[Init] ====== M30 ======");
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500000);
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        } else {
            Log.d("AvcEncoder", "[Init] ====== M20 ======");
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 8);
            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0);
        }

/*        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 10);*/
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
/*        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);*/

        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mediaCodec.start();

        Log.d(TAG, "[VIDEO IP] " + strIP);
        targetAddr = InetAddress.getByName(strIP);
        mSocket = new DatagramSocket();
    }



    public void close() throws IOException {
        mediaCodec.stop();
        mediaCodec.release();
    }

    public void offerEncoder1(byte[] _input) {
        try {
//            int width = 640;
//            int height = 480;
//
//            // nv21 --> i420
//            byte[] nv21Data = _input;
//            byte[] i420Data = new byte[width * height * 3 / 2];
//            YuvUtil.yuvNV21ToI420(nv21Data, width, height, i420Data);
//
//            // crop 640x480 --> 630x480
//            byte[] i420CropData = new byte[width * height * 3 / 2];
//            int cropWidth = 630;
//            int cropHeight = 480;
//            YuvUtil.yuvCropI420(i420Data, width, height, i420CropData, cropWidth, cropHeight, 10, 0);
//            i420Data = i420CropData;
//            width = cropWidth;
//            height = cropHeight;
//
//            // scale 630x480 --> 320x240
//            byte[] i420ScaleData = new byte[width * height * 3 / 2];
//            int scaleWidth = 320;
//            int scaleHeight = 240;
//            YuvUtil.yuvScaleI420(i420Data, width, height, i420ScaleData, scaleWidth, scaleHeight, 0);
//            i420Data = i420ScaleData;
//            width = scaleWidth;
//            height = scaleHeight;
//
//            // i420 --> nv21
//            byte[] input = new byte[width * height * 3 / 2];
//            YuvUtil.yuvI420ToNV12(i420Data, width, height, input);

            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(_input);
                mediaCodec.queueInputBuffer(inputBufferIndex, 0, _input.length, 0, 0);
            }

            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);
            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                byte[] outData = new byte[bufferInfo.size];
                outputBuffer.get(outData);
                outputStream.write(outData, 0, outData.length);
                //Log.i("AvcEncoder", outData.length + " bytes written");

                byte[] byteEncData = new byte[28];
                videoenc.encodeHNSRtpHeader(SIPStack.SIP_CODEC_H264, true);
                if(String.format("%02x", outData[4]&0xff).equals("41"))
                {

                    byte[] bIframe = new byte[sps.length+outData.length+byteEncData.length];

                    int iDataSize = sps.length+outData.length+16;
                    int iDataSize1 = sps.length+outData.length;
                    //Log.d("AvcEncoder", "[writeHNSRTPPacket] iDataSize " + iDataSize + " iDataSize1 " +  iDataSize1);

                    byte[] byteDataSize = new byte[4];

                    byteDataSize[0] = (byte)(iDataSize - 256);
                    byteDataSize[1] = (byte)(iDataSize / 256);

                    byteDataSize[2] = (byte)(iDataSize1 - 256);
                    byteDataSize[3] = (byte)(iDataSize1 / 256);

                    byteEncData[12] = byteDataSize[0];
                    byteEncData[13] = byteDataSize[1];
                    byteEncData[20] = byteDataSize[2];
                    byteEncData[21] = byteDataSize[3];



                    for(int i = 0; i < 12; i++)
                    {

                        byteEncData[i] = videoenc.rtpVideoBuffer[i];
                        if(i == 0)
                            byteEncData[i] = (byte)0x80;
//                        if(i < 12)
//                            Dialer.m_byteTest[i] = 0x00;
                        //Log.d("Dialer_KSJ", i + " " + String.format("%02x", byteEncData[i]&0xff));
                    }

                    System.arraycopy(byteEncData, 0, bIframe, 0, byteEncData.length);
                    System.arraycopy(sps, 0, bIframe, byteEncData.length, sps.length);
                    System.arraycopy(outData, 0, bIframe, byteEncData.length+sps.length, outData.length);

                    //outData[4] = 0x67;
                    //Log.d("Dialer_KSJ", "[writeHNSRTPPacket] Start " + outData.length + " true " + String.format("%02x", outData[4]&0xff));
                    //Log.d("Dialer_KSJ", "offerEncoder1 " + outData.length);
                    //MainActivity.mCallController.mVideoJNI.mV40IFCCD.recvFramecallback(outData, 0, outData.length, outData.length, 0);

                    //packet = new DatagramPacket(buf, buf.length, ina, 50061);



                    //Dialer.setDecode_buf(outData);
                    final byte[] _outData = bIframe;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            int port = 0;
                            if(MainActivity.m_bCallType == 1)
                                port = 50061;
                            else if(MainActivity.m_bCallType == 2)
                                port = 50063;

                            DatagramPacket packet = new DatagramPacket(_outData, _outData.length, targetAddr, port);
                            try {
                                mSocket.send(packet);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();


                    //UDP 통신용 소켓 생성
                    //DatagramSocket socket = new DatagramSocket(50061);
                    //서버 주소 변수
                    //InetAddress serverAddr = InetAddress.getByName("10.1.1.2");

                    //패킷으로 변경
                    //DatagramPacket packet = new DatagramPacket(outData, outData.length, serverAddr, 50061);

                    //패킷 전송!
                    //socket.send(packet);
                }
                else if(String.format("%02x", outData[4]&0xff).equals("67"))
                {
                    sps = new byte[outData.length];
                    //outData1 = new byte[outData.length];
                    for(int i = 0; i < outData.length; i++)
                    {
                        //Log.d("KSJ", i + " " + String.format("%02x", outData[i]&0xff));
                        sps[i] = outData[i];
                        //outData1[i] = outData[i];
                        //str += i + " "+String.format("%02x", outData[i]&0xff) + " ";
                    }

                    //Log.d("Dialer_KSJ", "offerEncoder2 " + outData.length);
                    //DatagramPacket packet = new DatagramPacket(outData, outData.length, targetAddr, 50061);
                    //packet = new DatagramPacket(buf, buf.length, ina, 50061);

                    //Dialer.setDecode_buf(outData);
                    //mSocket.send(packet);
                }
                else {
                    //Log.d("Dialer_KSJ", "[writeHNSRTPPacket] Start " + outData.length + " false " + String.format("%02x", outData[4] & 0xff));
                    byte[] bIframe = new byte[sps.length+outData.length+byteEncData.length];

                    int iDataSize = sps.length+outData.length+16;
                    int iDataSize1 = sps.length+outData.length;
                    // Log.d("Dialer_KSJ", "[writeHNSRTPPacket] iDataSize " + iDataSize + " iDataSize1 " +  iDataSize1);

                    byte[] byteDataSize = new byte[4];

                    byteDataSize[0] = (byte)(iDataSize - 256);
                    byteDataSize[1] = (byte)(iDataSize / 256);

                    byteDataSize[2] = (byte)(iDataSize1 - 256);
                    byteDataSize[3] = (byte)(iDataSize1 / 256);

//
                    for(int i = 0; i < 12; i++)
                    {

                        byteEncData[i] = videoenc.rtpVideoBuffer[i];
                        if(i == 0)
                            byteEncData[i] = (byte)0x80;
//                        if(i < 12)
//                            Dialer.m_byteTest[i] = 0x00;
                        //Log.d("Dialer_KSJ", i + " " + String.format("%02x", byteEncData[i]&0xff));
                    }

                    byteEncData[12] = byteDataSize[0];
                    byteEncData[13] = byteDataSize[1];
                    byteEncData[20] = byteDataSize[2];
                    byteEncData[21] = byteDataSize[3];

                    System.arraycopy(byteEncData, 0, bIframe, 0, byteEncData.length);
                    System.arraycopy(sps, 0, bIframe, byteEncData.length, sps.length);
                    System.arraycopy(outData, 0, bIframe, byteEncData.length+sps.length, outData.length);

                    /*for(int i = 0; i < Dialer.m_byteTest.length; i++)
                    {
                        Log.d("Dialer_KSJ", i + " " + String.format("%02x", Dialer.m_byteTest[i]&0xff));

                    }*/
                    //MainActivity.mCallController.mVideoJNI.mV40IFCCD.recvFramecallback(sps, 1, sps.length, sps.length, 0);
                    //MainActivity.mCallController.mVideoJNI.mV40IFCCD.recvFramecallback(bIframe, 1, bIframe.length, bIframe.length, 0);

                    //Dialer.setDecode_buf(bIframe);
                    final byte[] _outData = bIframe;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            int port = 0;
                            if(MainActivity.m_bCallType == 1)
                                port = 50061;
                            else if(MainActivity.m_bCallType == 2)
                                port = 50063;

                            DatagramPacket packet = new DatagramPacket(_outData, _outData.length, targetAddr, port);
                            try {
                                mSocket.send(packet);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                }

                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);

            }
        } catch (Throwable t) {
            t.printStackTrace();
        }

    }

    public void offerEncoder(byte[] input) {
        try {
            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(input);
                mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
            }

            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                byte[] outData = new byte[bufferInfo.size];
                outputBuffer.get(outData);
                //Log.d("KSJ", "offerEncoder1-1");
                //if (sps != null && pps != null) {
                //Log.d("KSJ", "offerEncoder1");
                //ByteBuffer frameBuffer = ByteBuffer.wrap(outData);
                //frameBuffer.putInt(bufferInfo.size - 4);
                //String str = "";
                    /*for(int i = 0; i < sps.length; i++)
                    {
                        Log.d("KSJ", "sps" + " "+String.format("%02x", sps[i]&0xff));
                        //str += i + " "+String.format("%02x", outData[i]&0xff) + " ";
                    }

                    for(int i = 0; i < pps.length; i++)
                    {
                        Log.d("KSJ", "pps" + " "+String.format("%02x", pps[i]&0xff));
                        //str += i + " "+String.format("%02x", outData[i]&0xff) + " ";
                    }*/
                //Log.d("KSJ", str);
                //Log.d("KSJ", "offerEncoder2");
                //outData[0] = 0x00;
                //outData[1] = 0x00;
                //outData[2] = 0x00;
                //outData[3] = 0x01;
                    /*for(int i = 0; i < 10; i++)
                    {
                        Log.d("KSJ", "i" + " " + String.format("%02x", outData[i]&0xff));
                        //str += i + " "+String.format("%02x", outData[i]&0xff) + " ";
                    }*/
                    /*
                    if(String.format("%02x", outData[4]&0xff).equals("41"))
                    {
                        //Log.d("KSJ", "[writeHNSRTPPacket] Start " + outData.length + " true " + String.format("%02x", outData[4]&0xff));
                        //Log.d("KSJ", "offerEncoder1");
                        MainActivity.mCallController.mVideoJNI.mV40IFCCD.recvFramecallback(outData, 0, outData.length, outData.length, 0);
                    }
                    else {
                        //Log.d("KSJ", "[writeHNSRTPPacket] Start " + outData.length + " false " + String.format("%02x", outData[4] & 0xff));
                        MainActivity.mCallController.mVideoJNI.mV40IFCCD.recvFramecallback(outData, 1, outData.length, outData.length, 0);
                    }*/
                //frameListener.frameReceived(outData, 0, outData.length);
                //} else

                {
                    //Log.d("KSJ", "offerEncoder2");
                    ByteBuffer spsPpsBuffer = ByteBuffer.wrap(outData);
                    if (spsPpsBuffer.getInt() == 0x00000001) {
                        System.out.println("parsing sps/pps");
                    } else {
                        System.out.println("something is amiss?");
                    }
                    int ppsIndex = 0;
                    //while(!(spsPpsBuffer.get() == 0x00 && spsPpsBuffer.get() == 0x00 && spsPpsBuffer.get() == 0x00 && spsPpsBuffer.get() == 0x01)) {

                    //}
                    ppsIndex = spsPpsBuffer.position();
                    sps = new byte[ppsIndex - 8];
                    System.arraycopy(outData, 4, sps, 0, sps.length);
                    pps = new byte[outData.length - ppsIndex];
                    System.arraycopy(outData, ppsIndex, pps, 0, pps.length);

                    for(int i = 0; i < 10; i++)
                    {
                        Log.d("KSJ", "i" + " " + String.format("%02x", outData[i]&0xff));
                        //str += i + " "+String.format("%02x", outData[i]&0xff) + " ";
                    }

                    //MainActivity.mCallController.mVideoJNI.mV40IFCCD.recvFramecallback(outData, 1, outData.length, outData.length, 0);
                    //MainActivity.mCallController.mVideoJNI.mV40IFCCD.recvFramecallback(outData, 0, 0, 0, 0);
                    //if (null != parameterSetsListener) {
                    //    parameterSetsListener.avcParametersSetsEstablished(sps, pps);
                    // }
                }
                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }

    }

    String byteArrayToHex(byte[] a) {
        StringBuilder sb = new StringBuilder();
        for(final byte b: a)
            sb.append(String.format("%02x ", b&0xff));
        return sb.toString();
    }
}
