//
// PT3Tuner.cpp
//

#include <io.h>
#include <new>
#include <share.h>
#include <winsock2.h>

#define DBG_LEVEL 0
#include "Raym/Log.h"
#include "ry0/device/PT3/PT3Tuner.h"
#include "ry0/device/PT3/PT3Core.h"
#include "EX_Buffer.h"

namespace ry0
{
namespace device
{
namespace PT3
{

static HMODULE module_ = NULL; // for scan

int PT3Tuner::scan(Tuner *tuners[], HMODULE multi2_dll)
{
    DebugLog2("%s()", __FUNCTION__);

    if (module_ == NULL)
    {
        module_ = ::LoadLibrary(L"SDK_EARTHSOFT_PT3.dll");
        if (module_ == NULL)
        {
            DebugLog1("Can't load library: SDK_EARTHSOFT_PT3.dll");
            return -1;
        }
    }

    EARTH::PT::Bus::NewBusFunction function = reinterpret_cast<EARTH::PT::Bus::NewBusFunction>(::GetProcAddress(module_, "_"));
    if (function == NULL)
    {
        DebugLog0("internal error.");
        return -1;
    }

    EARTH::PT::Bus *bus = NULL;
    EARTH::status status = function(&bus);
    if (status != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: NewBusFunction() 0x%08x", status);
        return -1;
    }

    EARTH::uint32 version;
    bus->GetVersion(&version);
    if ((version >> 8) != 2)
    {
        DebugLog3("no support version");
        return -1;
    }


    EARTH::PT::Bus::DeviceInfo deviceInfo[ry0::device::MAX_DEVICES];
    EARTH::uint32 deviceInfoCount = sizeof(deviceInfo)/sizeof(*deviceInfo);
    bus->Scan(deviceInfo, &deviceInfoCount);

    DebugLog3("deviceInfoCount = %d", deviceInfoCount);

    int tunerCount = 0;

    for (EARTH::uint32 i = 0; i < deviceInfoCount; ++i)
    {
        try
        {
            PT3Core *core = new PT3Core(bus, &deviceInfo[i], &tuners[tunerCount], multi2_dll);
            tunerCount += PT3Core::MAX_TUNERS;
        }
        catch (int e)
        {
            DebugLog0("throw %d", e);
        }
        catch (const std::bad_alloc& e)
        {
            DebugLog0("throw bad_alloc %s", e);
        }
    }

    return tunerCount;
}

unsigned __stdcall PT3Tuner_transfer(void *arg)
{
    ((PT3Tuner *)arg)->transfer();
    return 0;
}

PT3Tuner::PT3Tuner(PT3Core *core, uint32_t tuner, HMODULE multi2_dll)
{
    DebugLog2("PT3Tuner::PT3Tuner()");

    InitializeCriticalSection(&_cs);

    EnterCriticalSection(&_cs);

    _core = core;
    _tuner = tuner;
    _listener = NULL;
    memset(_name, sizeof(_name), 0x00);
    _channel = -1;
    _recording = false;
    _recfd = -1;

    _blockIndex = 0;
    _buffer = NULL;

    _b25 = NULL;
    _bcas = NULL;

    while (multi2_dll != NULL)
    {
        // create "B25" & initialize
        ARIB_STD_B25 *(*func_b25)();
        func_b25 = reinterpret_cast<ARIB_STD_B25 *(*)()>(::GetProcAddress(multi2_dll, "create_arib_std_b25"));
        if (func_b25 == NULL)
        {
            DebugLog0("b25 func address get ng.");
            break;
        }
        _b25 = func_b25();
        if (_b25 == NULL)
        {
            DebugLog0("b25 create ng.");
            break;
        }

        int ret = _b25->set_multi2_round(_b25, 4);
        if (ret != 0)
        {
            _b25->release(_b25);
            _b25 = NULL;
            DebugLog0("b25 set multi2 round ng. %d", ret);
            break;
        }

        ret = _b25->set_strip(_b25, 0);
        if (ret != 0)
        {
            _b25->release(_b25);
            _b25 = NULL;
            DebugLog0("b25 set strip ng. %d", ret);
            break;
        }

        ret = _b25->set_emm_proc(_b25, 0);
        if (ret != 0)
        {
            _b25->release(_b25);
            _b25 = NULL;
            DebugLog0("b25 set emm proc ng. %d", ret);
            break;
        }

        // create "B-CAS" & initialize
        B_CAS_CARD *(*func_bcas)();
        func_bcas = reinterpret_cast<B_CAS_CARD *(*)()>(::GetProcAddress(multi2_dll, "create_b_cas_card"));
        if (func_bcas == NULL)
        {
            // 関数アドレス取得NG
            DebugLog0("bcas func address get ng.");
            break;
        }
        _bcas = func_bcas();
        if (_bcas == NULL)
        {
            _b25->release(_b25);
            _b25 = NULL;
            DebugLog0("bcas create ng.");
            break;
        }

        ret = _bcas->init(_bcas);
        if (ret != 0)
        {
            _b25->release(_b25);
            _b25 = NULL;
            _bcas->release(_bcas);
            _bcas = NULL;
            DebugLog0("bcas init ng. %d", ret);
            break;
        }
        ret = _b25->set_b_cas_card(_b25, _bcas);
        if (ret != 0)
        {
            _b25->release(_b25);
            _b25 = NULL;
            _bcas->release(_bcas);
            _bcas = NULL;
            DebugLog0("b25 set bcas card ng. %d", ret);
            break;
        }

        DebugLog2("b25 & bcas initialize success.");

        break;
    }

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,0), &wsaData);

