
/*---------------------------------------------------------------------------*/
/* ios_video_io.cpp                                                          */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"

#include "client/ios/ios_async/ios_async.h"
#include "platform/phone_ios.h"
#include "../ios/ios_main.h"

#include "common/os/iomux.h"
#include "common/interface/time.h"
#include "common/interface/socket.h"
#include "common/ilib/str.h"
#include "common/interface/media.h"
#include "common/interface/appsharing.h"
#include "common/service/media/video_io.h"

#include "ios_video_io.h"

extern class forms_async forms_async;
extern int webcam_preview_length;
extern const byte *webcam_preview_data;

#define MAX_TRANSCODERS   2
//static IosVideoIoChannel *(transcoderToChannelMap[MAX_TRANSCODERS]) = { NULL };
static int videoIoChannelCount = 0;
//static enum VideoCoder videoIoChannelRtpCoder = VideoCoder::VP8;
static bool stop_feed_containers = false;

/*---------------------------------------------------------------------------*/
/* IosVideoIoChannel                                                         */
/*---------------------------------------------------------------------------*/

class IVideoIoChannel * IVideoIoChannel::Create(class IIoMux * const iomux, class IVideoIo * mediaIo, class UMediaIoChannel * const user, class IInstanceLog * log)
{
    return new IosVideoIoChannel(iomux, mediaIo, user, log);
}

extern "C" void onVideoStart();
extern "C" void onVideoEnd();

IosVideoIoChannel::IosVideoIoChannel(class IIoMux * const iomux, class IVideoIo * mediaIo, class UMediaIoChannel * const user, class IInstanceLog * log) :
    VideoIoChannel(iomux, mediaIo, user, log)
{
    lTimestamp = 0;
    videoFrame = (char *)malloc(640*480*2);
    dropFrame = false;
    sequenceGap = false;
    rtpMarker = false;
    frameLength = 0;
    frameLengthFua = 0;
    fragSb = 0;
    fuaTimestamp = 0;
    rxVp8PicId = 0;
    firCommandSequence = 0;

    transcoder_id = -1;
    if (videoIoChannelCount == 0) {
        onVideoStart();
    }
    videoIoChannelCount++;
}

IosVideoIoChannel::~IosVideoIoChannel()
{
    if(videoFrame) free(videoFrame);

    videoIoChannelCount--;
    if (videoIoChannelCount == 0) {
        onVideoEnd();
    }
}

/*
void IosVideoIoChannel::Initialize(void * context, const char * channelId, enum VideoCoder coder, ConnectionType connType)
{
    if (connType == ConnectionType::REMOTE_RTP) {
        char coder_string[16];
        videoIoChannelRtpCoder = coder;
        _sprintf(coder_string, "%d", (coder == VideoCoder::H264) ? FORMS_WEBCAM_CODER_H264 : FORMS_WEBCAM_CODER_VP8);
        set_forms_property(&forms_async, "FORMS/SET-WEBCAM-CODER", coder_string);
    }
    else if (connType == ConnectionType::LOCAL_VIDEO_STREAM) {
        char coder_string[16];
        _sprintf(coder_string, "%d", (coder == VideoCoder::H264) ? FORMS_WEBCAM_CODER_H264 : FORMS_WEBCAM_CODER_VP8);
        set_forms_property(&forms_async, "FORMS/SET-PREVIEW-CODER", coder_string);
    }
    VideoIoChannel::Initialize(context, channelId, coder, connType);
}

void IosVideoIoChannel::TryClose(void)
{
    if (transcoder_id != -1) {
        forms_async.enqueue(FORMS_ASYNC_VIDEO_TRANSCODE, 0, transcoder_id, 0, 0, NULL, 0, 0, 0, 0, false,
            (videoIoChannelRtpCoder == VideoCoder::H264) ? FORMS_WEBCAM_CODER_H264 : FORMS_WEBCAM_CODER_VP8,
            (coder == VideoCoder::H264) ? FORMS_WEBCAM_CODER_H264 : FORMS_WEBCAM_CODER_VP8);
    }
    else {
        if (connType == ConnectionType::REMOTE_RTP) {
            videoIoChannelRtpCoder = VideoCoder::VP8;
        }
        VideoIoChannel::TryClose();
    }
}
*/

void phone_stop_feed_video_containers(bool stop_feed)
{
    debug->printf("phone_stop_feed_video_containers %u", stop_feed);
    stop_feed_containers = stop_feed;
}

