//
// PT1Core.cpp
//

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

namespace ry0
{
namespace device
{
namespace PT1
{

unsigned __stdcall PT1Core_transfer(void *arg)
{
    ((PT1Core *)arg)->transfer();
    return 0;
}

PT1Core::PT1Core(EARTH::PT::Bus *bus, const EARTH::PT::Bus::DeviceInfo *deviceInfo, Tuner *tuners[MAX_TUNERS], HMODULE multi2_dll)
{
    DebugLog2("PT1Core::PT1Core()");

    InitializeCriticalSection(&_cs);

    // initialize: variables
    _device = NULL;
    for (EARTH::uint i = 0; i < 4; ++i)
    {
        _tuners[i] = NULL;
        _locked[i] = false;
    }
    _name[0] = '\0';
    _transfer = ST_IDLE;

    // initialize: PT1
    EARTH::status status;
    status = bus->NewDevice(deviceInfo, &_device, NULL);
    if (status != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Bus::NewDevice() (0x%08x)\n", status);
        throw EXCEPTION_NEW_DEVICE;
    }

    status = _device->Open();
    if (status != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Device::Open() (0x%08x)\n", status);
        int except = EXCEPTION_OPEN;
        status = _device->Delete();
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::Delete() (0x%08x)\n", status);
            except |= EXCEPTION_DELETE;
        }
        throw except;
    }

    status = _device->SetTunerPowerReset(EARTH::PT::Device::TUNER_POWER_ON_RESET_ENABLE);
    if (status != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Device::SetTunerPowerReset(TUNER_POWER_ON_RESET_ENABLE) (0x%08x)\n", status);
        int except = EXCEPTION_SET_TUNER_POWER_RESET;
        status = _device->Close();
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::Close() (0x%08x)\n", status);
            except |= EXCEPTION_CLOSE;
        }
        status = _device->Delete();
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::Delete() (0x%08x)\n", status);
            except |= EXCEPTION_DELETE;
        }
        throw except;
    }

    /*
    EARTH::OS::Thread::Sleep((20)*1000*1000); // 20 ms
    */
    ::Sleep(20);
    
    status = _device->SetTunerPowerReset(EARTH::PT::Device::TUNER_POWER_ON_RESET_DISABLE);
    if (status != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Device::SetTunerPowerReset(TUNER_POWER_ON_RESET_DISABLE) (0x%08x)\n", status);
        int except = EXCEPTION_SET_TUNER_POWER_RESET;
        status = _device->Close();
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::Close() (0x%08x)\n", status);
            except |= EXCEPTION_CLOSE;
        }
        status = _device->Delete();
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::Delete() (0x%08x)\n", status);
            except |= EXCEPTION_DELETE;
        }
        throw except;
    }

    /*
    EARTH::OS::Thread::Sleep((1)*1000*1000); // 1 ms
    */
    ::Sleep(1);

    for (EARTH::uint i = 0; i < 2; ++i)
    {
        status = _device->InitTuner(i);
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::InitTuner() (0x%08x)\n", status);
            int except = EXCEPTION_INIT_TUNER;
            status = _device->Close();
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::Close() (0x%08x)\n", status);
                except |= EXCEPTION_CLOSE;
            }
            status = _device->Delete();
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::Delete() (0x%08x)\n", status);
                except |= EXCEPTION_DELETE;
            }
            throw except;
        }
    }

    for (EARTH::uint i = 0; i < 2; ++i)
    {
        for (EARTH::uint j = 0; j < 2; ++j)
        {
            status = _device->SetTunerSleep(i, static_cast<EARTH::PT::Device::ISDB>(j), false);
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::SetTunerSleep() (0x%08x)\n", status);
                int except = EXCEPTION_SET_TUNER_SLEEP;
                status = _device->Close();
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::Close() (0x%08x)\n", status);
                    except |= EXCEPTION_CLOSE;
                }
                status = _device->Delete();
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::Delete() (0x%08x)\n", status);
                    except |= EXCEPTION_DELETE;
                }
                throw except;
            }
        }
    }

    for (EARTH::uint i = 0; i < 2; ++i)
    {
        for (EARTH::uint j = 0; j < 2; ++j)
        {
            status = _device->SetStreamEnable(i, static_cast<EARTH::PT::Device::ISDB>(j), true);
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::SetStreamEnable() (0x%08x)\n", status);
                int except = EXCEPTION_SET_STREAM_ENABLE;
                status = _device->Close();
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::Close() (0x%08x)\n", status);
                    except |= EXCEPTION_CLOSE;
                }
                status = _device->Delete();
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::Delete() (0x%08x)\n", status);
                    except |= EXCEPTION_DELETE;
                }
                throw except;
            }
        }
    }
    
    for (EARTH::uint i = 0; i < 2; ++i)
    {
        for (EARTH::uint j = 0; j < 2; ++j)
        {
            status = _device->SetStreamGray(i, static_cast<EARTH::PT::Device::ISDB>(j), 4 + i*2 + j);
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::SetStreamGray() (0x%08x)\n", status);
                int except = EXCEPTION_SET_STREAM_GRAY;
                status = _device->Close();
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::Close() (0x%08x)\n", status);
                    except |= EXCEPTION_CLOSE;
                }
                status = _device->Delete();
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::Delete() (0x%08x)\n", status);
                    except |= EXCEPTION_DELETE;
                }
                throw except;
            }
        }
    }

    for (EARTH::uint i = 0; i < 4; ++i)
    {
        _tuners[i] = new PT1Tuner(this, i, multi2_dll);
        tuners[i] = _tuners[i];
    }

    int ret = sprintf_s(_name, sizeof(_name), "PT%d@%02d%02d%02d",
                        deviceInfo->PTn,
                        deviceInfo->Bus,
                        deviceInfo->Slot,
                        deviceInfo->Function);
    if (ret < 0)
    {
        DebugLog3("error: snprintf() (0x%08x)\n", ret);
        int except = EXCEPTION_OTHER;
        status = _device->Close();
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::Close() (0x%08x)\n", status);
            except |= EXCEPTION_CLOSE;
        }
        status = _device->Delete();
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::Delete() (0x%08x)\n", status);
            except |= EXCEPTION_DELETE;
        }
        throw except;
    }

    // start thread
    EnterCriticalSection(&_cs);

    HANDLE h;
    unsigned int uiThreadId;
    h = (HANDLE)_beginthreadex(NULL,
                               0,
                               PT1Core_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);

}