    if ((_udp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
    {
        DebugLog0("fatal error occured: socket(): 0x%08x\n", WSAGetLastError());
        abort();
    }
    struct sockaddr_in own_addr;
    own_addr.sin_family = AF_INET;
    own_addr.sin_port = 0;
    own_addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(_udp, (struct sockaddr *)&own_addr, sizeof(own_addr)) == SOCKET_ERROR)
    {
        DebugLog0("fatal error occured: bind()\n");
        abort();
    }
    _dst_addr.sin_family = AF_UNSPEC;

    HANDLE h;
    unsigned int uiThreadId;
    h = (HANDLE)_beginthreadex(NULL,
                               0,
                               PT3Tuner_transfer,
                               this,
                               0,
                               &uiThreadId);
    if (h != NULL)
    {
        _transfer = ST_READY;

        LeaveCriticalSection(&_cs);

        bool done = false;
        while (!done)
        {
            bool needSleep = false;
            EnterCriticalSection(&_cs);

            if (_transfer == ST_IDLE)
            {
                done = true;
            }
            else if (_transfer == ST_RUN)
            {
                done = true;
            }
            else if (_transfer == ST_READY)
            {
                needSleep = true;
            }
            LeaveCriticalSection(&_cs);

            if (needSleep)
            {
                ::Sleep(100); // 100 ms
            }
        }

        EnterCriticalSection(&_cs);
    }
    else
    {
        throw EXCEPTION_OTHER;
    }

    LeaveCriticalSection(&_cs);
}

PT3Tuner::~PT3Tuner()
{
    DebugLog3("PT3Tuner::~PT3Tuner() called.\n");
    EnterCriticalSection(&_cs);
    if (_bcas != NULL)
    {
        _bcas->release(_bcas);
        _bcas = NULL;
    }
    if (_b25 != NULL)
    {
        _b25->release(_b25);
        _b25 = NULL;
    }
    if (_recfd != -1)
    {
        _recfd = -1;
    }
    if (_dst_addr.sin_family == AF_INET)
    {
        _dst_addr.sin_family = AF_UNSPEC;
    }
    _listener = NULL;

    if (_transfer == ST_RUN)
    {
        _transfer = ST_STOP;
        LeaveCriticalSection(&_cs);
        bool done = false;
        while (!done)
        {
            EnterCriticalSection(&_cs);
            done = (_transfer == ST_IDLE);
            LeaveCriticalSection(&_cs);
        }
        EnterCriticalSection(&_cs);
    }

    closesocket(_udp);
    delete _buffer;

    LeaveCriticalSection(&_cs);

    _core->release(this);

    DeleteCriticalSection(&_cs);

    DebugLog3("PT3Tuner::~PT3Tuner()\n");
}

void PT3Tuner::setListener(Listener *listener)
{
    DebugLog2("PT3Tuner::setListener()\n");
    EnterCriticalSection(&_cs);
    _listener = listener;
    LeaveCriticalSection(&_cs);
    DebugLog2("PT3Tuner::setListener() end\n");
}

const char *PT3Tuner::name()
{
    DebugLog2("PT3Tuner::name()\n");

    EnterCriticalSection(&_cs);
    if (strlen(_name) == 0)
    {
        sprintf_s(_name, sizeof(_name), "%s-%02d", _core->name(), _tuner);
    }
    LeaveCriticalSection(&_cs);

    return _name;
}

Tuner::Type PT3Tuner::type()
{
//    DebugLog2("PT3Tuner::type()\n");

    switch (_tuner)
    {
    case 0:
    case 2:
        return ISDB_S;
    case 1:
    case 3:
        return ISDB_T;
    }
    return TYPE_NA;
}

Tuner::LnbPower PT3Tuner::lnbPower()
{
    DebugLog2("PT3Tuner::lnbPower()\n");

    return LNB_POWER_OFF;
}

bool PT3Tuner::getCnAgc(uint32_t *cn100, uint32_t *agc, uint32_t *maxAgc)
{
    DebugLog2("PT3Tuner::getCnAgc()\n");
    bool result = false;
    if ((cn100 != NULL) && (agc != NULL) && (maxAgc != NULL))
    {
        result = _core->getCnAgc(_tuner, (EARTH::uint32 *)cn100, (EARTH::uint32 *)agc, (EARTH::uint32 *)maxAgc);
    }
    return result;
}

int PT3Tuner::channel()
{
    int result = -1;
    EnterCriticalSection(&_cs);
    result = _channel;
    LeaveCriticalSection(&_cs);
    return result;
}

bool PT3Tuner::setChannel(int channel)
{
    bool result = false;
    EnterCriticalSection(&_cs);
    if (_channel != channel)
    {
        if (!_recording)
        {
            result = _core->setChannel(_tuner, channel);
            if (result)
            {
                _channel = channel;
            }
            else
            {
                _channel = -1;
            }
        }
    }
    else
    {
        result = true;
    }
    LeaveCriticalSection(&_cs);
    return result;
}

bool PT3Tuner::startRecording(int fd)
{
    bool result = false;
    EnterCriticalSection(&_cs);
    if ((!_recording) && (!_locked) && (fd >= 0))
    {
        _recording = true;
        _locked = true;
        _recfd = fd;
        result = true;
    }
    LeaveCriticalSection(&_cs);
    return result;
}

int PT3Tuner::stopRecording()
{
    int result = -1;
    EnterCriticalSection(&_cs);
    if (_recording)
    {
        _recording = false;
        if (_dst_addr.sin_family != AF_INET)
        {
            _locked = false;
        }
        result = _recfd;
        _recfd = -1;
    }
    LeaveCriticalSection(&_cs);
    return result;
}

bool PT3Tuner::isRecording()
{
    bool result = false;
    EnterCriticalSection(&_cs);
    result = _recording;
    LeaveCriticalSection(&_cs);
    return result;
}

bool PT3Tuner::lock()
{
    bool result = false;
    EnterCriticalSection(&_cs);
    if (!_locked)
    {
        _locked = true;
        result = true;
    }
    LeaveCriticalSection(&_cs);
    return result;
}

void PT3Tuner::unlock()
{
    EnterCriticalSection(&_cs);
    if (_locked)
    {
        if (!_recording)
        {
            _locked = false;
        }
    }
    LeaveCriticalSection(&_cs);
}

bool PT3Tuner::isLocked()
{
    bool result = false;
    EnterCriticalSection(&_cs);
    result = _locked;
    LeaveCriticalSection(&_cs);
    return result;
}

bool PT3Tuner::startStreaming(struct sockaddr_in *addr)
{
    bool result = false;
    EnterCriticalSection(&_cs);
    if (_dst_addr.sin_family == AF_UNSPEC)
    {
        if (addr->sin_family == AF_INET)
        {
            _dst_addr = *addr;
            _locked = true;
            result = true;
        }
    }
    LeaveCriticalSection(&_cs);
    return result;
}

void PT3Tuner::stopStreaming()
{
    EnterCriticalSection(&_cs);
    if (_dst_addr.sin_family == AF_INET)
    {
        _dst_addr.sin_family = AF_UNSPEC;
        if (!_recording)
        {
            _locked = false;
        }
    }
    LeaveCriticalSection(&_cs);
}

bool PT3Tuner::isStreaming()
{
    bool result = false;
    EnterCriticalSection(&_cs);
    result = (_dst_addr.sin_family == AF_INET);
    LeaveCriticalSection(&_cs);
    return result;
}

bool PT3Tuner::checkReady(uint32_t blockIndex)
{
    volatile EARTH::uint8 *ptr = static_cast<EARTH::uint8 *>(_buffer->Ptr(static_cast<EARTH::uint32>(blockIndex)));
    if (ptr == NULL)
    {
        return false;
    }

    EARTH::uint8 data = ptr[0];
    EARTH::status status = _buffer->SyncCpu(blockIndex);
    if (status != EARTH::PT::STATUS_OK)
    {
        return false;
    }

    if (data == 0x47)
    {
        return true;
    }

    if (data == NOT_SYNC_BYTE)
    {
        return false;
    }

    return false;
}

void PT3Tuner::transfer()
{
    DebugLog2("PT3Tuner::transfer() called.\n");

    EnterCriticalSection(&_cs);

    while (true)
    {
        DebugLog2("PT3Tuner[%02d]::transfer() Begin:\n", _tuner);

        // Begin()
        EARTH::status status;

        _blockIndex = 0;

        if (_buffer == NULL)
        {
            _buffer = new EARTH::EX::Buffer(_core->_device);

            status = _buffer->Alloc(BLOCK_SIZE, BLOCK_COUNT);
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog0("_buffer->Alloc() NG\n");
                LeaveCriticalSection(&_cs);
                break;
            }
        }

        EARTH::uint8 *ptr = static_cast<EARTH::uint8 *>(_buffer->Ptr(0));
        if (ptr == NULL)
        {
            DebugLog0("_buffer->Ptr() NG: 01\n");
            LeaveCriticalSection(&_cs);
            break;
        }

        bool sync_error = false;
        for (EARTH::uint32 i = 0; i < BLOCK_COUNT; ++i)
        {
            ptr[BLOCK_SIZE * i] = NOT_SYNC_BYTE;
            status = _buffer->SyncCpu(i);

            if (status != EARTH::PT::STATUS_OK)
            {
                sync_error = true;
                break;
            }
        }
        if (sync_error)
        {
            DebugLog0("sync_error\n");
            LeaveCriticalSection(&_cs);
            break;
        }

        status = _core->setTransferTestMode(type(), _tuner);
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog0("_core->setTransferTestMode() NG\n");
            LeaveCriticalSection(&_cs);
            break;
        }