/*
void IosVideoIoChannel::Transcode(const byte * buf, int len, dword timestamp, dword duration, short sequenceNumberDiff)
{
    if (transcoder_id == -1) {
        do {
            transcoder_id++;
        }
        while((transcoder_id < MAX_TRANSCODERS) && transcoderToChannelMap[transcoder_id]);
        if (transcoder_id == MAX_TRANSCODERS) {
            debug->printf("IosVideoIoChannel::VideoSample out of transcoders");
            return;
        }
        transcoderToChannelMap[transcoder_id] = this;
    }
    forms_async.enqueue(FORMS_ASYNC_VIDEO_TRANSCODE, 0, transcoder_id, timestamp, duration, sequenceNumberDiff, buf, len, 0, 0, 0, false,
        (videoIoChannelRtpCoder == VideoCoder::H264) ? FORMS_WEBCAM_CODER_H264 : FORMS_WEBCAM_CODER_VP8,
        (coder == VideoCoder::H264) ? FORMS_WEBCAM_CODER_H264 : FORMS_WEBCAM_CODER_VP8);
}
*/
/*
void IosVideoIoChannel::VideoSample(const void * buf, int len, dword timestamp, dword duration)
{
    if (connType == ConnectionType::REMOTE_VIDEO_STREAM) {
        if (!stop_feed_containers) {
            if (videoIoChannelRtpCoder == coder) {
                VideoIoChannel::VideoSample(buf, len, timestamp, duration);
            }
            else {
                Transcode((const byte *)buf, len, timestamp, duration, 0);
            }
        }
    }
    else {
        if ((connType == ConnectionType::LOCAL_VIDEO_STREAM) && webcam_preview_data && (webcam_preview_length != 0)) {
            if (!stop_feed_containers) {
                VideoIoChannel::VideoSample(webcam_preview_data, webcam_preview_length, timestamp, duration);
            }
        }
        else if (buf && (len != 0)) {
            VideoIoChannel::VideoSample(buf, len, timestamp, duration);
        }
    }
}
*/

#define RTP_STAP_A             24
#define RTP_STAP_B             25
#define RTP_MTAP16             26
#define RTP_MTAP24             27
#define RTP_FU_A               28
#define RTP_FU_B               29

#define NAL_UNIT_NON_IDR       1
#define NAL_UNIT_IDR           5
#define NAL_UNIT_SEI           6
#define NAL_UNIT_SPS           7
#define NAL_UNIT_PPS           8
#define NAL_UNIT_DELIMITER     9

