/**
 * @file Analyzer.cpp
 *
 */

#include <time.h>

#define DBG_LEVEL 0
#include "Raym/Log.h"

#include "Raym/Raym.h"
#include "b25/aribstr.h"
#include "ry0/iPTd/Analyzer.h"

using namespace Raym;

namespace ry0
{
namespace iPTd
{

Analyzer::Analyzer()
{
    _sdt = NULL;
    _eit = NULL;
    _demux.setListener(this);
}

Analyzer::~Analyzer()
{
    if (_sdt != NULL)
    {
        delete _sdt;
    }
}

Analyzer *Analyzer::alloc()
{
    return new Analyzer();
}

Analyzer *Analyzer::init()
{
    return this;
}

Dictionary *Analyzer::stationInfo()
{
    Dictionary *result = NULL;

    _demux.setFlag(MPEG2::TS::Demultiplexer::FLG_SDT, true);

    RaymLock(this);

    uint8_t count = 0;
    while ((result == NULL) && (count++ < 32))
    {
        while (_sdt == NULL)
        {
            RaymCondWait(this);
        }

        if (_sdt->_table_id == 0x42)
        {
            result = Dictionary::dictionaryWithCapacity(0);
            Array *services = Array::arrayWithCapacity(0);
            result->setObject(services, KEY_SERVICES);
            for (uint32_t i = 0; i < _sdt->_service_count; ++i)
            {
                Dictionary *service = Dictionary::dictionaryWithCapacity(0);
                services->addObject(service);
                char tmp[32];
                sprintf_s(tmp, "%d", _sdt->_services[i]._service_id);
                service->setString(tmp, KEY_SERVICE_ID);
                sprintf_s(tmp, "%d", _sdt->_services[i]._desc->service_type);
                service->setString(tmp, KEY_SERVICE_TYPE);
                service->setBool(false, KEY_IPTV_ENABLED);
                if (_sdt->_services[i]._desc->service_name_length > 0)
                {
                    String *name = String::stringWithCString(_sdt->_services[i]._desc->service_name, ShiftJISStringEncoding);
                    service->setString(name, KEY_NAME);
                    if (i == 0)
                    {
                        result->setString(name, KEY_NAME);
                    }
                }
            }
        }

        delete _sdt;
        _sdt = NULL;
    }

    RaymUnlock(this);

    _demux.setFlag(MPEG2::TS::Demultiplexer::FLG_SDT, false);

    return result;
}

Array *Analyzer::collectEPGs(time_t limit)
{
    _demux.setFlag(MPEG2::TS::Demultiplexer::FLG_EIT, true);

    std::vector<MPEG2::TS::EIT *> eits;

    RaymLock(this);

    time_t start = time(NULL);
    while (true)
    {
        while ((_eit == NULL) && (time(NULL) < start + limit))
        {
            RaymCondTimedWait(this, 1000);
        }

        if (_eit != NULL)
        {
            eits.push_back(_eit);
            _eit = NULL;
        }
        else
        {
            break;
        }
    }

    RaymUnlock(this);

    _demux.setFlag(MPEG2::TS::Demultiplexer::FLG_EIT, false);

    Array *result = Array::arrayWithCapacity(0);

    std::vector<MPEG2::TS::EIT *>::iterator it;
    for (it = eits.begin(); it != eits.end(); ++it)
    {
        MPEG2::TS::EIT *eit = (*it);
        if ((eit->_table_id == MPEG2::TS::EIT::TABLE_ID_SELF) ||
            ((MPEG2::TS::EIT::TABLE_ID_SELF_SCHEDULE_BEGIN <= eit->_table_id) && (eit->_table_id <= MPEG2::TS::EIT::TABLE_ID_SELF_SCHEDULE_END)))
        {
            MPEG2::TS::EIT::Event *event;
            while ((event = eit->nextEvent()) != NULL)
            {
                char service_id[32];
                sprintf_s(service_id, "%d", eit->_service_id);

                char event_id[32];
                sprintf_s(event_id, "%d", event->_event_id);

                Dictionary *epg = NULL;

                for (uint32_t i = 0; i < result->count(); ++i)
                {
                    epg = (Dictionary *)result->objectAtIndex(i);
                    if (epg->stringForKey(KEY_EPG_SERVICE_ID)->isEqualToString(service_id) &&
                        epg->stringForKey(KEY_EPG_EVENT_ID)->isEqualToString(event_id))
                    {
                        break;
                    }
                    epg = NULL;
                }

                if (epg == NULL)
                {
                    epg = Dictionary::dictionaryWithCapacity(0);
                    epg->setString(service_id, KEY_EPG_SERVICE_ID);
                    epg->setString(event_id, KEY_EPG_EVENT_ID);

                    char date[16];
                    sprintf_s(date, sizeof(date), "%02d/%02d/%02d", event->_st_year, event->_st_month, event->_st_day);
                    epg->setString(date, KEY_EPG_DATE);

                    char start[16];
                    sprintf_s(start, sizeof(start), "%02d:%02d:%02d", event->_st_hour, event->_st_min, event->_st_sec);
                    epg->setString(start, KEY_EPG_START);

                    int hour = event->_st_hour + event->_dur_hour;
                    int min = event->_st_min + event->_dur_min;
                    int sec = event->_st_sec + event->_dur_sec;
                    if (sec >= 60)
                    {
                        min += 1;
                        sec -= 60;
                    }
                    if (min >= 60)
                    {
                        hour += 1;
                        min -= 60;
                    }
                    char end[16];
                    sprintf_s(end, sizeof(end), "%02d:%02d:%02d", hour, min, sec);
                    epg->setString(end, KEY_EPG_END);

                    char duration[8];
                    sprintf_s(duration, sizeof(duration), "%d", event->_dur_hour * 3600 + event->_dur_min * 60 + event->_dur_sec);
                    epg->setString(duration, KEY_EPG_DURATION);

                    result->addObject(epg);
                }

                Array *ext_items = NULL;
                String *ext_item_desc = NULL;
                Data *ext_item = NULL;
                Data *ext_text = NULL;

                MPEG2::TS::Descriptor *desc;
                while ((desc = event->nextDescriptor()) != NULL)
                {
                    switch (desc->_descriptor_tag)
                    {
                    case MPEG2::TS::TAG_BOUQUET_NAME_DESCRIPTOR:
                        DebugLog3("TAG_BOUQUET_NAME_DESCRIPTOR\n");
                        break;

                    case MPEG2::TS::TAG_SERVICE_DESCRIPTOR:
                        DebugLog3("TAG_SERVICE_DESCRIPTOR\n");
                        break;

                    case MPEG2::TS::TAG_SHORT_EVENT_DESCRIPTOR:
                        DebugLog3("TAG_SHORT_EVENT_DESCRIPTOR\n");
                        if (desc->_short_event._event_name_length > 0)
                        {
                            char *tmp = (char *)malloc(desc->_short_event._event_name_length * 3);
                            if (tmp != NULL)
                            {
                                AribToString(tmp, (const char *)desc->_short_event._event_name, desc->_short_event._event_name_length);
                                String *event_name = String::stringWithCString(tmp, ShiftJISStringEncoding);
                                if (event_name != NULL)
                                {
                                    epg->setString(event_name, KEY_EPG_TITLE);
                                }
                                free(tmp);
                            }
                        }
                        if (desc->_short_event._text_length > 0)
                        {
                            char *tmp = (char *)malloc(desc->_short_event._text_length * 3);
                            if (tmp != NULL)
                            {
                                AribToString(tmp, (const char *)desc->_short_event._text, desc->_short_event._text_length);
                                String *text = String::stringWithCString(tmp, ShiftJISStringEncoding);
                                if (text != NULL)
                                {
                                    epg->setString(text, KEY_EPG_DESCRIPTION);
                                }
                                free(tmp);
                            }
                        }
                        break;

                    case MPEG2::TS::TAG_EXTENDED_EVENT_DESCRIPTOR:
                        DebugLog3("TAG_EXTENDED_EVENT_DESCRIPTOR\n");
                        {
                            for (int i = 0; i < desc->_extended_event._item_count; ++i)
                            {
                                if (ext_items == NULL)
                                {
                                    ext_items = Array::arrayWithCapacity(0);
                                    epg->setObject(ext_items, KEY_EPG_EXT_ITEMS);
                                }

                                if (desc->_extended_event._items[i]._item_description_length > 0)
                                {
                                    if (ext_item != NULL)
                                    {
                                        char *tmp = (char *)malloc(ext_item->length() * 3);
                                        if (tmp != NULL)
                                        {
                                            AribToString(tmp, (const char *)ext_item->bytes(), ext_item->length());
                                            String *desc = String::stringWithCString(tmp, ShiftJISStringEncoding);
                                            if (desc != NULL)
                                            {
                                                Dictionary *dict = Dictionary::dictionaryWithCapacity(0);
                                                if (ext_item_desc != NULL)
                                                {
                                                    dict->setString(ext_item_desc, KEY_EPG_EXT_ITEM_DESCRIPTION);
                                                    ext_item_desc = NULL;
                                                }
                                                dict->setString(desc, KEY_EPG_EXT_ITEM);
                                                ext_items->addObject(dict);
                                            }
                                            free(tmp);
                                        }
                                        ext_item = NULL;
                                    }

                                    char *tmp = (char *)malloc(desc->_extended_event._items[i]._item_description_length * 3);
                                    if (tmp != NULL)
                                    {
                                        AribToString(tmp, (const char *)desc->_extended_event._items[i]._item_description,
                                                    desc->_extended_event._items[i]._item_description_length);
                                        ext_item_desc = String::stringWithCString(tmp, ShiftJISStringEncoding);
                                        free(tmp);
                                    }
                                    else
                                    {
                                        ext_item_desc = NULL;
                                    }
                                }

                                if (desc->_extended_event._items[i]._item_length > 0)
                                {
                                    if (ext_item == NULL)
                                    {
                                        ext_item = Data::dataWithCapacity(0);
                                    }
                                    ext_item->appendBytes(desc->_extended_event._items[i]._item,
                                                          desc->_extended_event._items[i]._item_length);
                                }
                            }
                            if (desc->_extended_event._text_length > 0)
                            {
                                if (ext_text == NULL)
                                {
                                    ext_text = Data::dataWithCapacity(0);
                                }
                                ext_text->appendBytes(desc->_extended_event._text, desc->_extended_event._text_length);
                            }
                        }
                        break;

                    case MPEG2::TS::TAG_CONTENT_DESCRIPTOR:
                        DebugLog3("TAG_CONTENT_DESCRIPTOR\n");
                        break;

                    case MPEG2::TS::TAG_SERIES_DESCRIPTOR:
                        DebugLog3("TAG_SERIES_DESCRIPTOR\n");
                        break;

                    case MPEG2::TS::TAG_EVENT_GROUP_DESCRIPTOR:
                        DebugLog3("TAG_EVENT_GROUP_DESCRIPTOR\n");
                        break;

                    case MPEG2::TS::TAG_COMPONENT_DESCRIPTOR:
                        DebugLog3("TAG_COMPONENT_DESCRIPTOR\n");
                        break;

                    case MPEG2::TS::TAG_DIGITAL_COPY_CONTROL_DESCRIPTOR:
                        DebugLog3("TAG_DIGITAL_COPY_CONTROL_DESCRIPTOR\n");
                        break;

                    case MPEG2::TS::TAG_AUDIO_COMPONENT_DESCRIPTOR:
                        DebugLog3("TAG_AUDIO_COMPONENT_DESCRIPTOR\n");
                        break;

                    case MPEG2::TS::TAG_DATA_CONTENT_DESCRIPTOR:
                        DebugLog3("TAG_AUDIO_COMPONENT_DESCRIPTOR\n");
                        break;

                    default:
                        DebugLog3("Unknown descriptor: 0x%02X\n", desc->_descriptor_tag);
                        break;
                    }
                }

                if (ext_item != NULL)
                {
                    char *tmp = (char *)malloc(ext_item->length() * 3);
                    if (tmp != NULL)
                    {
                        AribToString(tmp, (const char *)ext_item->bytes(), ext_item->length());
                        String *desc = String::stringWithCString(tmp, ShiftJISStringEncoding);
                        if (desc != NULL)
                        {
                            Dictionary *dict = Dictionary::dictionaryWithCapacity(0);
                            if (ext_item_desc != NULL)
                            {
                                dict->setString(ext_item_desc, KEY_EPG_EXT_ITEM_DESCRIPTION);
                                ext_item_desc = NULL;
                            }
                            dict->setString(desc, KEY_EPG_EXT_ITEM);
                            ext_items->addObject(dict);
                        }
                        free(tmp);
                    }
                    ext_item = NULL;
                }

                if (ext_text != NULL)
                {
                    char *tmp = (char *)malloc(ext_text->length() * 3);
                    if (tmp != NULL)
                    {
                        AribToString(tmp, (const char *)ext_text->bytes(), ext_text->length());
                        String *desc = String::stringWithCString(tmp, ShiftJISStringEncoding);
                        if (desc != NULL)
                        {
                            Dictionary *dict = Dictionary::dictionaryWithCapacity(0);
                            dict->setString("no desc", KEY_EPG_EXT_ITEM_DESCRIPTION);
                            dict->setString(desc, KEY_EPG_EXT_ITEM);
                            ext_items->addObject(dict);
                        }
                        free(tmp);
                    }
                    ext_text = NULL;
                }
            }
        }

        delete *it;
    }

    return result;
}

void Analyzer::put(uint8_t *buffer, uint32_t size)
{
    _demux.put(buffer, size);
}

void Analyzer::detect(MPEG2::TS::SDT *sdt)
{
    RaymLock(this);
    if (_sdt != NULL)
    {
        delete _sdt;
        _sdt = NULL;
    }
    _sdt = new MPEG2::TS::SDT(*sdt);
    RaymCondSignal(this);
    RaymUnlock(this);
}

void Analyzer::detect(MPEG2::TS::EIT *eit)
{
    RaymLock(this);
    if (_eit != NULL)
    {
        delete _eit;
        _eit = NULL;
    }
    _eit = new MPEG2::TS::EIT(*eit);
    RaymCondSignal(this);
    RaymUnlock(this);
}

} // iPTd
} // ry0