        EARTH::uint64 pageAddress = _buffer->PageDescriptorAddress();

        status = _core->setTransferPageDescriptorAddress(type(), _tuner, pageAddress);
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog0("_core->setTransferPageDescriptorAddress() NG\n");
            LeaveCriticalSection(&_cs);
            break;
        }

        status = _core->setTransferEnabled(type(), _tuner, true);
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog0("_core->setTransferEnabled() NG\n");
            LeaveCriticalSection(&_cs);
            break;
        }

        ptr = static_cast<EARTH::uint8 *>(_buffer->Ptr(0));
        if (ptr == NULL)
        {
            DebugLog0("_buffer->Ptr() NG: 02\n");
            LeaveCriticalSection(&_cs);
            break;
        }

        _transfer = ST_RUN;

        LeaveCriticalSection(&_cs);

        DebugLog2("PT3Tuner[%02d]::transfer() Loop:\n", _tuner);

        bool done = false;
        while (!done)
        {
            //OS::Thread::Sleep(1);
            ::Sleep(1); // 1ms

            EARTH::uint32 nextWriteIndex = _blockIndex + 1;
            if (BLOCK_COUNT <= nextWriteIndex)
            {
                nextWriteIndex = 0;
            }

//            DebugLog3("PT3Tuner[%02d]::transfer() before checkReady:\n", _tuner);
            while (checkReady(nextWriteIndex))
            {
                //Write(_blockIndex);

//                DebugLog3("PT3Tuner[%02d]::transfer() check 001\n", _tuner);

                status = _buffer->SyncIo(_blockIndex);
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog0("SyncIo error.\n");
                    break;
                }

//                DebugLog3("PT3Tuner[%02d]::transfer() check 002\n", _tuner);

                ptr = static_cast<EARTH::uint8 *>(_buffer->Ptr(_blockIndex));
                if (ptr == NULL)
                {
                    DebugLog0("_buffer->Ptr() error.\n");
                    break;
                }

//                DebugLog3("PT3Tuner[%02d]::transfer() check 003\n", _tuner);

                EnterCriticalSection(&_cs);

                uint8_t *buf = ptr;
                int32_t size = BLOCK_SIZE;
                if (_b25 != NULL)
                {
                    // MULTI2 Decode
                    ARIB_STD_B25_BUFFER sBuffer, dBuffer;
                    sBuffer.data = buf;
                    sBuffer.size = size;
                    dBuffer.data = NULL;
                    dBuffer.size = 0;

                    int ret = _b25->put(_b25, &sBuffer);
                    if (ret >= 0)
                    {
                        ret = _b25->get(_b25, &dBuffer);
                        if (ret >= 0)
                        {
                            buf = dBuffer.data;
                            size = dBuffer.size;
                        }
                        else
                        {
                            DebugLog3("_b25->get() NG. %d\n", ret);
                            _b25->reset(_b25);
                        }
                    }
                    else
                    {
                        DebugLog3("_b25->put() NG. %d\n", ret);
                        _b25->reset(_b25);
                    }
                }

//                DebugLog3("PT3Tuner[%02d]::transfer() check 004\n", _tuner);

                // recording
                if (_recfd >= 0)
                {
                    _write(_recfd, buf, size);
                }

//                DebugLog3("PT3Tuner[%02d]::transfer() check 005\n", _tuner);

                // streaming
                if (_dst_addr.sin_family == AF_INET)
                {
#if 1
                    int32_t offset = 0;
                    int32_t unit = 188 * 4;
                    int32_t remain = size;
                    while (remain > 0)
                    {
                        if (remain < unit)
                        {
                            unit = remain;
                        }
                        int len = sendto(_udp, (const char *)&buf[offset], unit, 0, (struct sockaddr *)&_dst_addr, sizeof(struct sockaddr_in));
                        if (len > 0)
                        {
                            remain -= len;
                            offset += len;
                        }
                        else
                        {
                            char tmp[1024];
                            strerror_s(tmp, sizeof(tmp), errno);
                            DebugLog3("len = %d, %s\n", len, tmp);
                            break;
                        }
                    }
#else
                    sendto(_udp, (const char *)buf, size, 0, (struct sockaddr *)&_dst_addr, sizeof(struct sockaddr_in));
#endif
                }
        
//                DebugLog3("PT3Tuner[%02d]::transfer() check 006\n", _tuner);

                // listener
                if (_listener != NULL)
                {
                    DebugLog3("check \n");
                    _listener->put(buf, size);
                }

//                DebugLog3("PT3Tuner[%02d]::transfer() check 007\n", _tuner);

                LeaveCriticalSection(&_cs);


                ptr[0] = NOT_SYNC_BYTE;

//                DebugLog3("PT3Tuner[%02d]::transfer() check 008\n", _tuner);
                status = _buffer->SyncCpu(_blockIndex);
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog0("SyncCpu error.\n");
                    break;
                }

//                DebugLog3("PT3Tuner[%02d]::transfer() check 009\n", _tuner);
                _blockIndex++;
                if (BLOCK_COUNT <= _blockIndex)
                {
                    _blockIndex = 0;
                }
//                DebugLog3("PT3Tuner[%02d]::transfer() check 010\n", _tuner);

                break;
            }

            EnterCriticalSection(&_cs);
            done = (_transfer != ST_RUN);
            LeaveCriticalSection(&_cs);
        }

        DebugLog2("PT3Tuner[%02d]::transfer() End:\n", _tuner);

        // End
        status = _core->setTransferEnabled(type(), _tuner, false);
        if (status != EARTH::PT::STATUS_OK)
        {
        }

        if (done)
        {
            break;
        }
    }

    EnterCriticalSection(&_cs);
    _transfer = ST_IDLE;
    LeaveCriticalSection(&_cs);
}

} // PT3
} // device
} // ry0