/*
void IosVideoIoChannel::SendFullIntraRequest(void)
{
    byte buf[20];

    log->Log(LOG_VIDEO, "VideoChannel(%p)::SendFullIntraRequest %d", this, firCommandSequence);

    // RTP/AVPF RFC4585 RFC5104
    buf[0] = 0x84;  // V=2, FMR=4 (FIR - Full Intra Request)
    buf[1] = 0xce;  // PT=206 (PSFB - Payload-specific feedback message)
    buf[2] = 0x00;  // length is 4 dwords
    buf[3] = 0x04;
#if 0
    WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
    WRITE_DWORD_NET(&buf[8], remoteSsrc);           // peer ssrc
    WRITE_DWORD_NET(&buf[12], remoteSsrc);          // ssrc the FIR is asserted for
#endif
    buf[16] = firCommandSequence;                   // Full intra request command sequence
    buf[17] = 0x00;  // reserved
    buf[18] = 0x00;  // reserved
    buf[19] = 0x00;  // reserved

    firCommandSequence++;

    RtcpSend(buf, 20);
}
*/
/*
void IosVideoIoChannel::Recv(const byte * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    if(videoIoChannelRtpCoder == VideoCoder::H264) return RecvH264(buf, len, timestamp, sequenceNumberDiff, marker);
    else if(videoIoChannelRtpCoder == VideoCoder::VP8) return RecvVp8(buf, len, timestamp, sequenceNumberDiff, marker);
    else if(videoIoChannelRtpCoder == VideoCoder::VP9) return RecvVp8(buf, len, timestamp, sequenceNumberDiff, marker);
}

void IosVideoIoChannel::RecvH264(const byte * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    int nalUnitType = (buf[0] & 0x1F);
    static const byte startCode[] ={0, 0, 0, 1};
    static const unsigned int startCodeSize = sizeof(startCode);

    log->Log(LOG_VIDEO, "VideoChannel(%p)::Recv h264 len=%d timestamp=%x sequenceNumberDiff=%d marker=%d,%d sequenceGap=%d", this, len, timestamp, sequenceNumberDiff, rtpMarker, marker, sequenceGap);

    if(lTimestamp != timestamp) {
        if(frameLength && rtpMarker && !dropFrame) {
            Transcode((const byte *)videoFrame, frameLength, lTimestamp, 0, sequenceGap ? 1 : 0);
            sequenceGap = false;
        }
        else SendFullIntraRequest();
        if(sequenceNumberDiff) {
            dropFrame = true;
            sequenceGap = true;
        }
        else dropFrame = false;
        frameLength = 0;
        frameLengthFua = 0;
        fragSb = 0;
        fuaTimestamp = 0;
    }
    else if(sequenceNumberDiff) {
        dropFrame = true;
        sequenceGap = true;
    }
    rtpMarker = marker;

    if(nalUnitType < RTP_STAP_A) {
        memcpy(&videoFrame[frameLength], startCode, startCodeSize);
        memcpy(&videoFrame[frameLength + startCodeSize], buf, len);
        frameLength += startCodeSize + len;
    }
    else if(nalUnitType == RTP_STAP_A) {
        int indPkt = 1;

        while(indPkt < len) {
            int nalLength = READ_WORD_NET(&buf[indPkt]);

            if((2+nalLength) > len) {
                break;
            }
            indPkt += 2;
            nalUnitType = (buf[indPkt] & 0x1F);
            if ((frameLength == 0) && ((nalUnitType == NAL_UNIT_IDR) || (nalUnitType == NAL_UNIT_SPS) || (nalUnitType == NAL_UNIT_PPS))) {
                log->Log(LOG_VIDEO, "VideoChannel(%p)::recv h264 IDR", this);
            }
            memcpy(&videoFrame[frameLength], startCode, startCodeSize);
            memcpy(&videoFrame[frameLength + startCodeSize], &buf[indPkt], nalLength);
            frameLength += startCodeSize + nalLength;

            indPkt += nalLength;
        }
    }
    else if(nalUnitType == RTP_FU_A) {
        if(buf[1] & 0x80) {  // Start Bit (not allowed start and end bit inside the same packet)
            int nalRefIdc;

            if(fragSb) {
                log->Log(LOG_VIDEO, "VideoChannel(%p)::recv Error: start bit already set", this);
                frameLengthFua = 0;
                fragSb = 0;
                fuaTimestamp = 0;
            }
            nalRefIdc = buf[0] & 0xE0;
            nalUnitType = buf[1] & 0x1F;

            if ((frameLength == 0) && ((nalUnitType == NAL_UNIT_IDR) || (nalUnitType == NAL_UNIT_SPS) || (nalUnitType == NAL_UNIT_PPS))) {
                log->Log(LOG_VIDEO, "VideoChannel(%p)::recv h264 IDR", this);
            }
            memcpy(&videoFrame[frameLength], startCode, startCodeSize);
            videoFrame[frameLength + startCodeSize] = (nalRefIdc | nalUnitType);
            memcpy(&videoFrame[frameLength + startCodeSize + 1], &buf[2], (len-2));
            frameLengthFua = startCodeSize + (len-1);

            fragSb = 1;
            fuaTimestamp = timestamp;
        }
        else {
            if(!frameLengthFua || !fragSb || (fuaTimestamp != timestamp) || sequenceNumberDiff) {
                log->Log(LOG_VIDEO, "VideoChannel(%p)::recv No start Bit available, no data, different timestamp or frame lost (%d,%d,%d,%d) ts=%d",
                    this, !frameLengthFua, !fragSb, (fuaTimestamp != timestamp), sequenceNumberDiff, lTimestamp);
                fragSb = 0;
                fuaTimestamp = 0;
                frameLengthFua = 0;
            }
            else {
                memcpy(&videoFrame[frameLength+frameLengthFua], &buf[2], len-2);
                frameLengthFua += len-2;
                if(buf[1] & 0x40) {  // End Bit
                    fragSb = 0;
                    fuaTimestamp = 0;
                    frameLength += frameLengthFua;
                }
            }
        }
    }
    else if(nalUnitType <= RTP_MTAP24) {
        log->Log(LOG_VIDEO, "VideoChannel(%p)::recv aggregation type not implemented yet", this);
    }
    else if(nalUnitType == RTP_FU_B) {
        log->Log(LOG_VIDEO, "VideoChannel(%p)::recv fragmentation type FU_B not implemented yet", this);
    }
    else {
        log->Log(LOG_VIDEO, "VideoChannel(%p)::recv Unknown NAL type received(%d)=%d", this, nalUnitType);
    }
    lTimestamp = timestamp;
}

void IosVideoIoChannel::RecvVp8(const byte * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    log->Log(LOG_VIDEO, "VideoChannel(%p)::Recv vp8 %d ql %d %x!=%x fragSb=%d picId=%d rtpMarker=%d,%d dropFrame=%d sequenceNumberDiff=%d gapSequence=%d", this, len, frameLength, timestamp, lTimestamp, fragSb, rxVp8PicId, rtpMarker, marker, dropFrame, sequenceNumberDiff, sequenceGap);

    if(lTimestamp != timestamp) {
        if(frameLength && rtpMarker && !dropFrame) {
            Transcode((const byte *)videoFrame, frameLength, lTimestamp, 0, sequenceGap ? 1 : 0);
            sequenceGap = false;
        }
        else {
            SendFullIntraRequest();
        }
        if(sequenceNumberDiff) {
            dropFrame = true;
            sequenceGap = true;
        }
        else dropFrame = false;
        frameLength = 0;
        fragSb = 0;
        rxVp8PicId = 0;
    }
    else if(sequenceNumberDiff) {
        dropFrame = true;
        sequenceGap = true;
    }
    rtpMarker = marker;

    int rxPid = 0;
    int offHdr = 1;                        // Payload descriptor
    if(buf[0] & 0x80) {                    // Check payload descriptor for Extension header
        offHdr++;
        if(buf[1] & 0x80) {                // Picture ID present
            if(buf[2] & 0x80) {            // Long Picture ID
                rxPid = (((word)(buf[2] & 0x7f))<<8) + (byte)buf[3];
                offHdr += 2;
            }
            else {
                rxPid = buf[2];
                offHdr++;
            }
        }
        if(buf[1] & 0x40) offHdr++;         // TL0PICIDX byte present
        if(buf[1] & 0x20) offHdr++;         // TID byte present, TID/KEYIDX octet
        else if(buf[1] & 0x10) offHdr++;    // KEYIDX byte present, TID/KEYIDX octet
    }
    else rxPid = buf[0] & 0x7;

    if(!frameLength) {
        fragSb = (buf[0] & 0x10) ? 1 : 0;
        if(fragSb) {
            rxVp8PicId = rxPid;
            if (!(buf[0] & 0x07) && !(buf[offHdr] & 0x01)) {
                log->Log(LOG_VIDEO, "VideoChannel(%p)::recv vp8 keyframe", this);
            }
        }
    }

    if(fragSb && (rxVp8PicId == rxPid)) {
        memcpy(&videoFrame[frameLength], &buf[offHdr], len-offHdr);
        frameLength += len-offHdr;
    }
    //else debug->printf("discard frame(%d) %d %d,%d buf %x,%x len=%d,%d,%d", (buf[0] & 0x80), fragSb, rxVp8PicId, rxPid, buf[2], buf[3], frameLength, len, offHdr);

    lTimestamp = timestamp;
}
*/
/*
void IosVideoIoChannel::ForwardRtp(const void * buf, int len, dword timestamp, dword duration, short sequenceNumberDiff, bool marker, enum VideoCoder videoIoChannelCoder)
{
    if (connType == ConnectionType::REMOTE_VIDEO_STREAM) {
        if (!stop_feed_containers) {
            if (videoIoChannelRtpCoder == coder) {
                VideoIoChannel::ForwardRtp(buf, len, timestamp, duration, sequenceNumberDiff, marker, videoIoChannelCoder);
            }
            else {
                Recv((const byte *)buf, len, timestamp, sequenceNumberDiff, marker);
            }
        }
    }
    else {
        if ((connType == ConnectionType::LOCAL_VIDEO_STREAM) && webcam_preview_data && (webcam_preview_length != 0)) {
            if (!stop_feed_containers) {
                VideoIoChannel::VideoSample(webcam_preview_data, webcam_preview_length, timestamp, duration);
            }
        }
        else if (buf && (len != 0)) {
            VideoIoChannel::VideoSample(buf, len, timestamp, duration);
        }
    }
}
*/

/*
void phone_video_transcode(int id, dword timestamp, dword duration, short sequenceDiff, int length, const byte *data, int width, int height, int stride, bool keyFrame)
{
    IosVideoIoChannel * ch = transcoderToChannelMap[id];
    if (!ch) return;
    if (data) {
        ch->VideoIoChannel::VideoSample(data, length, timestamp, duration);
    }
    else {
        transcoderToChannelMap[id] = NULL;
        ch->transcoder_id = -1;
        ch->TryClose();
    }
}
*/

/*---------------------------------------------------------------------------*/