PT1Core::~PT1Core()
{
    EARTH::status status = _device->Close();
    if (status != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Device::Close() (0x%08x)\n", status);
    }
    status = _device->Delete();
    if (status != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Device::Delete() (0x%08x)\n", status);
    }

    DeleteCriticalSection(&_cs);

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

void PT1Core::release(Tuner *tuner)
{
    DebugLog2("PT1Core::release()\n");

    // check tuners
    bool needStop = true;
    for (EARTH::uint i = 0; i < MAX_TUNERS; ++i)
    {
        if (_tuners[i] == NULL)
        {
            needStop = false;
            break;
        }
    }
    if (needStop)
    {
        // stop transfer thread
        EnterCriticalSection(&_cs);
        if (_transfer == ST_RUN)
        {
            DebugLog3("_transfer == ST_RUN\n");
            _transfer = ST_STOP;

            LeaveCriticalSection(&_cs);
            bool done = false;
            while (!done)
            {
                EnterCriticalSection(&_cs);
                done = (_transfer == ST_IDLE);
                LeaveCriticalSection(&_cs);
            }
            EnterCriticalSection(&_cs);
        }
        else
        {
            DebugLog3("koko\n");
        }
        LeaveCriticalSection(&_cs);
    }

    bool exist = false;
    for (EARTH::uint i = 0; i < 4; ++i)
    {
        if (_tuners[i] == tuner)
        {
            _tuners[i] = NULL;
        }
        else if (_tuners[i] != NULL)
        {
            exist = true;
        }
    }
    if (!exist)
    {
        DebugLog3("delete.\n");
        delete this;
    }
}

const char *PT1Core::name()
{
    DebugLog2("PT1Core::name()\n");
    return _name;
}

bool PT1Core::getCnAgc(EARTH::uint tuner, EARTH::uint *cn100, EARTH::uint *agc, EARTH::uint *maxAgc)
{
    DebugLog2("PT1Core::getCnAgc()\n");
    bool result = false;
    if ((tuner < MAX_TUNERS) && (cn100 != NULL) && (agc != NULL) && (maxAgc != NULL))
    {
        EnterCriticalSection(&_cs);
        EARTH::status status;
        status = _device->GetCnAgc(tuner / 2,
                                   tuner % 2 == 0 ? EARTH::PT::Device::ISDB_S : EARTH::PT::Device::ISDB_T,
                                   cn100, agc, maxAgc);
        if (status == EARTH::PT::STATUS_OK)
        {
            result = true;
        }
        LeaveCriticalSection(&_cs);
    }
    return result;
}

bool PT1Core::setChannel(EARTH::uint tuner, int channel)
{
    DebugLog2("TunerCore::setChannel(%d, %d)\n", channel, tuner);

    EARTH::status status;
    bool result = false;

    EnterCriticalSection(&_cs);

    if (tuner >= 0 && tuner < 4)
    {
        if ((tuner % 2) == 0)
        {
            // ISDB-S
            while ((0 <= channel) && (channel <= Tuner::MAX_CHANNELS_ISDB_S))
            {
                EARTH::PT::Device::TmccS tmcc;
                bool tmccIsValid = false;

                status = _device->SetFrequency((tuner / 2), EARTH::PT::Device::ISDB_S, channel, 0);
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::SetFrequency() (0x%08x)\n", status);
                    break;
                }

                for (int i = 0; i < 6; ++i)
                {
                    status = _device->GetTmccS((tuner / 2), &tmcc);
                    if (status == EARTH::PT::STATUS_OK)
                    {
                        tmccIsValid = true;
                        break;
                    }
                    else
                    {
                        DebugLog3("error: Device::GetTmccS() (0x%08x)\n", status);
                    }
                    ::Sleep(50);    // 50 ms
                }

                result = tmccIsValid;
                break;
            }
        }
        else
        {
            // ISDB-T
            while ((0 <= channel) && (channel <= Tuner::MAX_CHANNELS_ISDB_T))
            {
                EARTH::PT::Device::TmccT tmcc;
                bool tmccIsValid = false;

                status = _device->SetFrequency((tuner / 2), EARTH::PT::Device::ISDB_T, channel, 0);
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::SetFrequency() (0x%08x)\n", status);
                    break;
                }

                for (int i = 0; i < 2; ++i)
                {
                    status = _device->GetTmccT((tuner / 2), &tmcc);
                    if (status == EARTH::PT::STATUS_OK)
                    {
                        tmccIsValid = true;
                        break;
                    }
                    else
                    {
                        DebugLog3("error: Device::GetTmccT() (0x%08x)\n", status);
                    }
                }

                result = tmccIsValid;
                break;
            }
        }
        _locked[tuner] = result;
    }

    LeaveCriticalSection(&_cs);

    return result;
}

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

    while (true)
    {
        EARTH::status status;

        EARTH::PT::Device::BufferInfo bufferInfo;
        status = _device->GetBufferInfo(&bufferInfo);
        if (status != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::GetBufferInfo() (0x%08x)\n", status);
            break;
        }

        if (bufferInfo.VirtualSize == 0)
        {
            bufferInfo.VirtualSize  = VIRTUAL_SIZE;
            bufferInfo.VirtualCount = VIRTUAL_COUNT;
            bufferInfo.LockSize     = LOCK_SIZE;
            status = _device->SetBufferInfo(&bufferInfo);
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::SetBufferInfo() (0x%08x)\n", status);
                break;
            }
        }

        EnterCriticalSection(&_cs);
        _transfer = ST_RUN;
        LeaveCriticalSection(&_cs);

        bool done = false;
        while (!done)
        {
            // In order to examine to what extent DMA transfer or advanced, is cleared to 0 the end of each block
            for (int i = 0; i < VIRTUAL_COUNT; ++i)
            {
                for (int j = 0; j < VIRTUAL_SIZE; ++j)
                {
                    for (int k = 0; k < BLOCK_COUNT; ++k)
                    {
                        clearBlock(i, j, k);
                    }
                }
            }
            
            // reset transfer counter
            status = _device->ResetTransferCounter();
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::ResetTransferCounter() (0x%08x)\n", status);
                break;
            }
            
            // increment transfer counter
            bool flag = false;
            for (int i = 0; i < VIRTUAL_SIZE * VIRTUAL_COUNT; ++i)
            {
                status = _device->IncrementTransferCounter();
                if (status != EARTH::PT::STATUS_OK)
                {
                    DebugLog3("error: Device::IncrementTransferCounter() (0x%08x)\n", status);
                    flag = true;
                    break;
                }
            }
            if (flag)
            {
                break;
            }
            
            // enable DMA transfer
            status = _device->SetTransferEnable(true);
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::SetTransferEnable(true) (0x%08x)\n", status);
                break;
            }
            
            _virtualIndex = 0;
            _imageIndex = 0;
            _blockIndex = 0;
            
            DebugLog2("transfer start.\n");
            while (true)
            {
                if (!waitBlock())
                {
                    break;
                }
                
                copyBlock();
                
                if (!dispatchBlock())
                {
                    break;
                }
            }
            DebugLog2("transfer stop.\n");

            status = _device->SetTransferEnable(false);
            if (status != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::SetTransferEnable(false) (0x%08x)\n", status);
                break;
            }

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

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

    DebugLog2("PT1Core::transfer() end.\n");
}

