//
// PT3Core.cpp
//

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

namespace ry0
{
namespace device
{
namespace PT3
{

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

    InitializeCriticalSection(&_cs);

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

    // initialize: PT3
    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->InitTuner();
    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::uint32 i = 0; i < 2; ++i)
    {
        for (EARTH::uint32 j = 0; j < 2; ++j)
        {
            status = _device->SetTunerSleep(static_cast<EARTH::PT::Device::ISDB>(j), i, 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;
            }
        }
    }

    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;
    }

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

PT3Core::~PT3Core()
{
    DebugLog2("PT3Core::~PT3Core() called\n");

    if (_device == NULL)
    {
        DebugLog3("_device is NULL.\n");
    }

    EnterCriticalSection(&_cs);
    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);
    }
    _device = NULL;
    LeaveCriticalSection(&_cs);

    DeleteCriticalSection(&_cs);

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

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

    bool exist = false;
    for (EARTH::uint32 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 *PT3Core::name()
{
    DebugLog2("PT3Core::name()\n");
    return _name;
}

bool PT3Core::getCnAgc(EARTH::uint32 tuner, EARTH::uint32 *cn100, EARTH::uint32 *agc, EARTH::uint32 *maxAgc)
{
    DebugLog2("PT3Core::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 == 0) ? EARTH::PT::Device::ISDB_S : EARTH::PT::Device::ISDB_T,
                                   tuner / 2, cn100, agc, maxAgc);
        if (status == EARTH::PT::STATUS_OK)
        {
            result = true;
        }
        LeaveCriticalSection(&_cs);
    }
    return result;
}

bool PT3Core::setChannel(EARTH::uint32 tuner, int channel)
{
    DebugLog2("PT3Core::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;

                DebugLog2("call SetFrequency()\n");
                status = _device->SetFrequency(EARTH::PT::Device::ISDB_S, (tuner / 2), channel);
                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(EARTH::PT::Device::ISDB_T, (tuner / 2), channel);
                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);

    DebugLog2("PT3Core::setChannel(%d, %d) end\n", channel, tuner);

    return result;
}

EARTH::status PT3Core::setTransferTestMode(Tuner::Type type, uint32_t tuner)
{
//    DebugLog2("PT3Core::setTransferTestMode()\n");
    EARTH::status status = EARTH::PT::STATUS_INVALID_PARAM_ERROR;
    if ((tuner < MAX_TUNERS) && (type != Tuner::TYPE_NA))
    {
        EnterCriticalSection(&_cs);
        status = _device->SetTransferTestMode((type == Tuner::ISDB_T) ? EARTH::PT::Device::ISDB_T : EARTH::PT::Device::ISDB_S, static_cast<EARTH::uint32>(tuner / 2));
        LeaveCriticalSection(&_cs);
    }
    return status;
}

EARTH::status PT3Core::setTransferPageDescriptorAddress(Tuner::Type type, uint32_t tuner, EARTH::uint64 pageAddress)
{
//    DebugLog2("PT3Core::setTransferPageDescriptorAddress()\n");
    EARTH::status status = EARTH::PT::STATUS_INVALID_PARAM_ERROR;
    if ((tuner < MAX_TUNERS) && (type != Tuner::TYPE_NA))
    {
        EnterCriticalSection(&_cs);
        status = _device->SetTransferPageDescriptorAddress((type == Tuner::ISDB_T) ? EARTH::PT::Device::ISDB_T : EARTH::PT::Device::ISDB_S,
                                                           static_cast<EARTH::uint32>(tuner / 2), pageAddress);
        LeaveCriticalSection(&_cs);
    }
    return status;
}

EARTH::status PT3Core::setTransferEnabled(Tuner::Type type, uint32_t tuner, bool enabled)
{
//    DebugLog2("PT3Core::setTransferPageDescriptorAddress()\n");
    EARTH::status status = EARTH::PT::STATUS_INVALID_PARAM_ERROR;
    if ((tuner < MAX_TUNERS) && (type != Tuner::TYPE_NA))
    {
        EnterCriticalSection(&_cs);
        status = _device->SetTransferEnabled((type == Tuner::ISDB_T) ? EARTH::PT::Device::ISDB_T : EARTH::PT::Device::ISDB_S, static_cast<EARTH::uint32>(tuner / 2), enabled);
        LeaveCriticalSection(&_cs);
    }
    return status;
}

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

