/* exported H265Session */
/* global Uint8Array, H265SPSParser, H265Decoder, inheritObject,
  RtpSession, VideoBufferList, ArrayBuffer, decodeMode */
/* eslint-disable no-magic-numbers */

var H265Session = function () {
    'use strict';
    var rtpTimeStamp = 0,
        inputLength = 0,
        size1K = 1024,
        size1M = size1K * size1K,
        playback = false,
        outputSize = 0,
        curSize = 0,
        inputBuffer = new Uint8Array(size1M),
        PREFIX = new Uint8Array(4),
        SPSParser = null,
        privIRtpTime = 0,
        // frameDiffTime = 0,
        // needDropCnt = 0,
        delayingTime = 0,
        DELAY_LIMIT = 8000,
        decodedData = {
            frameData: null,
            timeStamp: null,
        },
        timeData = {
            'timestamp': null,
            'timezone': null,
        },
        frameRate = 0;
    var width = 0,
        height = 0;

    PREFIX[0] = '0x00';
    PREFIX[1] = '0x00';
    PREFIX[2] = '0x00';
    PREFIX[3] = '0x01';

    var setBuffer = function (buffer1, buffer2) {
        var tempBuffer = buffer1;
        if ((inputLength + buffer2.length) > tempBuffer.length) {
            tempBuffer = new Uint8Array(tempBuffer.length + size1M);
        }

        tempBuffer.set(buffer2, inputLength);
        inputLength += buffer2.length;
        return tempBuffer;
    };

    var decodeMode = 'canvas';

    function Constructor() {
        this.decoder = new H265Decoder();
    }

    Constructor.prototype = {
        init: function () {
            SPSParser = new H265SPSParser();
            console.log(SPSParser.parse)
            //this.decoder.setIsFirstFrame(false);
            this.videoBufferList = new VideoBufferList();
            this.firstDiffTime = 0;
            this.checkDelay = true;
        },
        setFrameRate(fps) {
            frameRate = fps;
            //console.log('frameRate: ', frameRate)
        },
        remuxRTPData: function (rtspInterleaved, rtpHeader, rtpPayload, isBackup) {
            var HEADER = rtpHeader,
                PAYLOAD = null,
                timeData = {
                    'timestamp': null,
                    'timezone': null,
                },
                extensionHeaderLen = 0,
                PaddingSize = 0,
                data = {};

            if (rtspInterleaved[0] !== 0x24) {
                console.log("H265Session::it is not valid interleave header (RTSP over TCP)");
                return;
            } else if ((rtpHeader[0] & 0x0F) === 0x0F) {
                console.log("H265Session::There is additional CSRC which is not handled in this version");
                return;
            } else if ((rtpHeader[0] & 0x20) === 0x20) {
                PaddingSize = rtpPayload[rtpPayload.length - 1];
                console.log("H265Session::PaddingSize - " + PaddingSize);
            }

            //Extension bit check in RTPHeader
            if ((rtpHeader[0] & 0x10) === 0x10) {
                extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4;

                //Playback check
                if (rtpPayload[0] === 0xAB && rtpPayload[1] === 0xAD) {
                    var startHeader = 4,
                        NTPmsw = new Uint8Array(new ArrayBuffer(4)),
                        NTPlsw = new Uint8Array(new ArrayBuffer(4)),
                        gmt = new Uint8Array(new ArrayBuffer(2)),
                        fsynctime = {
                            'seconds': null,
                            'useconds': null,
                        },
                        microseconds = null;

                    NTPmsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
                    startHeader += 4;

                    NTPlsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
                    startHeader += 6;

                    gmt.set(rtpPayload.subarray(startHeader, startHeader + 2), 0);

                    microseconds = (this.ntohl(NTPlsw) / 0xffffffff) * 1000;
                    fsynctime.seconds = ((this.ntohl(NTPmsw) - 0x83AA7E80) >>> 0);
                    fsynctime.useconds = microseconds;
                    gmt = (((gmt[0] << 8) | gmt[1]) << 16) >> 16;

                    timeData = {
                        timestamp: fsynctime.seconds,
                        timestamp_usec: fsynctime.useconds,
                        timezone: gmt,
                    };

                    if ((this.getFramerate() === 0 || typeof this.getFramerate() === "undefined") &&
                        (typeof this.getTimeStamp() !== "undefined")) {
                        var diffUsec = timeData.timestamp_usec - this.getTimeStamp().timestamp_usec;
                        this.setFramerate(Math.round(1000 / (((timeData.timestamp -
                            this.getTimeStamp().timestamp) === 0 ? 0 : 1000) + (diffUsec))));
                    }
                    this.setTimeStamp(timeData);
                    playback = true;
                }
            }

            PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize);
            rtpTimeStamp = this.ntohl(rtpHeader.subarray(4, 8));

            var nalType = (PAYLOAD[0] >> 1) & 0x3f;

            switch (nalType) {
                default:
                    inputBuffer = setBuffer(inputBuffer, PREFIX);
                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
                    break;
                // Fragmentation unit(FU)
                case 49:
                    var startBit = ((PAYLOAD[2] & 0x80) === 0x80);
                    var endBit = ((PAYLOAD[2] & 0x40) === 0x40);
                    var fuType = PAYLOAD[2] & 0x3f;
                    var payloadStartIndex = 3;

                    if (startBit === true && endBit === false) {
                        var newNalHeader = new Uint8Array(2);
                        newNalHeader[0] = (PAYLOAD[0] & 0x81) | (fuType << 1);
                        newNalHeader[1] = PAYLOAD[1];

                        inputBuffer = setBuffer(inputBuffer, PREFIX);
                        inputBuffer = setBuffer(inputBuffer, newNalHeader);
                        inputBuffer = setBuffer(inputBuffer,
                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
                    } else {
                        inputBuffer = setBuffer(inputBuffer,
                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
                    }
                    break;
                //SPS
                case 33:
                    SPSParser.parse(PAYLOAD);
                    var resolution = SPSParser.getSizeInfo();
                    curSize = resolution.width * resolution.height;
                    if (playback && !isBackup) {
                        var limitSize = 1280 * 720;
                        if (curSize > limitSize) {
                            data.error = {
                                errorCode: "998",
                                description: "Resolution is too big",
                                place: "h265Session.js",
                            };
                            this.rtpReturnCallback(data);
                            return; // data;
                        }
                    }
                    inputBuffer = setBuffer(inputBuffer, PREFIX);
                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
                    width = resolution.width;
                    height = resolution.height;
                    break;
            } //end of switch(nalType)
            //case 48 Remove because not use more

            //check marker bit
            var frameType = '';
            if ((HEADER[1] & 0x80) === 0x80) {
                // var DepacketizingTime = Date.now() - beforeDepacketizing;
                if (outputSize !== curSize) {
                    outputSize = curSize;
                    this.decoder.setOutputSize(outputSize);
                }

                var inputBufferSub = inputBuffer.subarray(0, inputLength);
                if (inputBufferSub[4] === 0x40) {
                    frameType = 'I';
                    if (this.firstDiffTime == 0) {
                        privIRtpTime = rtpTimeStamp;
                        delayingTime = 0;
                        this.firstDiffTime = (Date.now() - (rtpTimeStamp / 90).toFixed(0));
                        // needDropCnt = 0;
                    } else {
                        // frameDiffTime = Math.round(((rtpTimeStamp / 90).toFixed(0) - privIRtpTime) / this.getGovLength());
                        if((rtpTimeStamp - privIRtpTime) < 0){
                            this.firstDiffTime = delayingTime + (Date.now() - (rtpTimeStamp / 90).toFixed(0));
                            // console.log("firstDiffTime = " + this.firstDiffTime + " rtpTimeStamp = " + rtpTimeStamp + " privIRtpTime = " + privIRtpTime );
                        }
                        delayingTime = (Date.now() - (rtpTimeStamp / 90).toFixed(0)) - this.firstDiffTime;
                        privIRtpTime = rtpTimeStamp;
                        if (delayingTime > DELAY_LIMIT) {
                            if (this.checkDelay === true && playback === false) {
                                data.error = {
                                    errorCode: "997",
                                    description: "Delay time is too long",
                                    place: "h265Session.js",
                                };
                                // console.log("h265Session::Delay time is too long 997 error ");

                                this.rtpReturnCallback(data);
                                return; // data;
                            }
                        }
                        // needDropCnt = (needDropCnt > 0) ? Math.round(needDropCnt / frameDiffTime) : 0;
                    }
                } else {
                    frameType = 'P';
                }

                decodedData.frameData = null;
                if (isBackup !== true || playback !== true) {
                    console.log('frameType', frameType)
                    decodedData.frameData = this.decoder.decode(inputBufferSub);
                }

                decodedData.timeStamp = null;
                inputLength = 0;
                if (playback === true) {
                    timeData = (timeData.timestamp === null ? this.getTimeStamp() : timeData);
                    decodedData.timeStamp = timeData;
                }
                if (isBackup) {
                    data.backupData = {
                        'stream': inputBufferSub,
                        'frameType': frameType,
                        'width': width,
                        'height': height,
                        'codecType': 'h265',
                    };
                    if (timeData.timestamp !== null && typeof timeData.timestamp !== "undefined") {
                        data.backupData.timestamp_usec = timeData.timestamp_usec;
                    } else {
                        data.backupData.timestamp = (rtpTimeStamp / 90).toFixed(0);
                    }
                }
                data.decodedData = decodedData;
                if (decodeMode !== "canvas") {
                    data.decodeMode = "canvas";
                }
                console.log(data)
                this.rtpReturnCallback(data);
                return; // data;
            }
        },
        bufferingRtpData: function (rtspInterleaved, rtpHeader, rtpPayload) {
            var HEADER = rtpHeader,
                PAYLOAD = null,
                extensionHeaderLen = 0,
                PaddingSize = 0;

            if (rtspInterleaved[0] !== 0x24) {
                console.log("H265Session::it is not valid interleave header (RTSP over TCP)");
                return;
            } else if ((rtpHeader[0] & 0x0F) === 0x0F) {
                console.log("H265Session::There is additional CSRC which is not handled in this version");
                return;
            } else if ((rtpHeader[0] & 0x20) === 0x20) {
                PaddingSize = rtpPayload[rtpPayload.length - 1];
                console.log("H265Session::PaddingSize - " + PaddingSize);
            }

            //Extension bit check in RTPHeader
            if ((rtpHeader[0] & 0x10) === 0x10) {
                extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4;

                //Playback check
                if (rtpPayload[0] === 0xAB && rtpPayload[1] === 0xAD) {
                    var startHeader = 4,
                        NTPmsw = new Uint8Array(new ArrayBuffer(4)),
                        NTPlsw = new Uint8Array(new ArrayBuffer(4)),
                        gmt = new Uint8Array(new ArrayBuffer(2)),
                        fsynctime = {
                            'seconds': null,
                            'useconds': null,
                        },
                        microseconds = null;

                    NTPmsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
                    startHeader += 4;

                    NTPlsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
                    startHeader += 6;

                    gmt.set(rtpPayload.subarray(startHeader, startHeader + 2), 0);

                    microseconds = (this.ntohl(NTPlsw) / 0xffffffff) * 1000;
                    fsynctime.seconds = ((this.ntohl(NTPmsw) - 0x83AA7E80) >>> 0);
                    fsynctime.useconds = microseconds;
                    gmt = (((gmt[0] << 8) | gmt[1]) << 16) >> 16;

                    timeData = {
                        timestamp: fsynctime.seconds,
                        timestamp_usec: fsynctime.useconds,
                        timezone: gmt,
                    };
                    playback = true;
                }
            }

            PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize);

            rtpTimeStamp = new Uint8Array(new ArrayBuffer(4));
            rtpTimeStamp.set(rtpHeader.subarray(4, 8), 0);
            rtpTimeStamp = this.ntohl(rtpTimeStamp);

            var nalType = (PAYLOAD[0] >> 1) & 0x3f;

            switch (nalType) {
                default:
                    inputBuffer = setBuffer(inputBuffer, PREFIX);
                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
                    break;
                // Fragmentation unit(FU)
                case 49:
                    var startBit = ((PAYLOAD[2] & 0x80) === 0x80);
                    var endBit = ((PAYLOAD[2] & 0x40) === 0x40);
                    var fuType = PAYLOAD[2] & 0x3f;
                    var payloadStartIndex = 3;

                    if (startBit === true && endBit === false) {
                        var newNalHeader = new Uint8Array(2);
                        newNalHeader[0] = (PAYLOAD[0] & 0x81) | (fuType << 1);
                        newNalHeader[1] = PAYLOAD[1];

                        inputBuffer = setBuffer(inputBuffer, PREFIX);
                        inputBuffer = setBuffer(inputBuffer, newNalHeader);
                        inputBuffer = setBuffer(inputBuffer,
                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
                    } else {
                        inputBuffer = setBuffer(inputBuffer,
                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
                    }
                    break;
                //SPS
                case 33:
                    SPSParser.parse(PAYLOAD);
                    var resolution = SPSParser.getSizeInfo();
                    curSize = resolution.width * resolution.height;
                    inputBuffer = setBuffer(inputBuffer, PREFIX);
                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
                    width = resolution.width;
                    height = resolution.height;
                    break;
            } //end of switch(nalType)
            //case 48 Remove because not use more

            //check marker bit
            if ((HEADER[1] & 0x80) === 0x80) {
                if (outputSize !== curSize) {
                    outputSize = curSize;
                    this.decoder.setOutputSize(outputSize);
                }

                var stepBufferSub = new Uint8Array(inputBuffer.subarray(0, inputLength));

                if (this.videoBufferList !== null) {
                    this.videoBufferList.push(stepBufferSub, width, height,
                        'h265', (stepBufferSub[4] === 0x40) ? 'I' : 'P', timeData);
                }
                inputLength = 0;
            }
        },
        findIFrame: function () {
            if (this.videoBufferList !== null) {
                var bufferNode = this.videoBufferList.findIFrame();
                if (bufferNode === null || typeof bufferNode === "undefined") {
                    return false;
                } else {
                    var data = {};
                    this.setTimeStamp(bufferNode.timeStamp);
                    data.frameData = this.decoder.decode(bufferNode.buffer);
                    data.timeStamp = bufferNode.timeStamp;
                    return data;
                }
            }
        },
        ntohl: function (buffer) {
            return (((buffer[0] << 24) + (buffer[1] << 16) +
                (buffer[2] << 8) + buffer[3]) >>> 0);
        },
        set rtpSessionCallback(func) {
            this.rtpReturnCallback = func;
        },
    };

    return new Constructor();
};
/* eslint-enable no-magic-numbers */