uint32_t PT1Core::offset(uint32_t imageIndex, uint32_t blockIndex, uint32_t additionalOffset)
{
    blockIndex += BLOCK_COUNT * imageIndex;
    uint32_t offset = (BLOCK_SIZE * blockIndex + additionalOffset) / sizeof(uint32_t);
    return offset;
}

uint32_t PT1Core::offset(uint32_t imageIndex, uint32_t blockIndex)
{
    return offset(imageIndex, blockIndex, (BLOCK_SIZE - sizeof(uint32_t)));
}

uint32_t PT1Core::read(uint32_t virtualIndex, uint32_t imageIndex, uint32_t blockIndex)
{
    void *voidPtr;
    if (_device->GetBufferPtr(virtualIndex, &voidPtr) != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Device::GetBufferPtr() :read\n");
        return 0;
    }

    volatile const uint32_t *ptr = (volatile const uint32_t *)voidPtr;
    if (ptr != NULL)
    {
        return ptr[offset(imageIndex, blockIndex)];
    }
    else
    {
        DebugLog3("ptr is NULL (read)\n");
    }
    return 0;
}

void PT1Core::clearBlock(uint32_t virtualIndex, uint32_t imageIndex, uint32_t blockIndex)
{
    void *voidPtr = NULL;
    if (_device->GetBufferPtr(virtualIndex, &voidPtr) != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Device::GetBufferPtr() :clearBlock\n");
        return;
    }

    uint32_t *ptr = (uint32_t *)voidPtr;
    if (ptr != NULL)
    {
        ptr[offset(imageIndex, blockIndex)] = 0;
    }
    else
    {
        DebugLog3("ptr is NULL (clearBlock)");
    }
}

