//
// PT1Tuner.cpp
//

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

#define DBG_LEVEL 0
#include "Raym/Log.h"
#include "ry0/device/PT1/PT1Tuner.h"
#include "ry0/device/PT1/PT1Core.h"

namespace ry0
{
namespace device
{
namespace PT1
{

static HMODULE module_ = NULL; // for scan

PT1Tuner::PT1Tuner(PT1Core *core, uint32_t tuner, HMODULE multi2_dll)
{
    DebugLog2("PT1Tuner::PT1Tuner()\n");

    InitializeCriticalSection(&_cs);

    EnterCriticalSection(&_cs);

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

    _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);

    // temporary
    if ((_udp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
    {
        DebugLog0("fatal error occured: socket(): 0x%08x", 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;

    LeaveCriticalSection(&_cs);
}

PT1Tuner::~PT1Tuner()
{
    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;

    LeaveCriticalSection(&_cs);

    _core->release(this);

    closesocket(_udp);

    DeleteCriticalSection(&_cs);

    DebugLog2("PT1Tuner::~PT1Tuner()\n");
}

void PT1Tuner::setListener(Listener *listener)
{
    EnterCriticalSection(&_cs);
    _listener = listener;
    LeaveCriticalSection(&_cs);
}

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

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

    return _name;
}

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

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

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

    return LNB_POWER_OFF;
}

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

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

bool PT1Tuner::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 PT1Tuner::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 PT1Tuner::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 PT1Tuner::isRecording()
{
    bool result = false;
    EnterCriticalSection(&_cs);
    result = _recording;
    LeaveCriticalSection(&_cs);
    return result;
}

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

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

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

bool PT1Tuner::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 PT1Tuner::stopStreaming()
{
    EnterCriticalSection(&_cs);
    if (_dst_addr.sin_family == AF_INET)
    {
        _dst_addr.sin_family = AF_UNSPEC;
        if (!_recording)
        {
            _locked = false;
        }
    }
    LeaveCriticalSection(&_cs);
}

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


void PT1Tuner::addPacket(uint32_t packet)
{
//    DebugLog3("%s\n", __FUNCTION__);
//    return;

    uint32_t packetCounter = BIT_SHIFT_MASK(packet, 26,  3);
    uint32_t packetStart   = BIT_SHIFT_MASK(packet, 25,  1);
    uint32_t packetData    = BIT_SHIFT_MASK(packet,  0, 24);

    // check counter
    uint32_t count = BIT_SHIFT_MASK(_count, 0, 3);
    _count++;

    if (packetCounter != count)
    {
//        DebugLog3("counter value is invalid.\n");
    }

    // check packet start offset flag
    if (packetStart)
    {
        if (BIT_SHIFT_MASK(packetData, 15, 1))
        {
//            DebugLog3("There is an error that could not be corrected by Reed-Solomon decoding\n");
        }
        else
        {
            // check sync byte
            if (BIT_SHIFT_MASK(packetData, 16, 8) != 0x47)
            {
//                DebugLog3("The first byte of the packet does not have an 0x47.\n");
            }
        }

        if (_packetOffset != 0)
        {
//            DebugLog3("The previous packet was not completed.\n");
        }
        _packetOffset = 0;
    }

    // copy
    uint32_t i;
    for (i = 0; i < 3; ++i)
    {
        if (_packetOffset < PT1Core::PACKET_SIZE)
        {
            _buffer[PT1Core::PACKET_SIZE * _packetCount + _packetOffset] = BIT_SHIFT_MASK(packetData, 8*(2-i), 8);
            _packetOffset++;
        }
    }
    if (PT1Core::PACKET_SIZE <= _packetOffset)
    {
        // One packet is complete
        _packetOffset = 0;

        if (PT1Core::PAGE_SIZE * PT1Core::PAGE_COUNT / PT1Core::PACKET_SIZE <= ++_packetCount)
        {
            _packetCount = 0;

            uint8_t *ptr = _buffer;
            int32_t size = (PT1Core::PAGE_SIZE * PT1Core::PAGE_COUNT);

            EnterCriticalSection(&_cs);

            if (_b25 != NULL)
            {
                // MULTI2 Decode
                ARIB_STD_B25_BUFFER sBuffer, dBuffer;
                sBuffer.data = _buffer;
                sBuffer.size = (PT1Core::PAGE_SIZE * PT1Core::PAGE_COUNT);
                dBuffer.data = NULL;
                dBuffer.size = 0;

                int ret = _b25->put(_b25, &sBuffer);
                if (ret >= 0)
                {
                    ret = _b25->get(_b25, &dBuffer);
                    if (ret >= 0)
                    {
                        ptr = 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);
                }
            }

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

            // streaming
            if (_dst_addr.sin_family == AF_INET)
            {
#if 1
                int32_t offset = 0;
                int32_t unit = 188 * 4;
//                int32_t unit = 188 * 8;
                int32_t remain = size;
                while (remain > 0)
                {
                    if (remain < unit)
                    {
                        unit = remain;
                    }
                    int len = sendto(_udp, (const char *)&ptr[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", len, tmp);
                        break;
                    }
                }
#else
                sendto(_udp, (const char *)ptr, size, 0,  (struct sockaddr *)&_dst_addr, sizeof(struct sockaddr_in));
#endif                
            }
        
             // listener
            if (_listener != NULL)
            {
                _listener->put(ptr, size);
            }

            LeaveCriticalSection(&_cs);

        }
    }
}

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

    if (module_ == NULL)
    {
        module_ = ::LoadLibrary(L"SDK_EARTHSOFT_PT1_PT2.dll");
        if (module_ == NULL)
        {
            DebugLog0("Can't load library: ");
            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::uint version;
    bus->GetVersion(&version);
    if ((version >> 8) != 2)
    {
        DebugLog3("no support version");
        return -1;
    }

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

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

    int tunerCount = 0;

    for (EARTH::uint i = 0; i < deviceInfoCount; ++i)
    {
        const EARTH::PT::Bus::DeviceInfo *info = &deviceInfo[i];
        if (info->BadBitCount == 0)
        {
            try
            {
                PT1Core *core = new PT1Core(bus, info, &tuners[tunerCount], multi2_dll);
                tunerCount += PT1Core::MAX_TUNERS;
            }
            catch (int e)
            {
                DebugLog0("throw %d", e);
            }
            catch (const std::bad_alloc& e)
            {
                DebugLog0("throw bad_alloc %s", e);
            }
        }
    }

    return tunerCount;
}

} // PT1
} // device
} // ry0