bool PT1Core::waitBlock()
{
    bool result = true;
    while (result)
    {
        /*
        EARTH::OS::Thread::Sleep((10)*1000*1000); // 10 ms
        */
        ::Sleep(10);

        //
        if (read(_virtualIndex, _imageIndex, _blockIndex) != 0)
        {
            break;
        }

        EnterCriticalSection(&_cs);
        result = (_transfer == ST_RUN);
        LeaveCriticalSection(&_cs);
    }
    return result;
}

void PT1Core::copyBlock()
{
    void *voidPtr;
    if (_device->GetBufferPtr(_virtualIndex, &voidPtr) != EARTH::PT::STATUS_OK)
    {
        DebugLog3("error: Device::GetBufferPtr(): copyBlock\n");
        return;
    }

    uint32_t *srcPtr = (uint32_t *)voidPtr;

    srcPtr += offset(_imageIndex, _blockIndex, 0);

    memcpy(_buffer, srcPtr, BLOCK_SIZE);

    //
    srcPtr[BLOCK_SIZE / sizeof(*srcPtr) - 1] = 0;

    if (BLOCK_COUNT <= ++_blockIndex)
    {
        _blockIndex = 0;

        //
        if (_device->IncrementTransferCounter() != EARTH::PT::STATUS_OK)
        {
            DebugLog3("error: Device::IncrementTransferCounter()\n");
            return;
        }

        if (VIRTUAL_SIZE <= ++_imageIndex)
        {
            _imageIndex = 0;
            if (VIRTUAL_COUNT <= ++_virtualIndex)
            {
                _virtualIndex = 0;
            }
        }
    }
}

bool PT1Core::dispatchBlock()
{
    const uint32_t *ptr = (uint32_t *)_buffer;

    uint32_t i;
    for (i = 0; i < BLOCK_SIZE / sizeof(*ptr); ++i)
    {
        uint32_t packet = ptr[i];

        uint32_t packetId    = BIT_SHIFT_MASK(packet, 29, 3);
        uint32_t packetError = BIT_SHIFT_MASK(packet, 24, 1);

        if (packetError)
        {
            // check error
            EARTH::PT::Device::TransferInfo info;
            if (_device->GetTransferInfo(&info) != EARTH::PT::STATUS_OK)
            {
                DebugLog3("error: Device::GetTransferInfo()\n");
                return false;
            }

            if (info.TransferCounter1)
            {
                DebugLog3("Transfer counter is now less than 1\n");
            }
            else if (info.BufferOverflow)
            {
                DebugLog3("Buffer Overflow\n");
            }
            else
            {
                DebugLog3("Transfer error\n");
            }
            return false;
        }

        if ((1 <= packetId) && (packetId <= 4))
        {
            if (_locked[packetId - 1] && (_tuners[packetId - 1] != NULL))
            {
                _tuners[packetId - 1]->addPacket(packet);
            }
        }
    }

    return true;
}

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

