//
//
//

//#include "stdafx.h"

#define DBG_LEVEL 0
#include <Raym/Log.h>

#include <time.h>
#include <direct.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <share.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Iphlpapi.h>

#include "b25/aribstr.h"
#include "b25/arib_std_b25.h"
#include "b25/b_cas_card.h"

#include "net/RTSPRequest.h"

#include "ry0/iPTd/Controller.h"
#include "ry0/iPTd/HTTPLiveStreaming.h"
#include "ry0/iPTd/Analyzer.h"


using namespace Raym;
using namespace NET;
using namespace ry0::device;

namespace ry0
{
namespace iPTd
{

static const char *PLIST_PREFIX = "com.gmail.tim.and.pom";

// プロパティデフォルト値
static const char * DEF_NAME                = "iPTd_R2";
static const char * DEF_HOSTNAME            = "localhost";
static const int    DEF_HTTP_PORT           = 50080;        // HTTPポート
static const int    DEF_BEGIN_UDP_PORT      = 51000;        // UDPポートの開始位置
static const int    DEF_SUSPEND_TIME        = 5;            // 休止するまでの時間（分単位）
static const int    DEF_FORCED_SUSPEND_TIME = 120;          // 強制休止するまでの時間（分単位）
static const char * DEF_COLLECT_EPG_TIME    = "02:00:00";   // EPG収集する時刻 HH:MM::SS

// 非同期実行コマンド
static const long long CMD_RESTART              = 0x0001;  // 再開処理
static const long long CMD_SUSPEND              = 0x0002;  // サスペンド
static const long long CMD_PERIODIC             = 0x0003;  // 周期処理
static const long long CMD_PERIODIC_2           = 0x0004;  // 周期処理２
static const long long CMD_COLLECT_EPG_ISDB_S   = 0x0005;  // 番組情報取得(ISDB-S)
static const long long CMD_COLLECT_EPG_ISDB_T   = 0x0006;  // 番組情報取得(ISDB-T)

//
static const TimeInterval DEF_COLLECT_EPG_DELAY    = 1.0;
static const TimeInterval DEF_COLLECT_EPG_RETRY    = 60.0;

static const time_t DEF_COLLECT_EPG_LIMIT_S  = 10;
static const time_t DEF_COLLECT_EPG_LIMIT_T  = 20;

static const time_t OFFSET_OF_START_TIME        = -2;       // 録画開始時刻の補正（秒単位）
static const time_t OFFSET_OF_END_TIME          = -3;       // 録画停止時刻の補正（秒単位）
static const time_t OFFSET_OF_WAKEUP            = -300;     // 起動スケジュールの補正（秒単位）  注：休止するまでの時間(DEF_SUSPEND_TIME)よりも短くすること
static const time_t OFFSET_OF_SUPPRESSION_TIME  = -600;     // 録画開始前に休止の抑制を開始する時間（秒単位）


#if 0

void Controller::delaySuspend()
{
    // 
    Timer *timer = Timer::scheduledTimerWithTimeInterval(1.0, this, (void *)CMD_SUSPEND, false);
    if (timer == NULL)
    {
        DebugLog0("Can't start  timer.");
    }
}
#endif

std::string Controller::createVideoPath(int tuner)
{
    DebugLog2("Controller::createVideoPath()");

    std::string result = "";

    while (true)
    {
        time_t now;
        time(&now);
        TM tm;
        if (localtime_s(&tm, &now) != 0)
        {
            break;
        }

        result = _store_path->cString();
        DebugLog2("result: %s\n", result.c_str());

        char tmp[128];
        if (sprintf_s(tmp, sizeof(tmp), "\\%04d", tm.tm_year + 1900) < 0)
        {
            DebugLog0("sprintf_s() error: year\n");
            result = "";
            break;
        }
        result += tmp;
        DebugLog2("result: %s\n", result.c_str());

        STAT stat;
        if (_stat(result.c_str(), &stat) != 0)
        {
            if (_mkdir(result.c_str()) != 0)
            {
                DebugLog0("_mkdir() error: year\n");
                result = "";
                break;
            }
            _stat(result.c_str(), &stat);
        }
        if ((stat.st_mode & _S_IFDIR) != _S_IFDIR)
        {
            DebugLog0("%s is not directory.\n", result.c_str());
            result = "";
            break;
        }

        if (sprintf_s(tmp, sizeof(tmp), "\\%02d", tm.tm_mon + 1) < 0)
        {
            DebugLog0("sprintf_s() error: month\n");
            result = "";
            break;
        }
        result += tmp;
        DebugLog2("result: %s\n", result.c_str());

        if (_stat(result.c_str(), &stat) != 0)
        {
            if (_mkdir(result.c_str()) != 0)
            {
                DebugLog0("_mkdir() error: month\n");
                result = "";
                break;
            }
            _stat(result.c_str(), &stat);
        }
        if ((stat.st_mode & _S_IFDIR) != _S_IFDIR)
        {
            DebugLog0("%s is not directory.", result.c_str());
            result = "";
            break;
        }

        if (sprintf_s(tmp, sizeof(tmp),
                      "\\%04d%02d%02d_%02d%02d%02d_%03d_%s.ts",
                      tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
                      _tuners[tuner]->channel(), _tuners[tuner]->name()) < 0)
        {
            DebugLog0("sprintf_s() error: filename");
            result = "";
            break;
        }
        result += tmp;
        DebugLog2("result: %s\n", result.c_str());

        break;
    }

    return result;
}

// epgの開始時刻でソートする為の比較関数
Integer compareFunction(Object *obj1, Object *obj2, void *context)
{
    if (isKindOfClass(Dictionary, obj1) && isKindOfClass(Dictionary, obj2))
    {
        time_t st1;
        time_t ed1;
        Controller::getTimeWithEPG((Dictionary *)obj1, &st1, &ed1);

        time_t st2;
        time_t ed2;
        Controller::getTimeWithEPG((Dictionary *)obj2, &st2, &ed2);

        if (st1 < st2)
        {
            return OrderedAscending;
        }
        else if (st1 > st2)
        {
            return OrderedDescending;
        }
        else
        {
            if (ed1 < ed2)
            {
                return OrderedAscending;
            }
            else if (ed1 > ed2)
            {
                return OrderedDescending;
            }
        }
    }
    return OrderedSame;
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- EPG関連 -------
#endif

void Controller::removePastEPGs()
{
    DebugLog2("Controller::removePastEPGs()");

    time_t now = time(NULL);

    RaymLock(_epgs);

    Dictionary *temp_data = _epgs->dictionaryForKey(KEY_TEMP_DATA);
    if (temp_data != NULL)
    {
        temp_data->retain();
        _epgs->removeObjectForKey(KEY_TEMP_DATA);
    }
    else
    {
        temp_data = Dictionary::alloc()->initWithCapacity(0);
    }

    Dictionary *services = _epgs->dictionaryForKey(KEY_SERVICES);
    if (services != NULL)
    {
        Array *keys = services->allKeys();
        for (uint i = 0; i < keys->count(); ++i)
        {
            String *key = (String *)keys->objectAtIndex(i);

            std::string xmltv_programs;
            Dictionary *temp_service = temp_data->dictionaryForKey(key);
            if (temp_service == NULL)
            {
                temp_service = Dictionary::dictionaryWithCapacity(0);
                temp_data->setObject(temp_service, key);
            }

            Array *old_epgs = services->arrayForKey(key);
            Array *new_epgs = Array::arrayWithCapacity(0);
            for (uint j = 0; j < old_epgs->count(); ++j)
            {
                Dictionary *epg = (Dictionary *)old_epgs->objectAtIndex(j);
                time_t start = 0;
                time_t end = 0;
                getTimeWithEPG(epg, &start, &end);
                if (now <= end)
                {
                    new_epgs->addObject(epg);

                    Array *ch_list = temp_service->arrayForKey(KEY_CHANNELS);
                    if (ch_list != NULL)
                    {
                        std::string epg_date     = epg->stringForKey(KEY_EPG_DATE)->stringByReplacingOccurrencesOfString("/", "")->cString();
                        std::string epg_start    = epg->stringForKey(KEY_EPG_START)->stringByReplacingOccurrencesOfString(":", "")->cString();
                        std::string epg_end      = epg->stringForKey(KEY_EPG_END)->stringByReplacingOccurrencesOfString(":", "")->cString();
                        std::string epg_event_id = epg->stringForKey(KEY_EPG_EVENT_ID)->cString();
                        std::string epg_duration = epg->stringForKey(KEY_EPG_DURATION)->cString();
                        std::string epg_title    = epg->stringForKey(KEY_EPG_TITLE)->cString();

                        for (uint ch_idx = 0; ch_idx < ch_list->count(); ++ch_idx)
                        {
                            xmltv_programs += "  <programme start=\"" + epg_date + epg_start + " +0900\" stop=\"";
                            xmltv_programs += epg_date + epg_end + " +0900\" channel=\"";
                            xmltv_programs += ((String *)ch_list->objectAtIndex(ch_idx))->cString();
                            xmltv_programs += "\" event_id=\"" + epg_event_id + "\" duration=\"" + epg_duration + "\">\r\n";

                            xmltv_programs += "    <title lang=\"ja_JP\">" + epg_title + "</title>\r\n";

                            xmltv_programs += "  </programme>\r\n";
                        }
                    }
                }
            }
            services->setObject(new_epgs, key);

            if (xmltv_programs.length() > 0)
            {
                temp_service->setObject(String::stringWithUTF8String(xmltv_programs.c_str()), KEY_PROGRAMS);
            }
        }
    }

    _epgs->writeToFile(_epgs_path, true);

    _epgs->setObject(temp_data, KEY_TEMP_DATA);
    temp_data->release();

    RaymUnlock(_epgs);
}

void Controller::collectEPGsForTuner(int tuner, time_t limit)
{
    DebugLog2("Controller::collectEPGsForTuner(%d) start.", tuner);

    // 既にロックされた状態でコールされる前提
    bool locked = false;
    RaymLock(this);
    if ((0 <= tuner) && (tuner < _tunerCount))
    {
        locked = _tuners[tuner]->isLocked();
    }
    RaymUnlock(this);
    if (!locked)
    {
        DebugLog2("Controller::collectEPGsForTuner(%d) end(no locked).", tuner);
        return;
    }

    Analyzer *an = Analyzer::alloc()->init();

    RaymLock(this);
    _tuners[tuner]->setListener(an);
    RaymUnlock(this);

    Array *collected = an->collectEPGs(limit);

    RaymLock(this);
    _tuners[tuner]->setListener(NULL);
    RaymUnlock(this);

    an->release();

    RaymLock(_epgs);

    for (uint j = 0; j < collected->count(); ++j)
    {
        Dictionary *epg = (Dictionary *)collected->objectAtIndex(j);

        if (epg->stringForKey(KEY_EPG_TITLE) == NULL)
        {
            // タイトルが無い場合は不要
            continue;
        }

        // Service ID を Primary Key
        String *key = epg->stringForKey(KEY_EPG_SERVICE_ID);
        if (key != NULL)
        {
            Dictionary *services = _epgs->dictionaryForKey(KEY_SERVICES);
            if (services == NULL)
            {
                services = Dictionary::dictionaryWithCapacity(0);
                _epgs->setObject(services, KEY_SERVICES);
            }
            Array *epgs = services->arrayForKey(key);
            if (epgs == NULL)
            {
                epgs = Array::arrayWithCapacity(0);
                services->setObject(epgs, key);
            }
            bool inserted = false;
            for (uint idx = 0; idx < epgs->count(); ++idx)
            {
                Dictionary *epg2 = (Dictionary *)epgs->objectAtIndex(idx);
                if (epg->stringForKey(KEY_EPG_EVENT_ID)->isEqualToString(epg2->stringForKey(KEY_EPG_EVENT_ID)))
                {
                    inserted = true;
                    break;
                }
                if (compareFunction(epg, epgs->objectAtIndex(idx), NULL) == OrderedAscending)
                {
                    epgs->insertObject(epg, idx);
                    inserted = true;
                    break;
                }
            }
            if (!inserted)
            {
                epgs->addObject(epg);
            }
        }
    }

    RaymUnlock(_epgs);

    removePastEPGs();

    DebugLog2("Controller::collectEPGsForTuner(%d) end.", tuner);
}

bool Controller::collectEPGs(Tuner::Type type)
{
    // 使用するチューナを決定
    int tuner = 0;
    bool locked = false;
    RaymLock(this);
    for (int i = _tunerCount - 1; i >= 0; --i)
    {
        if (isTunerEnabled(i))
        {
            if ((!_tuners[i]->isLocked()) && (_tuners[i]->type() == type))
            {
                if (_tuners[i]->lock())
                {
                    tuner = i;
                    locked = true;
                    break;
                }
            }
        }
    }
    RaymUnlock(this);

    // ロック確認
    if (!locked)
    {
        DebugLog3("Controller::collectEPGs(%s) end(Can't locled).", type == Tuner::ISDB_S ? "ISDB-S" : "ISDB-T");
        // ロックできない場合は収集しない
        return false;
    }

    bool canceled = false;
    DebugLog0("Collect EPG of \"%s(#%d)\" is started.", _tuners[tuner]->name(), tuner);

    // 現在のチャンネルを保存
    int channel = _tuners[tuner]->channel();

    // チャンネルを変更しつつEPGを取得
    int max_channel = (type == Tuner::ISDB_S ? Tuner::MAX_CHANNELS_ISDB_S : Tuner::MAX_CHANNELS_ISDB_T);
    for (int ch = 0; ch <= max_channel; ++ch)
    {
        // チャンネルが有効かチェック
        if (isChannelEnabled(tuner, ch))
        {
            // チャンネル設定
            if (_tuners[tuner]->setChannel(ch))
            {
                time_t limit = (time_t)_epgs->integerForKey(stationName(type, ch));
                if (limit == 0)
                {
                    limit = ((type == Tuner::ISDB_S) ? DEF_COLLECT_EPG_LIMIT_S : DEF_COLLECT_EPG_LIMIT_T);
                    _epgs->setInteger((int)limit, stationName(type, ch));
                }

                collectEPGsForTuner(tuner, limit);

                RaymLock(this);
                if (((type == Tuner::ISDB_S) && _cancel_epg_collect_s) ||
                    ((type == Tuner::ISDB_T) && _cancel_epg_collect_t))
                {
                    ch = Tuner::MAX_CHANNELS_ISDB_T + 1;
                    canceled = true;
                }
                RaymUnlock(this);

                // キャンセル確認
                //   終了不可 -> 録画待機中／録画中  なので、収集は諦める
                if (!canTerminate())
                {
                    ch = Tuner::MAX_CHANNELS_ISDB_T + 1;
                    canceled = true;
                }
            }
        }
    }

    // 元のチャンネルに戻す
    _tuners[tuner]->setChannel(channel);

    // lock
    RaymLock(this);

    // チューナをアンロック
    _tuners[tuner]->unlock();

    if (type == Tuner::ISDB_S)
    {
        _cancel_epg_collect_s = false;
    }
    else
    {
        _cancel_epg_collect_t = false;
    }

    // unlock
    RaymUnlock(this);

    DebugLog0("Collect EPG of \"%s(#%d)\" was %s.", _tuners[tuner]->name(), tuner, canceled ? "canceled" : "finished");

    return !canceled;
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- 予約録画関連 -------
#endif

//
// 録画予約：サービスID／イベントID指定
//   EPGデータから指定のサービスID／イベントIDのEPGを取り出し、EPG指定の録画予約をコール
//
bool Controller::reserve(int service_id, int event_id)
{
    DebugLog0("Controller::reserve(service_id, event_id)");
    DebugLog0("event_id: %d, service_id: %d", event_id, service_id);

    bool result = false;

    // lock
    RaymLock(this);

    if ((_epgs != NULL) && (_epgs->dictionaryForKey(KEY_SERVICES) != NULL))
    {
        Array *events = _epgs->dictionaryForKey(KEY_SERVICES)->arrayForKey(String::stringWithFormat("%d", service_id));
        if (events != NULL)
        {
            DebugLog0("count: %d, event_id: %d, service_id: %d", events->count(), event_id, service_id);
            for (uint i = 0; i < events->count(); ++i)
            {
                Dictionary *epg = (Dictionary *)events->objectAtIndex(i);
                DebugLog0("epg event_id: %s", epg->stringForKey(KEY_EPG_EVENT_ID)->cString());
                if (epg->stringForKey(KEY_EPG_EVENT_ID)->isEqualToString(String::stringWithFormat("%d", event_id)))
                {
                    result = reserve(epg);
                }
            }
        }
        else
        {
            DebugLog0("events is NULL");
        }
    }
    else
    {
        DebugLog0("_epgs is NULL");
    }

    // unlock
    RaymUnlock(this);

    return result;
}

//
// 録画予約：EPG指定
//   EPGからサービスIDを取り出し、チューナ情報を検索して一致するサービスIDがあったらチューナ／EPG指定の録画予約を試行
//
bool Controller::reserve(Dictionary *in_epg)
{
    DebugLog0("Controller::reserve(epg)");

    bool result = false;

    // lock
    RaymLock(this);

    while (in_epg != NULL)
    {
        // EPGを複製
        Dictionary *epg = Dictionary::dictionaryWithDictionary(in_epg);
        if (epg == NULL)
        {
            DebugLog3("Dictionary::dictionaryWithDictionary() ng.");
            break;
        }

        // サービスID取得
        String *service_id = epg->stringForKey(KEY_EPG_SERVICE_ID);
        if (service_id == NULL)
        {
            DebugLog3("epg->stringForKey(KEY_EPG_SERVICE_ID) ng.");
            break;
        }
        DebugLog3("service_id: %s\n", service_id->cString());

        // 全チューナ情報取得
        Dictionary *tunerInfos = _props->dictionaryForKey(KEY_TUNERS);
        if (tunerInfos == NULL)
        {
            DebugLog3("_props->dictionaryForKey(KEY_TUNERS) ng.");
            break;
        }

        // チューナを検索
        for (int i = 0; (!result) && (i < _tunerCount); ++i)
        {
            // チューナ情報取得
            Dictionary *tunerInfo = tunerInfos->dictionaryForKey(_tuners[i]->name());
            if (tunerInfo == NULL)
            {
                DebugLog3("tunerInfos->dictionaryForKey(_tuners[%d]->name()) ng.", i);
                continue;
            }

            // 全チャンネル情報取得
            Dictionary *channels = tunerInfo->dictionaryForKey(KEY_CHANNELS);
            if (channels == NULL)
            {
                DebugLog3("tunerInfo->dictionaryForKey(KEY_CHANNELS) ng.");
                continue;
            }

            // キー取得
            Array *chkeys = channels->allKeys();
            for (uint ch = 0; ch < chkeys->count(); ++ch)
            {
                // チャンネル情報取得
                Dictionary *channel = channels->dictionaryForKey((String *)chkeys->objectAtIndex(ch));
                if (channel == NULL)
                {
                    DebugLog3("channels->dictionaryForKey() ng.");
                    continue;
                }

                // 全サービス情報取得
                Array *services = (Array *)channel->objectForKey(KEY_SERVICES);
                if (services == NULL)
                {
                    DebugLog3("channel->objectForKey() ng.");
                    continue;
                }

                for (uint s = 0; s < services->count(); ++s)
                {
                    // サービス情報取得
                    Dictionary *service = (Dictionary *)services->objectAtIndex(s);
                    if (service == NULL)
                    {
                        DebugLog3("service->objectAtIndex() ng.");
                        continue;
                    }

                    // サービスIDを比較
                    String *sid = service->stringForKey(KEY_SERVICE_ID);
                    if ((sid != NULL) && sid->isEqualToString(service_id))
                    {
                        // チャンネルを設定
                        epg->setString((String *)chkeys->objectAtIndex(ch), KEY_EPG_CHANNEL);

                        // 録画予約
                        result = reserve(i, epg);

                        // チャンネルループのカウンタを更新（＝ループを終了させる）
                        ch = chkeys->count();
                        break;
                    }
                }
            }
        }

        break;
    }

    // unlock
    RaymUnlock(this);

    return result;
}

//
// 録画予約：チューナ／EPG指定
//
bool Controller::reserve(int tuner, Dictionary *in_epg)
{
    DebugLog0("Controller::reserve(tuner, epg)");

    bool result = false;

    // lock
    RaymLock(this);

    while ((0 <= tuner) && (tuner < _tunerCount) && (in_epg != NULL))
    {
        Dictionary *epg = Dictionary::dictionaryWithDictionary(in_epg);
        if (epg == NULL)
        {
            DebugLog3("Dictionary::dictionaryWithDictionary() ng.");
            break;
        }

        Array *array = _reservations->arrayForKey(_tuners[tuner]->name());
        if (array == NULL)
        {
            array = Array::arrayWithCapacity(0);
            _reservations->setObject(array, _tuners[tuner]->name());
        }

        time_t epg_start;
        time_t epg_end;
        getTimeWithEPG(epg, &epg_start, &epg_end);
        DebugLog2("epg start: %ld, end: %ld\n", epg_start, epg_end);

        time_t pre_start = 0;
        time_t pre_end = 0;
        pre_end = time(NULL);
        DebugLog2("pre_end: %ld", pre_end);

        for (uint i = 0; i < array->count(); ++i)
        {
            Dictionary *cur = (Dictionary *)array->objectAtIndex(i);
            time_t cur_start;
            time_t cur_end;
            getTimeWithEPG(cur, &cur_start, &cur_end);
            DebugLog2("cur start: %ld, end: %ld\n", cur_start, cur_end);
            if ((pre_end <= epg_start) && (epg_end <= cur_start))
            {
                DebugLog2("insert: %d\n", i);
                array->insertObject(epg, i);
                result = true;
                break;
            }
            pre_start = cur_start;
            pre_end = cur_end;
        }

        if (!result)
        {
            if (pre_end <= epg_start)
            {
                DebugLog2("add\n");
                array->addObject(epg);
                result = true;
            }
            else
            {
                DebugLog2("no add\n");
            }
        }
        if (result)
        {
            epg->setInteger(_reservation_seq_id, KEY_EPG_RESV_ID);
            _reservation_seq_id = (_reservation_seq_id + 1) % 1000000;
            _reservations->setInteger(_reservation_seq_id, KEY_EPG_LAST_RESV_ID);

            //
            _reservations->writeToFile(_reservations_path, true);
        }

        break;
    }

    // unlock
    RaymUnlock(this);

    return result;
}

//
// tuner: 0 - (_tunerCount - 1)  cancel current
// tuner: -1  cancel reserve_id
//
bool Controller::cancel(int tuner, int reserve_id)
{
    bool result = false;

    // lock
    RaymLock(this);

    //
    if ((0 <= tuner) && (tuner < _tunerCount))
    {
        Array *array = _reservations->arrayForKey(_tuners[tuner]->name());
        if (array != NULL)
        {
            if (array->count() > 0)
            {
                Dictionary *epg = (Dictionary *)array->objectAtIndex(0);
                String *status = epg->stringForKey(KEY_EPG_STATUS);
                if (status != NULL)
                {
                    if (status->isEqualToString("running"))
                    {
                        epg->setString("stop", KEY_EPG_STATUS);
                        result = true;
                    }
                }
            }
        }
    }

    //
    else if ((tuner < 0) && (0 <= reserve_id) && (reserve_id < 1000000))
    {
        for (int i = 0; i < _tunerCount; ++i)
        {
            Array *array = _reservations->arrayForKey(_tuners[i]->name());
            if (array != NULL)
            {
                for (uint j = 0; j < array->count(); ++j)
                {
                    Dictionary *epg = (Dictionary *)array->objectAtIndex(j);
                    if (reserve_id == epg->integerForKey(KEY_EPG_RESV_ID))
                    {
                        String *status = epg->stringForKey(KEY_EPG_STATUS);
                        if ((status != NULL) && status->isEqualToString("running"))
                        {
                            epg->setString("stop", KEY_EPG_STATUS);
                        }
                        else
                        {
                            array->removeObjectAtIndex(j);
                        }
                        result = true;
                        break;
                    }
                }
            }
            if (result)
            {
                break;
            }
        }
    }

    if (result)
    {
        _reservations->writeToFile(_reservations_path, true);
    }

    // unlock
    RaymUnlock(this);

    return result;
}

//
// 録画制御
//
void Controller::periodic(void)
{
    bool need_update = false;

#ifdef OBJC_MEMORY_CHECK
    DebugLog0("global_objc_count_ = %d", Raym::global_objc_count_);
#endif

    // lock
    RaymLock(this);

    // 現在時刻取得
    time_t now = time(NULL);

    DebugLog2("periodic: %d", now);

    //
    for (int tuner = 0; tuner < _tunerCount; ++tuner)
    {
        Array *array = _reservations->arrayForKey(_tuners[tuner]->name());
        if ((array == NULL) || (array->count() == 0))
        {
            // next tuner
            continue;
        }

        //
        Dictionary *epg = (Dictionary *)array->objectAtIndex(0);
        time_t start;
        time_t end;
        getTimeWithEPG(epg, &start, &end);
        
        //
        // 録画停止要否チェック
        //
        bool stop_need = false;
        while (true)
        {
            String *status = epg->stringForKey(KEY_EPG_STATUS);
            if (status != NULL)
            {
                if (status->isEqualToString("stop"))
                {
                    stop_need = true;
                    break;
                }
                if (!status->isEqualToString("running"))
                {
                    break;
                }
            }
            if (end + OFFSET_OF_END_TIME <= now)
            {
                stop_need = true;
            }
            break;
        }
        if (stop_need)
        {
            DebugLog2("I try stop\n");
            int fd =_tuners[tuner]->stopRecording();
            if (fd < 0)
            {
                DebugLog1("stopRecording() error.\n");
            }
            else
            {
                DebugLog2("stopRecording() ok\n");
                DebugLog0("stop recording of \"%s\"", _tuners[tuner]->name());
                _close(fd);
            }
            array->removeObject(epg);
            
            if (array->count() > 0)
            {
                epg = (Dictionary *)array->objectAtIndex(0);
            }
            else
            {
                epg = NULL;
            }
            need_update = true;
        }

        if (epg == NULL)
        {
            // next tuner
            continue;
        }

        //
        // 録画開始要否チェック
        //
        bool start_need = false;
        start = end = 0;
        getTimeWithEPG(epg, &start, &end);
        if ((start != 0) && (end != 0))
        {
            String *status = epg->stringForKey(KEY_EPG_STATUS);
            if ((status == NULL) || !(status->isEqualToString("running")))
            {
                if (end + OFFSET_OF_END_TIME <= now)
                {
                    // 既に終了時間が経過しているので削除する
                    array->removeObject(epg);
                }
                else if (start + OFFSET_OF_START_TIME <= now)
                {
                    start_need = true;
                }
            }
        }

        if (start_need)
        {
            DebugLog2("I need start.\n");
            String *ch = epg->stringForKey(KEY_EPG_CHANNEL);
            if (ch != NULL)
            {
                int channel = atoi(ch->cString());
                DebugLog2("channel: %d\n", channel);
                std::string videopath = createVideoPath(tuner);
                if (videopath != "")
                {
                    DebugLog2("videopath: %s\n", videopath.c_str());
                    int fd = -1;
                    if (_sopen_s(&fd, videopath.c_str(),
                                 (_O_CREAT | _O_EXCL | _O_WRONLY | _O_BINARY | _O_TRUNC), _SH_DENYRW, (_S_IREAD | _S_IWRITE)) == 0)
                    {
                        DebugLog2("open ok.\n");
                        bool startResult = true;
                        if (_tuners[tuner]->channel() != channel)
                        {
                            if (!setChannel(tuner, channel))
                            {
                                DebugLog3("setChannel() ng.");
                                startResult = false;
                            }
                        }

                        if (startResult)
                        {
                            if (_tuners[tuner]->startRecording(fd))
                            {
                                DebugLog2("startRecording() ok.");
                                DebugLog0("start recording of \"%s\" to %s.", _tuners[tuner]->name(), videopath.c_str());
                            }
                            else
                            {
                                DebugLog3("Tuner::startRecording() failed.");
                                startResult = false;
                            }
                        }

                        if (startResult)
                        {
                            epg->setString("running", KEY_EPG_STATUS);
                        }
                        else
                        {
                            _close(fd);
                        }
                    }
                    else
                    {
                        DebugLog0("open ng. 0x%08x\n", errno);
                    }
                }
                else
                {
                    DebugLog0("Can't create videopath.\n");
                }
            }
            else
            {
                DebugLog0("error.\n");
            }
        }
    }

    if (need_update)
    {
        //
        _reservations->writeToFile(_reservations_path, true);
    }

#if 0
    // EPG収集時刻を取得
    String *collect_str = _props->stringForKey(KEY_COLLECT_EPG_TIME);
    if (collect_str != NULL)
    {
        // 秒に変換
        time_t collect_time = 0;
        getTimeWithString(collect_str, &collect_time);

        // 現在時刻と比較
        if ((collect_time <= now) && (now < collect_time + 1))
        {
            // タイマが起動中か確認
            if ((_timer_epg_s == NULL) || !_timer_epg_s->valid())
            {
                // EPG収集用タイマ起動(ISDB-S)
                RELEASE(_timer_epg_s);
                _timer_epg_s = Timer::alloc()->initWithTimeInterval(DEF_COLLECT_EPG_DELAY, this, (void *)CMD_COLLECT_EPG_ISDB_S, true);
                if (_timer_epg_s != NULL)
                {
                    _timer_epg_s->fire();
                }
            }

            // タイマが起動中か確認
            if ((_timer_epg_t == NULL) || !_timer_epg_t->valid())
            {
                // EPG収集用タイマ起動(ISDB-T)
                RELEASE(_timer_epg_t);
                _timer_epg_t = Timer::alloc()->initWithTimeInterval(DEF_COLLECT_EPG_DELAY, this, (void *)CMD_COLLECT_EPG_ISDB_T, true);
                if (_timer_epg_t != NULL)
                {
                    _timer_epg_t->fire();
                }
            }
        }
    }
#endif

    // unlock
    RaymUnlock(this);

    //
    // 1/100秒単位が 0 に近くなるように次回T.O.を微調整
    // ただし、windowsは精度が低いので期待しないことw
    //

#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
    static const __time64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000Ui64;
#else
    static const __time64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000ULL;
#endif
    // 現在時刻を取得
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);

    // EPOCH秒への変換
    __time64_t now_sec;
    __time64_t now_usec;
    now_sec = ft.dwHighDateTime;
    now_sec <<= 32;
    now_sec |= ft.dwLowDateTime;
    now_sec /= 10;  /*convert into microseconds*/
    now_sec -= DELTA_EPOCH_IN_MICROSECS;
    now_usec = (now_sec % 1000000UL);
    now_sec = now_sec / 1000000UL;

    TimeInterval interval = (TimeInterval)now_usec;
    interval = interval / 1000000;
    _timer_periodic->setTimeInterval(1.005 - interval);

#ifdef OBJC_MEMORY_CHECK
    DebugLog0("global_objc_count_ = %d", Raym::global_objc_count_);
#endif
}

void Controller::updateKeywordsReservation()
{
    DebugLog2("Controller::updateKeywordsReservation()");

    // lock
    RaymLock(this);

    // 予約情報からキーワード予約フラグが設定されているものを削除
    for (int tuner = 0; tuner < _tunerCount; ++tuner)
    {
        Array *epgs = _reservations->arrayForKey(_tuners[tuner]->name());
        if (epgs != NULL)
        {
            for (uint epgs_idx_offset = epgs->count(); epgs_idx_offset > 0; --epgs_idx_offset)
            {
                Dictionary *epg = (Dictionary *)epgs->objectAtIndex(epgs_idx_offset - 1);
                if (epg->boolForKey(KEY_EPG_RESERVED_BY_KEYWORDS))
                {
                    epgs->removeObject(epg);
                }
            }
        }
    }

    // キーワードで検索
    Dictionary *keywords_info = _reservations->dictionaryForKey(KEY_EPG_KEYWORDS);

    if ((keywords_info != NULL) && (_epgs != NULL) && (_epgs->dictionaryForKey(KEY_SERVICES) != NULL))
    {
        // キーワード、EPG有り

        // _epgs からキー(サービスID)を取得
        Array *service_id_keys = _epgs->dictionaryForKey(KEY_SERVICES)->allKeys();

        // サービスIDでループ
        for (uint service_id_keys_idx = 0; service_id_keys_idx < service_id_keys->count(); ++service_id_keys_idx)
        {
            // サービスID
            String *service_id = (String *)service_id_keys->objectAtIndex(service_id_keys_idx);

            // 局名
            String *stationName = stationNameForServiceID(service_id);
            if (stationName == NULL)
            {
                // 局名が取得できなかったら次のサービスIDへ
                continue;
            }

            // 指定サービスIDのEPGを取得
            Array *events = _epgs->dictionaryForKey(KEY_SERVICES)->arrayForKey(service_id);

            // イベントIDでループ
            for (uint events_idx = 0; events_idx < events->count(); ++events_idx)
            {
                // 指定イベントIDのEPGを取得
                Dictionary *epg = (Dictionary *)events->objectAtIndex(events_idx);

                // 取得したEPGがキーワードの条件にマッチするか

                time_t start;
                getTimeWithString(epg->stringForKey(KEY_EPG_START), &start);

                // キーワードの配列を取得
                Array *keywords = keywords_info->allKeys();
                for (uint keywords_idx = 0; keywords_idx < keywords->count(); ++keywords_idx)
                {
                    // キーワード
                    String *kwd = (String *)keywords->objectAtIndex(keywords_idx);

                    // キーワード(分割)
                    Array *kwds = kwd->componentsSeparatedByString(",");
                    if (kwds == NULL)
                    {
                        continue;
                    }

                    // キーワード情報
                    Dictionary *kwdinf = keywords_info->dictionaryForKey(kwd);

                    // 局名フィルタ
                    String *kwdinf_service_id = kwdinf->stringForKey(KEY_EPG_SERVICE_ID);
                    if (kwdinf_service_id != NULL)
                    {
                        // 局名フィルタ有り
                        String *sname = stationNameForServiceID(kwdinf_service_id);
                        if (sname != NULL)
                        {
                            if (!stationName->isEqualToString(sname))
                            {
                                // 局名が異なる場合は次のキーワードへ
                                continue;
                            }
                        }
                    }

                    // 開始時刻フィルタ
                    String *kwdinf_start = kwdinf->stringForKey(KEY_EPG_START);
                    if (kwdinf_start != NULL)
                    {
                        // 開始時刻フィルタ有り
                        String *st = kwdinf_start->stringByAppendingString(":00");
                        time_t kwd_st, kwd_ed;
                        getTimeWithString(st, &kwd_st);
                        kwd_ed = kwd_st + 3600;

                        // 開始時刻フィルタの時刻 <= EPGの開始時刻 <= 開始時刻フィルタの時刻＋60min ならOK
                        if ((kwd_st > start) || (start > kwd_ed))
                        {
                            // 範囲外なので次のキーワードへ
                            continue;
                        }
                    }

                    // タイトル
                    String *title = epg->stringForKey(KEY_EPG_TITLE);
                    bool title_flag = (title != NULL);

                    // 概要
                    String *desc = epg->stringForKey(KEY_EPG_DESCRIPTION);
                    bool desc_flag = (desc != NULL);

                    // キーワード(分割)がタイトル／概要のどちらかに、全て(AND)含まれていたら予約対象とする
                    for (uint kwds_idx = 0; kwds_idx < kwds->count(); ++kwds_idx)
                    {
                        String *kw = ((String *)kwds->objectAtIndex(kwds_idx))->stringByTrimming();

                        // タイトル
                        if (title != NULL)
                        {
                            Range r = title->rangeOfString(kw);
                            if (r.location == NotFound)
                            {
                                title_flag = false;
                            }
                        }

                        // 概要
                        if (desc != NULL)
                        {
                            Range r = desc->rangeOfString(kw);
                            if (r.location == NotFound)
                            {
                                desc_flag = false;
                            }
                        }
                    }

                    if (title_flag || desc_flag)
                    {
                        // タイトル／概要のどちらかに、キーワード(分割)が全て含まれていたので
                        // この EPG を予約対象とする
                        Dictionary *epg2 = Dictionary::dictionaryWithDictionary(epg);
                        if (epg2 != NULL)
                        {
                            DebugLog0("kwd: %s", kwd->cString());
                            if (title != NULL)
                            {
                                DebugLog0("title: %s", title->cString());
                            }
                            if (desc != NULL)
                            {
                                DebugLog0("desc: %s", desc->cString());
                            }
                            epg2->setBool(true, KEY_EPG_RESERVED_BY_KEYWORDS);
                            reserve(epg2);
                        }
                    }
                }
            }
        }
    }

    // unlock
    RaymUnlock(this);
}

void Controller::updateSchedule()
{
    DebugLog2("Controller::updateSchedule()");

    // lock
    RaymLock(this);

    // レジューム時刻
    time_t resume_time = 0;

    // EPG収集時刻を取得
    String *collect_epg_time = _props->stringForKey(KEY_COLLECT_EPG_TIME);
    if (collect_epg_time != NULL)
    {
        getTimeWithString(collect_epg_time, &resume_time);
    }

    // 録画予約をチェック
    for (int tuner = 0; tuner < _tunerCount; ++tuner)
    {
        Array *array = _reservations->arrayForKey(_tuners[tuner]->name());
        if ((array == NULL) || (array->count() == 0))
        {
            // next tuner
            continue;
        }

        Dictionary *epg = (Dictionary *)array->objectAtIndex(0);
        time_t start = 0;
        time_t end = 0;
        getTimeWithEPG(epg, &start, &end);
        if ((start != 0) && (end != 0))
        {
            if (start < resume_time)
            {
                resume_time = start;
            }
        }
    }

    //
    TM resume_tm;
    resume_time += OFFSET_OF_WAKEUP;    // 起動時刻を調整
    if (localtime_s(&resume_tm, &resume_time) == 0)
    {
        if (_props->boolForKey(KEY_SHUTDOWN) && (_props->stringForKey(KEY_POWER_MANAGER) != NULL))
        {
            char http_req[256];
            sprintf_s(http_req, "http://%s/iptv.pl?wakeup=%04d/%02d/%02d_%02d:%02d:00",
                      _props->stringForKey(KEY_POWER_MANAGER)->cString(),
                      resume_tm.tm_year + 1900, resume_tm.tm_mon + 1,
                      resume_tm.tm_mday, resume_tm.tm_hour, resume_tm.tm_min);
            URL *url = URL::URLWithString(http_req);
            URLRequest *req = URLRequest::requestWithURL(url);
            URLResponse *resp = NULL;
            Error *error = NULL;
            Data *data = URLConnection::sendSynchronousRequest(req, &resp, &error);
            if (data != NULL)
            {
                String *html = String::stringWithUTF8String((const char *)data->bytes());
                if ((html != NULL) && (html->rangeOfString("<body>OK</body>").location != NotFound))
                {
                    DebugLog0("set wake schedule to \"%04d/%02d/%02d %02d:%02d:00\".",
                              resume_tm.tm_year + 1900, resume_tm.tm_mon + 1, resume_tm.tm_mday, resume_tm.tm_hour, resume_tm.tm_min);
                }
            }
        }
        else
        {
            resetWakeSchedule();
            if (setWakeSchedule(resume_tm.tm_year + 1900, resume_tm.tm_mon + 1, resume_tm.tm_mday, resume_tm.tm_hour, resume_tm.tm_min))
            {
                DebugLog0("set wake schedule to \"%04d/%02d/%02d %02d:%02d:00\".",
                          resume_tm.tm_year + 1900, resume_tm.tm_mon + 1, resume_tm.tm_mday, resume_tm.tm_hour, resume_tm.tm_min);
            }
        }
    }

    // unlock
    RaymUnlock(this);
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- チューナ制御 -------
#endif

void Controller::scanChannel(int tuner)
{
    DebugLog2("Controller::scanChannel(%d)", tuner);

    if ((tuner < 0) || (_tunerCount <= tuner))
    {
        DebugLog3("Invalid tuner: %d", tuner);
        return;
    }

    DebugLog0("start channel scan of \"%s\".", _tuners[tuner]->name());

    // lock
    RaymLock(this);

    // 設定ファイルから全チューナ情報取得
    Dictionary *tunersInfo = _props->dictionaryForKey(KEY_TUNERS);
    if (tunersInfo == NULL)
    {
        // 無ければ作成
        tunersInfo = Dictionary::dictionaryWithCapacity(0);
        _props->setObject(tunersInfo, KEY_TUNERS);
    }

    // 全チューナ情報から指定のチューナ情報を取得
    Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[tuner]->name());
    if (tunerInfo == NULL)
    {
        // 無ければ作成
        tunerInfo = Dictionary::dictionaryWithCapacity(0);
        tunersInfo->setObject(tunerInfo, _tuners[tuner]->name());
    }

    // チューナを未初期化に設定
    tunerInfo->setBool(false, KEY_INITIALIZED);

    // unlock
    RaymUnlock(this);

    // 全チャンネル情報を作成
    Dictionary *channels = Dictionary::dictionaryWithCapacity(0);
    tunerInfo->setObject(channels, KEY_CHANNELS);

    // チューナタイプにより最大チャンネル数を設定
    int max_channel;
    Tuner::Type type = _tuners[tuner]->type();
    if (type == Tuner::ISDB_S)
    {
        max_channel = Tuner::MAX_CHANNELS_ISDB_S;
    }
    else
    {
        max_channel = Tuner::MAX_CHANNELS_ISDB_T;
    }

    // 最終設定成功チャンネル保持用変数
    int lastChannel = -1;

    // チャンネルサーチ
    for (int ch = 0; ch <= max_channel; ++ch)
    {
        // チャンネル情報
        Dictionary *channelInfo = Dictionary::dictionaryWithCapacity(0);

        // チャンネルキー(10進数:PTに対するチャンネル番号)
        char chkey[4];
        sprintf_s(chkey, sizeof(chkey), "%03d", ch);
        channels->setObject(channelInfo, chkey);

        // チャンネルID(論理チャンネル番号)
        char channelID[8];
        if (type == Tuner::ISDB_S)
        {
            if (ch < 12)
            {
                sprintf_s(channelID, sizeof(channelID), "BS%02d", 1 + 2 * ch);
            }
            else if (ch < 24)
            {
                sprintf_s(channelID, sizeof(channelID), "ND%02d", 2 + 2 * (ch - 12));
            }
            else
            {
                sprintf_s(channelID, sizeof(channelID), "ND%02d", 1 + 2 * (ch -24));
            }
        }
        else
        {
            static int TABLE[][3] =
            {
                {   2,  0,   3 },
                {  12,  1,  22 },
                {  21,  0,  12 },
                {  62,  1,  63 },
                { 112,  0,  62 }
            };

            uint i;
            for (i = 0; i < sizeof(TABLE)/sizeof(*TABLE); ++i)
            {
                if (ch <= TABLE[i][0])
                {
                    sprintf_s(channelID, sizeof(channelID), "%s%d", TABLE[i][1] ? "C" : "", ch + TABLE[i][2] - TABLE[i][0]);
                    break;
                }
            }
        }

        // チャンネルIDを設定
        channelInfo->setString(channelID, KEY_CHANNEL_ID);

        // lock
        RaymLock(this);

        // チャンネル設定
        if (_tuners[tuner]->setChannel(ch))
        {
            // 設定成功
            DebugLog0("  CH %s: OK", chkey);

            // チャンネルを有効に設定
            channelInfo->setBool(true, KEY_ENABLED);

            // 局情報を取得
            Analyzer *an = Analyzer::alloc()->init();
            _tuners[tuner]->setListener(an);

            Dictionary *stationInfo = an->stationInfo();
            if (stationInfo != NULL)
            {
                if (stationInfo->stringForKey(KEY_NAME) != NULL)
                {
                    channelInfo->setString(stationInfo->stringForKey(KEY_NAME), KEY_NAME);
                }
                if (stationInfo->objectForKey(KEY_SERVICES) != NULL)
                {
                    channelInfo->setObject(stationInfo->objectForKey(KEY_SERVICES), KEY_SERVICES);
                }
                if (channelInfo->stringForKey(KEY_NAME) != NULL)
                {
                    DebugLog0("    Name: %s", channelInfo->stringForKey(KEY_NAME)->cString());
                }
            }

            _tuners[tuner]->setListener(NULL);
            an->release();

            // 成功したチャンネルを保持
            lastChannel = ch;
        }
        else
        {
            DebugLog0("  CH %s: NG", chkey);
        }

        RaymUnlock(this);
    }

    RaymLock(this);

    // チューナを初期化済みに更新
    tunerInfo->setBool(true, KEY_INITIALIZED);

    // 設定が成功したチャンネルが有るか
    if (lastChannel >= 0)
    {
        // チューナを有効に設定
        tunerInfo->setBool(true, KEY_ENABLED);

        // 最終チャンネルをステータスファイルに設定
        Dictionary *tunersInfo = _status->dictionaryForKey(KEY_TUNERS);
        if (tunersInfo == NULL)
        {
            tunersInfo = Dictionary::dictionaryWithCapacity(0);
            _status->setObject(tunersInfo, KEY_TUNERS);
        }

        Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[tuner]->name());
        if (tunerInfo == NULL)
        {
            tunerInfo = Dictionary::dictionaryWithCapacity(0);
            tunersInfo->setObject(tunerInfo, _tuners[tuner]->name());
        }

        tunerInfo->setInteger(lastChannel, KEY_CHANNEL);

        _status->writeToFile(_status_path, true);
    }

    // 設定ファイルを更新
    _props->writeToFile(_props_path, true);

    // unlock
    RaymUnlock(this);
}

int Controller::getChannel(int tuner)
{
    DebugLog2("Controller::getChannel()");

    int channel = -1;

    if ((0 <= tuner) && (tuner < _tunerCount))
    {
        // lock
        RaymLock(this);

        Dictionary *tunersInfo = _status->dictionaryForKey(KEY_TUNERS);
        if (tunersInfo != NULL)
        {
            Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[tuner]->name());
            if (tunerInfo != NULL)
            {
                channel = tunerInfo->integerForKey(KEY_CHANNEL);
            }
        }

        // unlock
        RaymUnlock(this);
    }

    return channel;
}

bool Controller::setChannel(int tuner, int channel)
{
    DebugLog2("Controller::setChannel()");

    bool result = false;

    if ((0 <= tuner) && (tuner < _tunerCount))
    {
        // lock
        RaymLock(this);

        if (!_tuners[tuner]->isLocked())
        {
            Dictionary *tunersInfo = _status->dictionaryForKey(KEY_TUNERS);
            if (tunersInfo == NULL)
            {
                tunersInfo = Dictionary::dictionaryWithCapacity(0);
                _status->setObject(tunersInfo, KEY_TUNERS);
            }

            Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[tuner]->name());
            if (tunerInfo == NULL)
            {
                tunerInfo = Dictionary::dictionaryWithCapacity(0);
                tunersInfo->setObject(tunerInfo, _tuners[tuner]->name());
            }

            if (channel != _tuners[tuner]->channel())
            {
                if (_tuners[tuner]->setChannel(channel))
                {
                    tunerInfo->setInteger(channel, KEY_CHANNEL);

                    _status->writeToFile(_status_path, true);

                    result = true;
                }
                else
                {
                    DebugLog0("set channel failed. %d, %d", tuner, channel);
                }
            }
        }

        // unlock
        RaymUnlock(this);
    }

    return result;
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- システム関連 周期処理 -------
#endif

//
// チューナ(streaming)制御／システム関連 周期処理
//
void Controller::periodic_2(void)
{
    //
    // UDPポート監視
    //

    // マッピング(UDPPort:tuner,ch)情報取得
    Dictionary *mapping = NULL;
    if ((_streaming_ctrls != NULL) && ((mapping = _streaming_ctrls->dictionaryForKey(KEY_MAPPING_UDP_TO_TUNER_SERVICE_ID)) != NULL))
    {
        // マッピング情報取得OK

        // 使用中のUDPの情報を取得
        // どれだけ使用中なのか不明なので、まずは必要サイズを調べる
        DWORD size = 0;
        if (GetExtendedUdpTable(NULL, &size, true, AF_INET, UDP_TABLE_OWNER_PID, 0) == ERROR_INSUFFICIENT_BUFFER)
        {
            // ERROR_INSUFFICIENT_BUFFER の場合、必要なバッファサイズが size に格納される

            // バッファ確保
            PMIB_UDPTABLE_OWNER_PID udptable = (PMIB_UDPTABLE_OWNER_PID)malloc(size);
            if (udptable != NULL)
            {
                // バッファ確保OK

                // UDP情報取得
                if (GetExtendedUdpTable(udptable, &size, true, AF_INET, UDP_TABLE_OWNER_PID, 0) == NO_ERROR)
                {
                    // 取得OK
                    DebugLog3("udptable->dwNumEntries: %d", udptable->dwNumEntries);

                    // 停止要否確認
                    Dictionary *using_port = _streaming_ctrls->dictionaryForKey(KEY_UDP_IN_USE);
                    if (using_port != NULL)
                    {
                        // key = 使用中ポート
                        Array *using_ports = using_port->allKeys();
                        if (using_ports != NULL)
                        {
                            // 使用中ポートでループ
                            for (uint i = 0; i < using_ports->count(); ++i)
                            {
                                // 停止要否フラグ
                                bool stop_need = true;

                                // 使用中のUDP情報でループ
                                for (uint j = 0; j < udptable->dwNumEntries; ++j)
                                {
                                    if (((String *)using_ports->objectAtIndex(i))->intValue() == ntohs((WORD)udptable->table[j].dwLocalPort))
                                    {
                                        // 使用中なので停止不要
                                        stop_need = false;
                                        break;
                                    }
                                }

                                // 停止要否
                                if (stop_need)
                                {
                                    // マッピング情報を取得
                                    String *tuner_and_service_id = mapping->stringForKey((String *)using_ports->objectAtIndex(i));
                                    if (tuner_and_service_id != NULL)
                                    {
                                         // チューナとサービスIDに分割
                                        Range r = tuner_and_service_id->rangeOfString("_");
                                        if (r.location != NotFound)
                                        {
                                            int tuner = tuner_and_service_id->substringToIndex(r.location)->intValue();
                                            int service_id = tuner_and_service_id->substringFromIndex(r.location + 1)->intValue();
                                            DebugLog3("tuner: %d, service_id: %d", tuner, service_id);

                                            DebugLog0("auto streaming stop: %s", ((String *)using_ports->objectAtIndex(i))->cString());

                                            _tuners[tuner]->stopStreaming();
                                            using_port->removeObjectForKey((String *)using_ports->objectAtIndex(i));
                                        }
                                    }
                                }
                            }
                        }
                    }


                    // 起動要否確認
                    for (uint i = 0; i < udptable->dwNumEntries; ++i)
                    {
                        // ポート番号を文字列に変換して
                        char port[10];
                        sprintf_s(port, "%d", ntohs((WORD)udptable->table[i].dwLocalPort));
                        DebugLog3("port = %s", port);

                        // マッピング情報を取得
                        String *tuner_and_service_id = mapping->stringForKey(port);
                        if (tuner_and_service_id != NULL)
                        {
                            // 取得OK: 監視対象ポートが使用されている

                            // 使用アプリを調べる
                            bool auto_streaming = false;
                            char exec_path[MAX_PATH];
                            memset(exec_path, 0, sizeof(exec_path));

                            // プロセスハンドル取得
                            size_t returnValue;
                            HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, udptable->table[i].dwOwningPid);
                            if (hProcess != NULL)
                            {
                                TCHAR exec[MAX_PATH];
                                memset(exec, 0, sizeof(exec));
                                DWORD len = sizeof(exec) - 1;

                                // イメージ取得
                                if (QueryFullProcessImageName(hProcess, 0, exec, &len))
                                {
                                    // ワイド -> マルチ 変換
                                    if (wcstombs_s(&returnValue, exec_path, sizeof(exec_path), exec, _TRUNCATE) == 0)
                                    {
                                        // 成功

                                        // とりあえず、、、現状は "ffmpeg.exe" / "vlc.exe" / "Kodi.exe" があったら auto_streaming を true にする
                                        if ((strstr(exec_path, "ffmpeg.exe") != NULL) ||
                                            (strstr(exec_path, "vlc.exe") != NULL) ||
                                            (strstr(exec_path, "Kodi.exe") != NULL))
                                        {
                                            auto_streaming = true;
                                        }
                                    }
                                }

                                // プロセスハンドル解放
                                CloseHandle(hProcess);
                            }

                            if (auto_streaming)
                            {
                                // チューナとサービスIDに分割
                                Range r = tuner_and_service_id->rangeOfString("_");
                                if (r.location != NotFound)
                                {
                                    int tuner = tuner_and_service_id->substringToIndex(r.location)->intValue();
                                    int service_id = tuner_and_service_id->substringFromIndex(r.location + 1)->intValue();
                                    DebugLog3("tuner: %d, service_id: %d", tuner, service_id);

                                    // 暫定
                                    int channel = -1;
                                    if (_streaming_ctrls->dictionaryForKey(KEY_MAPPING_TUNER_SERVICE_ID_TO_CHANNEL) != NULL)
                                    {
                                        channel = _streaming_ctrls->dictionaryForKey(KEY_MAPPING_TUNER_SERVICE_ID_TO_CHANNEL)->integerForKey(tuner_and_service_id);
                                    }

                                    // lock
                                    RaymLock(this);

                                    // ロックされてない場合
                                    if (!_tuners[tuner]->isLocked())
                                    {
                                        // ストリーミング開始可能

                                        if (_tuners[tuner]->channel() != channel)
                                        {
                                            setChannel(tuner, channel);
                                        }

                                        SOCKADDR_IN dst_addr;
                                        dst_addr.sin_family = AF_INET;
                                        dst_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
                                        dst_addr.sin_port = (WORD)udptable->table[i].dwLocalPort;

                                        if (_tuners[tuner]->startStreaming(&dst_addr))
                                        {
                                            // 成功
                                            DebugLog0("auto streaming start: %d", ntohs((WORD)udptable->table[i].dwLocalPort));

                                            // 使用中ポートに登録
                                            using_port = _streaming_ctrls->dictionaryForKey(KEY_UDP_IN_USE);
                                            if (using_port == NULL)
                                            {
                                                using_port = Dictionary::dictionaryWithCapacity(0);
                                                _streaming_ctrls->setObject(using_port, KEY_UDP_IN_USE);
                                            }
                                            using_port->setBool(true, port);
                                        }
                                    }

                                    // unlock
                                    RaymUnlock(this);
                                }
                            }
                        }
                    }
                }

                // バッファ解放
                free(udptable);
            }
        }
    }

    //
    // HLS
    //
    Dictionary *hls_info = _streaming_ctrls->dictionaryForKey(KEY_HLS_INFO);
    if (hls_info != NULL)
    {
        for (int i = 0; i < _tunerCount; ++i)
        {
            RaymLock(this);
            Dictionary *hls_info_tuner = hls_info->dictionaryForKey(_tuners[i]->name());
            if (hls_info_tuner != NULL)
            {
                int counter = hls_info_tuner->integerForKey(KEY_COUNTER);
                if (counter < 60)
                {
                    hls_info_tuner->setInteger(counter + 1, KEY_COUNTER);
                }
                else
                {
                    hls_info->removeObjectForKey(_tuners[i]->name());
                }
            }
            RaymUnlock(this);
        }
    }


    //
    // 1/100秒単位が 0 に近くなるように次回T.O.を微調整
    //

#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
    static const __time64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000Ui64;
#else
    static const __time64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000ULL;
#endif
    // 現在時刻を取得
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);

    // EPOCH秒への変換
    __time64_t now_sec;
    __time64_t now_usec;
    now_sec = ft.dwHighDateTime;
    now_sec <<= 32;
    now_sec |= ft.dwLowDateTime;
    now_sec /= 10;  /*convert into microseconds*/
    now_sec -= DELTA_EPOCH_IN_MICROSECS;
    now_usec = (now_sec % 1000000UL);
    now_sec = now_sec / 1000000UL;

    TimeInterval interval = (TimeInterval)now_usec;
    interval = interval / 1000000;
    _timer_periodic_2->setTimeInterval(1.005 - interval);
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- HTTP制御 -------
#endif

static std::string epg_regist_form(Dictionary *epg)
{
    DebugLog3("epg_regist_form() start.");

    std::string epgs;

    if ((epg != NULL) &&
        (epg->stringForKey(KEY_EPG_SERVICE_ID) != NULL) &&
        (epg->stringForKey(KEY_EPG_EVENT_ID) != NULL) &&
        (epg->stringForKey(KEY_EPG_START) != NULL) &&
        (epg->stringForKey(KEY_EPG_END) != NULL) &&
        (epg->stringForKey(KEY_EPG_DATE) != NULL) &&
        (epg->stringForKey(KEY_EPG_TITLE) != NULL))
    {
        epgs += "<form id=\"";
        epgs += "epg_";
        epgs += epg->stringForKey(KEY_EPG_SERVICE_ID)->cString();
        epgs += "_";
        epgs += epg->stringForKey(KEY_EPG_EVENT_ID)->cString();
        epgs += "\" title=\"";
        epgs += epg->stringForKey(KEY_EPG_START)->substringToIndex(5)->cString();
        epgs += "-";
        epgs += epg->stringForKey(KEY_EPG_END)->substringToIndex(5)->cString();
        epgs += "\" class=\"panel\" action=\"regist.cgi\" method=\"GET\" target=\"_self\" onclick='return confirm(\"Is it ok?\");'>";

        // 放送時間
        epgs += "<h2>";
        epgs += LocalizedString(KEY_I18N_Broadcasting_Time, NULL)->cString();
        epgs += "</h2>";
        epgs += "<fieldset>";
        epgs += "<p class=\"normalText\">&nbsp;&nbsp;";
        epgs += epg->stringForKey(KEY_EPG_DATE)->substringFromIndex(5)->cString();
        epgs += "&nbsp;";
        epgs += epg->stringForKey(KEY_EPG_START)->substringToIndex(5)->cString();
        epgs += "-";
        epgs += epg->stringForKey(KEY_EPG_END)->substringToIndex(5)->cString();
        epgs += "</p>";
        epgs += "</fieldset>";

        // 番組名
        epgs += "<h2>";
        epgs += LocalizedString(KEY_I18N_Program_Title, NULL)->cString();
        epgs += "</h2>";
        epgs += "<fieldset>";
        epgs += "<p class=\"normalText\">&nbsp;&nbsp;";
        epgs += epg->stringForKey(KEY_EPG_TITLE)->cString();
        epgs += "</p>";
        epgs += "</fieldset>";

        // 概要
        if (epg->stringForKey(KEY_EPG_DESCRIPTION) != NULL)
        {
            epgs += "<h2>";
            epgs += LocalizedString(KEY_I18N_Description, NULL)->cString();
            epgs += "</h2>";
            epgs += "<fieldset>";
            epgs += "<p class=\"normalText\">&nbsp;&nbsp;";
            epgs += epg->stringForKey(KEY_EPG_DESCRIPTION)->cString();
            epgs += "</p>";
            epgs += "</fieldset>";
        }

        //
        epgs += "<input type=\"hidden\" name=\"service_id\" value=\"";
        epgs += epg->stringForKey(KEY_EPG_SERVICE_ID)->cString();
        epgs += "\"/>";
        epgs += "<input type=\"hidden\" name=\"event_id\" value=\"";
        epgs += epg->stringForKey(KEY_EPG_EVENT_ID)->cString();
        epgs += "\"/>";
        epgs += "<input class=\"redButton\" type=\"submit\" value=\"";
        epgs += LocalizedString(KEY_I18N_New_Reservation, NULL)->cString();
        epgs += "\"/>";
    //    epgs += "<a class=\"redButton\" type=\"submit\">";
    //    epgs += LocalizedString(KEY_I18N_New_Reservation, NULL)->cString();
    //    epgs += "</a>";
        epgs += "</form>";
    }

    return epgs;
}

HTTPResponse *responseWithDictionary(HTTPRequest *request, Dictionary *dictionary)
{
    HTTPResponse *result = NULL;
    if ((request != NULL) && (dictionary != NULL))
    {
        std::string xml = dictionary->toString();

        // header
        InternetTextMessageHeader *header = InternetTextMessageHeader::alloc()->init();
        // Date
        // Server
        // Content-Encoding
        // Last-Modified
        // Content-Type
        header->setFieldBodyWithName("application/xml", "Content-Type");
        // Connection
        // Tranfer-Encoding
        // Content-Length
        header->setFieldBodyWithName(String::stringWithFormat("%I64u", xml.length()), "Content-Length");

        // body
        InternetTextMessageBody *body = InternetTextMessageBody::alloc()->initWithString(String::stringWithUTF8String(xml.c_str()));

        // message
        InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, body);
        RELEASE(header);
        RELEASE(body);
        if (message != NULL)
        {
            result = HTTPResponse::alloc()->init();
            result->autorelease();
            result->setVersion(request->version());
            result->setReason(NET::HTTPDaemon::reasonForStatus(200));
            result->setStatus(200);
            result->setMessage(message);
            RELEASE(message);
        }
    }
    return result;
}

// positive response by XML
HTTPResponse *responseForSuccess(HTTPRequest *request)
{
    Dictionary *dict = Dictionary::dictionaryWithCapacity(0);
    dict->setString("Success", KEY_RESULT);
    return responseWithDictionary(request, dict);
}

// negative response by XML
HTTPResponse *responseForFailed(HTTPRequest *request)
{
    Dictionary *dict = Dictionary::dictionaryWithCapacity(0);
    dict->setString("Failed", KEY_RESULT);
    return responseWithDictionary(request, dict);
}

HTTPResponse *Controller::responseWithHTML(HTTPRequest *request, String *html)
{
    HTTPResponse *result = NULL;
    if ((html != NULL) && (request != NULL))
    {
        // header
        InternetTextMessageHeader *header = InternetTextMessageHeader::alloc()->init();
        // Date
        // Server
        // Content-Encoding
        // Last-Modified
        // Content-Type
        header->setFieldBodyWithName("text/html", "Content-Type");
        // Connection
        // Tranfer-Encoding
        // Content-Length
        header->setFieldBodyWithName(String::stringWithFormat("%I64u", html->length()), "Content-Length");

        // body
        InternetTextMessageBody *body = InternetTextMessageBody::alloc()->initWithString(html);

        // message
        InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, body);
        RELEASE(header);
        RELEASE(body);
        if (message != NULL)
        {
//            result = HTTPResponse::response();
            result = HTTPResponse::alloc()->init();
            result->setVersion(request->version());
            result->setReason(NET::HTTPDaemon::reasonForStatus(200));
            result->setStatus(200);
            result->setMessage(message);
            result->autorelease();
            RELEASE(message);
        }
    }
    return result;
}

HTTPResponse *Controller::responseWithUTF8Text(HTTPRequest *request, String *text)
{
    HTTPResponse *result = NULL;
    if ((text != NULL) && (request != NULL))
    {
        // header
        InternetTextMessageHeader *header = InternetTextMessageHeader::alloc()->init();
        // Date
        // Server
        // Content-Encoding
        // Last-Modified
        // Content-Type
        header->setFieldBodyWithName("text/plane; charset=UTF-8", "Content-Type");
        // Connection
        // Tranfer-Encoding
        // Content-Length
        header->setFieldBodyWithName(String::stringWithFormat("%I64u", text->length()), "Content-Length");

        // body
        InternetTextMessageBody *body = InternetTextMessageBody::alloc()->initWithString(text);

        // message
        InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, body);
        RELEASE(header);
        RELEASE(body);
        if (message != NULL)
        {
//            result = HTTPResponse::response();
            result = HTTPResponse::alloc()->init();
            result->setVersion(request->version());
            result->setReason(NET::HTTPDaemon::reasonForStatus(200));
            result->setStatus(200);
            result->setMessage(message);
            result->autorelease();
            RELEASE(message);
        }
    }
    return result;
}

HTTPResponse *Controller::responseByResult(HTTPRequest *request, bool result)
{
    HTTPResponse *retval = NULL;

    bool iui = false;

    String *ref = NULL;
    InternetTextMessage *msg = request->message();
    if (msg != NULL)
    {
        InternetTextMessageHeader *header = msg->header();
        if (header != NULL)
        {
            String *field_referer = header->fieldBodyForName("Referer");
            if (field_referer != NULL)
            {
                String *field_host    = header->fieldBodyForName("Host");
                if (field_host != NULL)
                {
                    std::string tmp = "^http://";
                    tmp += field_host->cString();
                    tmp += "/(programs_(t|s)_\\d+|reservation)\\.html$";
                    iui = field_referer->isMatch(tmp.c_str());
                    DebugLog0("tmp: %s", tmp.c_str());
                    DebugLog0("ref: %s", field_referer->cString());
                }
            }
        }
    }

    if (iui)
    {
        String *path = _httpd->rootPath()->stringByAppendingPathComponent("template2.html");
        String *html = String::stringWithContentsOfFile(path->cString(), UTF8StringEncoding);
        if (html != NULL)
        {
            html = html->stringByReplacingOccurrencesOfString("%%TITLE%%", "Result");
            std::string contents;
            std::string reservations;
            contents += "<div id=\"home\" class=\"panel\" title=\"Result\" selected=\"true\">";
            if (result)
            {
                contents += "<h2>Success</h2>";
            }
            else
            {
                contents += "<h2>Failed</h2>";
            }
            contents += "</div>";

            html = html->stringByReplacingOccurrencesOfString("%%CONTENTS%%", contents.c_str());
            retval = responseWithHTML(request, html);
        }
    }
    else
    {
        if (result)
        {
            retval = responseForSuccess(request);
        }
        else
        {
            retval = responseForFailed(request);
        }
    }

    return retval;
}

HTTPResponse *Controller::responseForMain(HTTPRequest *request, SOCKADDR_IN *client)
{
    DebugLog2("Controller::responseForMain()");

    HTTPResponse *result = NULL;
    while ((request != NULL) && (client != NULL))
    {
        String *path = _httpd->rootPath();
        if (path == NULL)
        {
            DebugLog3("_httpd->rootPath() ng.");
            break;
        }

        path = path->stringByAppendingPathComponent("template1.html");
        if (path == NULL)
        {
            DebugLog3("path->stringByAppendingPathComponent() ng.");
            break;
        }

        String *html = String::stringWithContentsOfFile(path->cString(), UTF8StringEncoding);
        if (html == NULL)
        {
            DebugLog3("String::stringWithContentsOfFile() ng.");
            break;
        }

        String *server_name = _props->stringForKey(KEY_NAME);
        if (server_name == NULL)
        {
            DebugLog3("_props->stringForKey(KEY_NAME) ng.");
            break;
        }

        html = html->stringByReplacingOccurrencesOfString("%%TITLE%%", server_name);
        if (html == NULL)
        {
            DebugLog3("html->stringByReplacingOccurrencesOfString() ng.");
            break;
        }

        html = html->stringByReplacingOccurrencesOfString("%%PAGE_TITLE%%", LocalizedString(KEY_I18N_Main_Menu, NULL));
        if (html == NULL)
        {
            DebugLog3("html->stringByReplacingOccurrencesOfString() ng.");
        }

        std::string contents;
        contents += "<div id=\"home\" class=\"panel\" selected=\"true\">";
        contents += "<ul>";
        contents += "<li><a target=\"_self\" href=\"/programs.html\">";
        contents += LocalizedString(KEY_I18N_Programs, NULL)->cString();
        contents += "</a></li>";
        contents += "<li><a target=\"_self\" href=\"/tv.html\">";
        contents += LocalizedString(KEY_I18N_TV, NULL)->cString();
        contents += "</a></li>";
        contents += "<li><a target=\"_self\" href=\"/video.html\">";
        contents += LocalizedString(KEY_I18N_Video, NULL)->cString();
        contents += "</a></li>";
        contents += "<li><a target=\"_self\" href=\"/reservation.html\">";
        contents += LocalizedString(KEY_I18N_Reservation, NULL)->cString();
        contents += "</a></li>";
        contents += "</ul>";
#if 0
        contents += "<ul>";
        contents += "<li><a target=\"_self\" href=\"/exec_vlc.html\">VLC media player";
        contents += "</ul>";
#endif
        contents += "<ul>";
        contents += "<li><a target=\"_self\" href=\"/status.html\">";
        contents += LocalizedString(KEY_I18N_Tuner_Status, NULL)->cString();
        contents += "</a></li>";
        contents += "</ul>";
        contents += "<center><a target=\"_self\" href=\"/iptd.log\">Ver. ";
        contents += VERSION;
        contents += "</a></center>";
        contents += "</div>";

        html = html->stringByReplacingOccurrencesOfString("%%CONTENTS%%", contents.c_str());
        if (html != NULL)
        {
            result = responseWithHTML(request, html);
        }

        break;
    }

    return result;
}

HTTPResponse *Controller::responseForPrograms(HTTPRequest *request, SOCKADDR_IN *client)
{
    DebugLog2("Controller::responseForPrograms() start.");

    HTTPResponse *result = NULL;
    while ((request != NULL) && (client != NULL))
    {
        String *path = _httpd->rootPath();
        if (path == NULL)
        {
            DebugLog3("_httpd->rootPath() ng.");
            break;
        }

        path = path->stringByAppendingPathComponent("template2.html");
        if (path == NULL)
        {
            DebugLog0("path->stringByAppendingPathComponent() ng.");
            break;
        }

        String *html = String::stringWithContentsOfFile(path->cString(), UTF8StringEncoding);
        if (html == NULL)
        {
            DebugLog0("String::stringWithContentsOfFile() ng.");
            break;
        }

        html = html->stringByReplacingOccurrencesOfString("%%TITLE%%", LocalizedString(KEY_I18N_Programs, NULL));
        if (html == NULL)
        {
            DebugLog0("html->stringByReplacingOccurrencesOfString() ng.");
            break;
        }

        std::string contents;

        String *uri = request->URI();
        if (uri->isMatch("^/programs\\.html$"))
        {
            contents += "<ul id=\"home\" title=\"";
            contents += LocalizedString(KEY_I18N_Programs, NULL)->cString();
            contents += "\" selected=\"true\">";

            // 地デジ
            contents += "<li>";
            contents += "<a target=\"_self\" href=\"/programs_t.html\">";
            contents += LocalizedString(KEY_I18N_Digital_Terrestrial_Television_Broadcasting, NULL)->cString();
            contents += "</a></li>";
            // BS/CS
            contents += "<li>";
            contents += "<a target=\"_self\" href=\"/programs_s.html\">";
            contents += "BS/CS";
            contents += "</a></li>";

            contents += "</ul>";
        }
        else if (uri->isMatch("^/programs_t\\.html$"))
        {
            contents += "<ul id=\"home\" title=\"";
            contents += LocalizedString(KEY_I18N_Digital_Terrestrial_Television_Broadcasting, NULL)->cString();
            contents += "\" selected=\"true\">";

            // 地デジ(局名)
            Array *stations = stationInfos(Tuner::ISDB_T);
            for (uint i = 0; i < stations->count(); ++i)
            {
                Dictionary *station = (Dictionary *)stations->objectAtIndex(i);
                if ((station != NULL) &&
                    (station->stringForKey(KEY_CHANNEL_ID) != NULL) &&
                    (station->stringForKey(KEY_NAME) != NULL))
                {
                    contents += "<li>";
                    contents += "<a target=\"_self\" href=\"/programs_t_";
                    contents += station->stringForKey(KEY_CHANNEL_ID)->cString();
                    contents += ".html\">";
                    contents += station->stringForKey(KEY_NAME)->cString();
                    contents += "</a></li>";
                }
            }

            contents += "</ul>";
        }
        else if (uri->isMatch("^/programs_s\\.html$"))
        {
            contents += "<ul id=\"home\" title=\"";
            contents += "BS/CS";
            contents += "\" selected=\"true\">";

            Array *stations = stationInfos(Tuner::ISDB_S);
            for (uint i = 0; i < stations->count(); ++i)
            {
                Dictionary *station = (Dictionary *)stations->objectAtIndex(i);
                if ((station != NULL) &&
                    (station->stringForKey(KEY_CHANNEL_ID) != NULL) &&
                    (station->stringForKey(KEY_NAME) != NULL))
                {
                    contents += "<li>";
                    contents += "<a target=\"_self\" href=\"/programs_s_";
                    contents += station->stringForKey(KEY_CHANNEL_ID)->cString();
                    contents += ".html\">";
                    contents += station->stringForKey(KEY_NAME)->cString();
                    contents += "</a></li>";
                }
            }

            contents += "</ul>";
        }
        else if (uri->isMatch("^/programs_(t|s)_.+\\.html$"))
        {
            Array *stations = NULL;
            String *ch_id = NULL;
            if (uri->isMatch("programs_t_"))
            {
                stations = stationInfos(Tuner::ISDB_T);
                ch_id = uri->stringByReplacingOccurrencesOfString("/programs_t_", "");
                ch_id = ch_id->stringByReplacingOccurrencesOfString(".html", "");
            }
            else
            {
                stations = stationInfos(Tuner::ISDB_S);
                ch_id = uri->stringByReplacingOccurrencesOfString("/programs_s_", "");
                ch_id = ch_id->stringByReplacingOccurrencesOfString(".html", "");
            }

            for (uint i = 0; i < stations->count(); ++i)
            {
                Dictionary *station = (Dictionary *)stations->objectAtIndex(i);
                if ((station != NULL) &&
                    (station->stringForKey(KEY_CHANNEL_ID) != NULL) &&
                    station->stringForKey(KEY_CHANNEL_ID)->isEqualToString(ch_id))
                {
                    std::string epgs;

                    contents += "<ul id=\"home\" title=\"";
                    contents += station->stringForKey(KEY_NAME)->cString();
                    contents += "\" selected=\"true\">";

                    Array *programs = programsForServices(station->arrayForKey(KEY_SERVICES));
                    for (uint i = 0; i < programs->count(); ++i)
                    {
                        Dictionary *epg = (Dictionary *)programs->objectAtIndex(i);
                        if ((epg != NULL) &&
                            (epg->stringForKey(KEY_EPG_SERVICE_ID) != NULL) &&
                            (epg->stringForKey(KEY_EPG_EVENT_ID) != NULL) &&
                            (epg->stringForKey(KEY_EPG_DATE) != NULL) &&
                            (epg->stringForKey(KEY_EPG_START) != NULL) &&
                            (epg->stringForKey(KEY_EPG_END) != NULL))
                        {
                            contents += "<li>";
                            contents += "<a href=\"#epg_";
                            contents += epg->stringForKey(KEY_EPG_SERVICE_ID)->cString();
                            contents += "_";
                            contents += epg->stringForKey(KEY_EPG_EVENT_ID)->cString();
                            contents += "\">";
                            contents += epg->stringForKey(KEY_EPG_DATE)->substringFromIndex(5)->cString();
                            contents += "&nbsp;";
                            contents += epg->stringForKey(KEY_EPG_START)->substringToIndex(5)->cString();
                            contents += "-";
                            contents += epg->stringForKey(KEY_EPG_END)->substringToIndex(5)->cString();
                            contents += "</a></li>";

                            epgs += epg_regist_form(epg);
                        }
                    }

                    contents += "</ul>";

                    contents += epgs;

                    break;
                }
            }
        }

        html = html->stringByReplacingOccurrencesOfString("%%CONTENTS%%", contents.c_str());
        if (html != NULL)
        {
            result = responseWithHTML(request, html);
        }

        break;
    }

    return result;
}

#if 0
    HTTPResponse *result = NULL;
    while ((request != NULL) && (client != NULL))
    {
        String *path = _httpd->rootPath();
        if (path == NULL)
        {
            DebugLog0("_httpd->rootPath() ng.");
            break;
        }

        path = path->stringByAppendingPathComponent("template2.html");
        if (path == NULL)
        {
            DebugLog0("path->stringByAppendingPathComponent() ng.");
            break;
        }

        String *html = String::stringWithContentsOfFile(path->cString(), UTF8StringEncoding);
        if (html == NULL)
        {
            DebugLog0("String::stringWithContentsOfFile() ng.");
            break;
        }

        html = html->stringByReplacingOccurrencesOfString("%%TITLE%%", LocalizedString(KEY_I18N_Programs, NULL));
        if (html == NULL)
        {
            DebugLog0("html->stringByReplacingOccurrencesOfString() ng.");
            break;
        }

        std::string contents;
        std::string epgs;

        contents += "<ul id=\"home\" title=\"";
        contents += LocalizedString(KEY_I18N_Programs, NULL)->cString();
        contents += "\" selected=\"true\">";
        // 地デジ
        contents += "<li>";
        contents += "<a href=\"#isdb_t\">";
        contents += LocalizedString(KEY_I18N_Digital_Terrestrial_Television_Broadcasting, NULL)->cString();
        contents += "</a></li>";
        // BS/CS
        contents += "<li>";
        contents += "<a href=\"#isdb_s\">";
        contents += "BS/CS";
        contents += "</a></li>";

        contents += "</ul>";

        // 地デジ(局名)
        contents += "<ul id=\"isdb_t\" title=\"";
        contents += LocalizedString(KEY_I18N_Digital_Terrestrial_Television_Broadcasting, NULL)->cString();
        contents += "\">";
        Array *stations = stationInfos(Tuner::ISDB_T);
        for (uint i = 0; i < stations->count(); ++i)
        {
            Dictionary *station = (Dictionary *)stations->objectAtIndex(i);
            if ((station != NULL) &&
                (station->stringForKey(KEY_CHANNEL_ID) != NULL) &&
                (station->stringForKey(KEY_NAME) != NULL))
            {
                contents += "<li>";
                contents += "<a href=\"#isdb_t_";
                contents += station->stringForKey(KEY_CHANNEL_ID)->cString();
                contents += "\">";
                contents += station->stringForKey(KEY_NAME)->cString();
                contents += "</a></li>";
            }
        }
        contents += "</ul>";

        // 地デジ(番組データ)
        for (uint i = 0; i < stations->count(); ++i)
        {
            Dictionary *station = (Dictionary *)stations->objectAtIndex(i);
            if ((station != NULL) &&
                (station->stringForKey(KEY_CHANNEL_ID) != NULL) &&
                (station->stringForKey(KEY_NAME) != NULL))
            {

                contents += "<ul id=\"isdb_t_";
                contents += station->stringForKey(KEY_CHANNEL_ID)->cString();
                contents += "\" title=\"";
                contents += station->stringForKey(KEY_NAME)->cString();
                contents += "\">";

                Array *programs = programsForServices(station->arrayForKey(KEY_SERVICES));
                for (uint i = 0; i < programs->count(); ++i)
                {
                    Dictionary *epg = (Dictionary *)programs->objectAtIndex(i);
                    if ((epg != NULL) &&
                        (epg->stringForKey(KEY_EPG_SERVICE_ID) != NULL) &&
                        (epg->stringForKey(KEY_EPG_EVENT_ID) != NULL) &&
                        (epg->stringForKey(KEY_EPG_DATE) != NULL) &&
                        (epg->stringForKey(KEY_EPG_START) != NULL) &&
                        (epg->stringForKey(KEY_EPG_END) != NULL))
                    {
                        contents += "<li>";
                        contents += "<a href=\"#epg_";
                        contents += epg->stringForKey(KEY_EPG_SERVICE_ID)->cString();
                        contents += "_";
                        contents += epg->stringForKey(KEY_EPG_EVENT_ID)->cString();
                        contents += "\">";
                        contents += epg->stringForKey(KEY_EPG_DATE)->substringFromIndex(5)->cString();
                        contents += "&nbsp;";
                        contents += epg->stringForKey(KEY_EPG_START)->substringToIndex(5)->cString();
                        contents += "-";
                        contents += epg->stringForKey(KEY_EPG_END)->substringToIndex(5)->cString();
                        contents += "</a></li>";

                        epgs += epg_regist_form(epg);
                    }
                }
                contents += "</ul>";
            }
        }

        // BS/CS(局名)
        contents += "<ul id=\"isdb_s\" title=\"";
        contents += LocalizedString(KEY_I18N_Digital_Terrestrial_Television_Broadcasting, NULL)->cString();
        contents += "\">";
        stations = stationInfos(Tuner::ISDB_S);
        for (uint i = 0; i < stations->count(); ++i)
        {
            Dictionary *station = (Dictionary *)stations->objectAtIndex(i);
            if ((station != NULL) &&
                (station->stringForKey(KEY_CHANNEL_ID) != NULL) &&
                (station->stringForKey(KEY_NAME) != NULL))
            {
                contents += "<li>";
                contents += "<a href=\"#isdb_s_";
                contents += station->stringForKey(KEY_CHANNEL_ID)->cString();
                contents += "\">";
                contents += station->stringForKey(KEY_NAME)->cString();
                contents += "</a></li>";
            }
        }
        contents += "</ul>";

        // BS/CS(番組データ)
        for (uint i = 0; i < stations->count(); ++i)
        {
            Dictionary *station = (Dictionary *)stations->objectAtIndex(i);
            if ((station != NULL) &&
                (station->stringForKey(KEY_CHANNEL_ID) != NULL) &&
                (station->stringForKey(KEY_NAME) != NULL))
            {
                contents += "<ul id=\"isdb_s_";
                contents += station->stringForKey(KEY_CHANNEL_ID)->cString();
                contents += "\" title=\"";
                contents += station->stringForKey(KEY_NAME)->cString();
                contents += "\">";

                Array *programs = programsForServices(station->arrayForKey(KEY_SERVICES));
                for (uint i = 0; i < programs->count(); ++i)
                {
                    Dictionary *epg = (Dictionary *)programs->objectAtIndex(i);
                    if ((epg != NULL) &&
                        (epg->stringForKey(KEY_EPG_SERVICE_ID) != NULL) &&
                        (epg->stringForKey(KEY_EPG_EVENT_ID) != NULL) &&
                        (epg->stringForKey(KEY_EPG_DATE) != NULL) &&
                        (epg->stringForKey(KEY_EPG_START) != NULL) &&
                        (epg->stringForKey(KEY_EPG_END) != NULL))
                    {
                        contents += "<li>";
                        contents += "<a href=\"#epg_";
                        contents += epg->stringForKey(KEY_EPG_SERVICE_ID)->cString();
                        contents += "_";
                        contents += epg->stringForKey(KEY_EPG_EVENT_ID)->cString();
                        contents += "\">";
                        contents += epg->stringForKey(KEY_EPG_DATE)->substringFromIndex(5)->cString();
                        contents += "&nbsp;";
                        contents += epg->stringForKey(KEY_EPG_START)->substringToIndex(5)->cString();
                        contents += "-";
                        contents += epg->stringForKey(KEY_EPG_END)->substringToIndex(5)->cString();
                        contents += "</a></li>";

                        epgs += epg_regist_form(epg);
                    }
                }
                contents += "</ul>";
            }
        }

        contents += epgs;
        html = html->stringByReplacingOccurrencesOfString("%%CONTENTS%%", contents.c_str());
        if (html != NULL)
        {
            result = responseWithHTML(request, html);
        }

        break;
    }    
    return result;
}
#endif

HTTPResponse *Controller::responseForReservation(HTTPRequest *request, SOCKADDR_IN *client)
{
    DebugLog2("Controller::responseForReservation() start.");

    // lock
    RaymLock(this);

    HTTPResponse *result = NULL;
    while ((request != NULL) && (client != NULL))
    {
        // create array of reservations sorted by seq_id.
        Array *array = Array::arrayWithCapacity(0);
        for (int i = 0; i < _tunerCount; ++i)
        {
            Array *tmp = _reservations->arrayForKey(_tuners[i]->name());
            if (tmp == NULL)
            {
                continue;
            }
            for (uint idx = 0; idx < tmp->count(); ++idx)
            {
                Dictionary *epg = (Dictionary *)tmp->objectAtIndex(idx);
                if (epg == NULL)
                {
                    continue;
                }

                time_t epg_start;
                time_t tmp_end;
                getTimeWithEPG(epg, &epg_start, &tmp_end);

                bool inserted = false;
                for (uint j = 0; j < array->count(); ++j)
                {
                    Dictionary *epg2 = (Dictionary *)array->objectAtIndex(j);
                    if (epg2 == NULL)
                    {
                        continue;
                    }

                    time_t epg2_start;
                    getTimeWithEPG(epg2, &epg2_start, &tmp_end);

                    if (epg_start <= epg2_start)
                    {
                        array->insertObject(epg, j);
                        inserted = true;
                        break;
                    }
                }

                if (!inserted)
                {
                    array->addObject(epg);
                }
            }
        }

        String *path = _httpd->rootPath();
        if (path == NULL)
        {
            break;
        }

        path = path->stringByAppendingPathComponent("template2.html");
        if (path == NULL)
        {
            break;
        }

        String *html = String::stringWithContentsOfFile(path->cString(), UTF8StringEncoding);
        if (html == NULL)
        {
            break;
        }

        html = html->stringByReplacingOccurrencesOfString("%%TITLE%%", LocalizedString(KEY_I18N_Reservation, NULL));
        if (html == NULL)
        {
            break;
        }

        std::string contents;
        std::string reservations;
        contents += "<ul id=\"home\" title=\"";
        contents += LocalizedString(KEY_I18N_Reservation, NULL)->cString();
        contents += "\" selected=\"true\">";

#if 0
        contents += "<li><a href=\"#tuners\">";
        contents += "<font color=\"blue\">[";
        contents += LocalizedString(KEY_I18N_Tuner, NULL)->cString();
        contents += "]</font>";
        contents += "</a></li>";
#endif

        //
        // "[新規予約]"
        //
        contents += "<li><a href=\"#new_reservation\">";
        contents += "<font color=\"red\">[";
        contents += LocalizedString(KEY_I18N_New_Reservation, NULL)->cString();
        contents += "]</font>";
        contents += "</a></li>";

        //
        // "[キーワード]"
        //
        contents += "<li><a href=\"#keywords\">";
        contents += "<font color=\"blue\">[";
        contents += LocalizedString(KEY_I18N_Keywords, NULL)->cString();
        contents += "]</font>";
        contents += "</a></li>";

        //
        // 登録済み予約情報
        //
        for (uint i = 0; i < array->count(); ++i)
        {
            Dictionary *epg = (Dictionary *)array->objectAtIndex(i);
            char seq_id[7];
            sprintf_s(seq_id, "%06d", epg->integerForKey(KEY_EPG_RESV_ID) % 1000000);
            contents += "<li>";
            contents += "<a href=\"#seqid";
            contents += seq_id;
            contents += "\">";
            String *title = epg->stringForKey(KEY_EPG_TITLE);
            if (title != NULL)
            {
                contents += title->cString();
            }
            else
            {
                time_t start;
                time_t end;
                Controller::getTimeWithEPG(epg, &start, &end);
                TM tm;
                if (localtime_s(&tm, &start) == 0)
                {
                    char date_time[24];
                    sprintf_s(date_time, "%04d/%02d/%02d %02d:%02d:%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
                    contents += date_time;
                }
                else
                {
                    contents += seq_id;
                }
            }
            contents += "</a></li>";

            // reservations
            reservations += "<div id=\"";
            reservations += "seqid";
            reservations += seq_id;
            reservations += "\" title=\"";
            reservations += "xxxxxx";
            reservations += "\" class=\"panel\">";

            //reservations += "<h2>Status</h2>";
            reservations += "<fieldset>";

            //
            reservations += "<div class=\"row\">";
            reservations += "<label>Date</label>";
            reservations += "<span>";
            String *tmp = epg->stringForKey(KEY_EPG_DATE);
            reservations += tmp->cString();
            reservations += "</span>";
            reservations += "</div>";

            reservations += "<div class=\"row\">";
            reservations += "<label>Start</label>";
            reservations += "<span>";
            tmp = epg->stringForKey(KEY_EPG_START);
            reservations += tmp->cString();
            reservations += "</span>";
            reservations += "</div>";

            reservations += "<div class=\"row\">";
            reservations += "<label>End</label>";
            reservations += "<span>";
            tmp = epg->stringForKey(KEY_EPG_END);
            reservations += tmp->cString();
            reservations += "</span>";
            reservations += "</div>";

            reservations += "</fieldset>";

            reservations += "<a class=\"whiteButton\" type=\"submit\" href=\"/cancel.cgi?";
            reservations += "resv_id=";
            reservations += seq_id;
            reservations += "\" onclick='return confirm(\"To cancel. Is it ok?\");'>";
            reservations += LocalizedString(KEY_I18N_Cancel, NULL)->cString();
            reservations += "</a>";

            reservations += "</div>";
        }

        contents += "</ul>";
        contents += reservations;

        //
        // 新規予約：ここから
        //

        // 予約時間の初期値作成
        TM tm1, tm2;
        time_t time1 = time(NULL);
        time1 = (time1 + 15 * 60) / (15 * 60);
        time1 = time1 * 15 * 60;
        time_t time2 = time1 + 15 * 60;
        localtime_s(&tm1, &time1);
        localtime_s(&tm2, &time2);
        
        contents += "<form name=\"reservation\" id=\"new_reservation\" class=\"panel\" method=\"GET\" action=\"regist.cgi\" target=\"_self\">";
        contents += "<fieldset>";
        contents += "<div class=\"row\">";
        contents += "<label>";
        contents += LocalizedString(KEY_I18N_Station_Name, NULL)->cString();
        contents += " : </label>";
        contents += "<span>";
        contents += "<select name=\"service_id\" size=\"1\">";

        // 局情報の収集
        Array *stationInfos = Controller::stationInfos(Tuner::ISDB_T);
        if (stationInfos != NULL)
        {
            stationInfos->addObjectsFromArray(Controller::stationInfos(Tuner::ISDB_S));
        }
        else
        {
            stationInfos = Controller::stationInfos(Tuner::ISDB_S);
        }
        for (uint i = 0; (stationInfos != NULL) && (i < stationInfos->count()); ++i)
        {
            Array *services = ((Dictionary *)stationInfos->objectAtIndex(i))->arrayForKey(KEY_SERVICES);

// とりえあず、現状は最初のサービスのみ表示
//                for (uint j = 0; (services != NULL) &&  (j < services->count()); ++j)
            for (uint j = 0; (services != NULL) &&  (j < 1); ++j)
            {
                Dictionary *service = (Dictionary *)services->objectAtIndex(j);

                // テレビ放送かチェック
                if ((service->stringForKey(KEY_SERVICE_TYPE) != NULL) && service->stringForKey(KEY_SERVICE_TYPE)->isEqualToString("1"))
                {
                    String *name = service->stringForKey(KEY_NAME);
                    String *sid  = service->stringForKey(KEY_SERVICE_ID);
                    if ((name != NULL) && (sid != NULL))
                    {
                        String *tmp = String::stringWithFormat("<option value=\"%s\">%s[%s]</option>", sid->cString(), name->cString(), sid->cString());
                        contents += tmp->cString();
                    }
                }
            }
        }

        contents += "</select>";
        contents += "</span>";
        contents += "</div>";
        contents += "<div class=\"row\">";
        contents += "<label>";
        contents += LocalizedString(KEY_I18N_Date, NULL)->cString();
        contents += " : </label>";
        contents += "<span>";
        contents += "<select name=\"year\" size=\"1\">";
        for (int year = tm1.tm_year + 1900; year < tm1.tm_year + 1900 + 3; ++year)
        {
            char year_buf[64];
            sprintf_s(year_buf, "<option value=\"%d\">%d</option>", year, year);
            contents += year_buf;
        }
        contents += "</select>";
        contents += "<select name=\"month\" size=\"1\">";
        for (int month = 1; month < 13; ++month)
        {
            char month_buf[64];
            if (month == tm1.tm_mon + 1)
            {
                sprintf_s(month_buf, "<option value=\"%d\" selected>%d</option>", month, month);
            }
            else
            {
                sprintf_s(month_buf, "<option value=\"%d\">%d</option>", month, month);
            }
            contents += month_buf;
        }
        contents += "</select>";
        contents += "<select name=\"day\" size=\"1\">";
        for (int day = 1; day < 32; ++day)
        {
            char day_buf[64];
            if (day == tm1.tm_mday)
            {
                sprintf_s(day_buf, "<option value=\"%d\" selected>%d</option>", day, day);
            }
            else
            {
                sprintf_s(day_buf, "<option value=\"%d\">%d</option>", day, day);
            }
            contents += day_buf;
        }
        contents += "</select>";
        contents += "</span>";
        contents += "</div>";
        contents += "<div class=\"row\">";
        contents += "<label>";
        contents += LocalizedString(KEY_I18N_Start_Time, NULL)->cString();
        contents += " : </label>";
        contents += "<span>";
        contents += "<select name=\"start_hour\" size=\"1\">";
        for (int sh = 0; sh < 24; ++sh)
        {
            char sh_buf[64];
            if (sh == tm1.tm_hour)
            {
                sprintf_s(sh_buf, "<option value=\"%02d\" selected>%02d</option>", sh, sh);
            }
            else
            {
                sprintf_s(sh_buf, "<option value=\"%02d\">%02d</option>", sh, sh);
            }
            contents += sh_buf;
        }
        contents += "</select>";
        contents += "<select name=\"start_min\" size=\"1\">";
        for (int sm = 0; sm < 60; ++sm)
        {
            char sm_buf[64];
            if (sm == tm1.tm_min)
            {
                sprintf_s(sm_buf, "<option value=\"%02d\" selected>%02d</option>", sm, sm);
            }
            else
            {
                sprintf_s(sm_buf, "<option value=\"%02d\">%02d</option>", sm, sm);
            }
            contents += sm_buf;
        }
        contents += "</select>";
        contents += "</span>";
        contents += "</div>";
        contents += "<div class=\"row\">";
        contents += "<label>";
        contents += LocalizedString(KEY_I18N_End_Time, NULL)->cString();
        contents += " : </label>";
        contents += "<span>";
        contents += "<select name=\"end_hour\" size=\"1\">";
        for (int eh = 0; eh < 24; ++eh)
        {
            char eh_buf[64];
            if (eh == tm2.tm_hour)
            {
                sprintf_s(eh_buf, "<option value=\"%02d\" selected>%02d</option>", eh, eh);
            }
            else
            {
                sprintf_s(eh_buf, "<option value=\"%02d\">%02d</option>", eh, eh);
            }
            contents += eh_buf;
        }
        contents += "</select>";
        contents += "<select name=\"end_min\" size=\"1\">";
        for (int em = 0; em < 60; ++em)
        {
            char em_buf[64];
            if (em == tm2.tm_min)
            {
                sprintf_s(em_buf, "<option value=\"%02d\" selected>%02d</option>", em, em);
            }
            else
            {
                sprintf_s(em_buf, "<option value=\"%02d\">%02d</option>", em, em);
            }
            contents += em_buf;
        }
        contents += "</select>";
        contents += "</span>";
        contents += "</div>";
        contents += "<div class=\"row\">";
        contents += "<label>";
        contents += LocalizedString(KEY_I18N_Repeat, NULL)->cString();
        contents += " : </label>";
        contents += "<span>";
        contents += "<select name=\"repeat\" size=\"1\">";
        contents += "<option value=\"off\" selected>";
        contents += LocalizedString(KEY_I18N_Repeat_off, NULL)->cString();
        contents += "</option>";
        contents += "<option value=\"everyday\">";
        contents += LocalizedString(KEY_I18N_Repeat_everyday, NULL)->cString();
        contents += "</option>";
        contents += "<option value=\"weekly\">";
        contents += LocalizedString(KEY_I18N_Repeat_weekly, NULL)->cString();
        contents += "</option>";
        contents += "<option value=\"weekday\">";
        contents += LocalizedString(KEY_I18N_Repeat_weekday, NULL)->cString();
        contents += "</option>";
        contents += "</select>";
        contents += "</span>";
        contents += "</div>";
        contents += "</fieldset>";

        contents += "<input type=\"submit\" class=\"whiteButton\" value=\"";
        contents += LocalizedString(KEY_I18N_Registration, NULL)->cString();
        contents += "\">";
        contents += "</form>";

        //
        // 新規予約：ここまで
        //


        //
        // チューナ：ここから
        //
        contents += "<ul id=\"tuners\" title=\"";
        contents += LocalizedString(KEY_I18N_Tuner, NULL)->cString();
        contents += "\">";

        std::string controls;
        Dictionary *tunerInfos = _props->dictionaryForKey(KEY_TUNERS);
        if (tunerInfos != NULL)
        {
            for (int i = 0; i < _tunerCount; ++i)
            {
                Dictionary *tunerInfo = tunerInfos->dictionaryForKey(_tuners[i]->name());
                if (tunerInfo != NULL)
                {
                    if (tunerInfo->boolForKey(KEY_INITIALIZED) && tunerInfo->boolForKey(KEY_ENABLED))
                    {
                        char key[4];
                        sprintf_s(key, "%03d", i);

                        // tuner list
                        contents += "<li>";
                        contents += "<a href=\"#tuner";
                        contents += key;
                        contents += "\">";
                        contents += key;
                        contents += ": ";
                        Dictionary *dict = tunerInfo->dictionaryForKey(KEY_CHANNELS);
                        if (dict != NULL)
                        {
                            char chkey[4];
                            sprintf_s(chkey, "%03d", tunerInfo->integerForKey(KEY_CHANNEL));
                            dict = dict->dictionaryForKey(chkey);
                            String *name = dict->stringForKey(KEY_NAME);
                            if (name != NULL)
                            {
                                contents += name->cString();
                            }
                            else
                            {
                                dict = NULL;
                            }
                        }
                        if (dict == NULL)
                        {
                            contents += _tuners[i]->name();
                        }
                        contents += "</a></li>";

                        // controls
                        controls += "<form name=\"control\" id=\"tuner";
                        controls += key;
                        controls += "\" class=\"panel\" method=\"GET\" action=\"/";
                        controls += key;
                        controls += "/recording=on\" target=\"_self\">";

                        // 白い枠：ここから
                        controls += "<fieldset>";

                        // 局指定：ここから
                        controls += "<div class=\"row\">";
                        controls += "<label>";
                        controls += LocalizedString(KEY_I18N_Station_Name, NULL)->cString();
                        controls += " : </label>";
                        controls += "<span>";
                        controls += "<select name=\"channel\" size=\"1\">";
                        Dictionary *channels = tunerInfo->dictionaryForKey(KEY_CHANNELS);
                        if (channels != NULL)
                        {
                            for (int ch = 0; ch <= Tuner::MAX_CHANNELS_ISDB_T; ++ch)
                            {
                                char chkey[4];
                                sprintf_s(chkey, "%03d", ch);
                                dict = channels->dictionaryForKey(chkey);
                                if (dict == NULL)
                                {
                                    break;
                                }
                                Array *services = (Array *)dict->objectForKey(KEY_SERVICES);
                                if (services != NULL)
                                {
                                    for (uint s = 0; s < services->count(); ++s)
                                    {
                                        Dictionary *service = (Dictionary *)services->objectAtIndex(s);
                                        if (service != NULL)
                                        {
                                            char sid[8];
                                            sprintf_s(sid, "%d", service->integerForKey(KEY_SERVICE_ID));
                                            controls += "<option value=\"";
                                            controls += chkey;
                                            controls += "\"";
                                            // selected
                                            controls += ">";
                                            String *name = service->stringForKey(KEY_NAME);
                                            if (name != NULL)
                                            {
                                                controls += name->cString();
                                            }
                                            controls += "[";
                                            controls += sid;
                                            controls += "]</option>";
                                        }
                                    }
                                }
                            }
                        }
                        controls += "</select>";
                        controls += "</span>";
                        controls += "</div>";
                        // 局指定：ここまで

                        // 時間指定：ここから
                        controls += "<div class=\"row\">";
                        controls += "<label>";
                        controls += LocalizedString(KEY_I18N_Time, NULL)->cString();
                        controls += " : </label>";
                        controls += "<span>";
                        controls += "<select name=\"hour\" size=\"1\">";
                        for (int sh = 0; sh < 24; ++sh)
                        {
                            char sh_buf[64];
                            if (sh == 1)
                            {
                                sprintf_s(sh_buf, "<option value=\"%02d\" selected>%02d</option>", sh, sh);
                            }
                            else
                            {
                                sprintf_s(sh_buf, "<option value=\"%02d\">%02d</option>", sh, sh);
                            }
                            controls += sh_buf;
                        }
                        controls += "</select>";
                        controls += "<select name=\"min\" size=\"1\">";
                        for (int sm = 0; sm < 60; ++sm)
                        {
                            char sm_buf[64];
                            if (sm == 0)
                            {
                                sprintf_s(sm_buf, "<option value=\"%02d\" selected>%02d</option>", sm, sm);
                            }
                            else
                            {
                                sprintf_s(sm_buf, "<option value=\"%02d\">%02d</option>", sm, sm);
                            }
                            controls += sm_buf;
                        }
                        controls += "</select>";
                        controls += "</span>";
                        controls += "</div>";
                        // 時間指定：ここまで

                        controls += "</fieldset>";
                        // 白い枠：ここまで

                        controls += "<input type=\"submit\" class=\"whiteButton\" value=\"";
                        controls += LocalizedString(KEY_I18N_Registration, NULL)->cString();
                        controls += "\">";
                        controls += "</form>";
                    }
                }
            }
        }
        contents += "</ul>";
        contents += controls;

        //
        // チューナ：ここまで
        //

        //
        // キーワード：ここから
        //
        contents += "<ul id=\"keywords\" title=\"";
        contents += LocalizedString(KEY_I18N_Keywords, NULL)->cString();
        contents += "\">";

        contents += "<li><a href=\"#dialogForm\">";
        contents += "<font color=\"red\">[";
//        contents += LocalizedString(KEY_I18N_Keywords, NULL)->cString();
        contents += LocalizedString(KEY_I18N_Add, NULL)->cString();
        contents += "]</font>";
        contents += "</a></li>";

        // 登録済みキーワード
        Dictionary *keywords_info = _reservations->dictionaryForKey(KEY_EPG_KEYWORDS);
        if (keywords_info != NULL)
        {
            controls = "";
            Array *keys = keywords_info->allKeys();
            if (keys != NULL)
            {
                for (uint i = 0; i < keys->count(); ++i)
                {
                    Dictionary *kwd_inf = keywords_info->dictionaryForKey((String *)keys->objectAtIndex(i));
                    if (kwd_inf == NULL)
                    {
                        continue;
                    }

                    char tmp[32];
                    sprintf_s(tmp, "%d", i);
                    contents += "<li><a href=\"#keywords_";
                    contents += tmp;
                    contents += "\">";
                    contents += ((String *)keys->objectAtIndex(i))->cString();
                    contents += "</a></li>";

                    controls += "<form name=\"filter\" id=\"keywords_";
                    controls += tmp;
                    controls += "\" class=\"panel\" method=\"GET\" action=\"mod_keywords.cgi\" target=\"_self\">";
                    controls += "<input type=\"hidden\" name=\"keywords\" value=\"";
                    controls += ((String *)keys->objectAtIndex(i))->cString();
                    controls += "\">";
                    controls += "<fieldset>";

                    controls += "<div class=\"row\">";
                    controls += "<label>";
                    controls += LocalizedString(KEY_I18N_Keywords, NULL)->cString();
                    controls += " : </label>";
                    controls += "<span>";
                    controls += ((String *)keys->objectAtIndex(i))->cString();
                    controls += "</span>";
                    controls += "</div>";

                    controls += "<div class=\"row\">";
                    controls += "<label>";
                    controls += LocalizedString(KEY_I18N_Station_Name, NULL)->cString();
                    controls += " : </label>";
                    controls += "<span>";
                    controls += "<select name=\"service_id\" size=\"1\">";
                    controls += "<option value=\"-\">----</option>";

                    String *service_id = kwd_inf->stringForKey(KEY_EPG_SERVICE_ID);

                    // 局情報の収集
                    Array *stationInfos = Controller::stationInfos(Tuner::ISDB_T);
                    if (stationInfos != NULL)
                    {
                        stationInfos->addObjectsFromArray(Controller::stationInfos(Tuner::ISDB_S));
                    }
                    else
                    {
                        stationInfos = Controller::stationInfos(Tuner::ISDB_S);
                    }
                    for (uint i = 0; (stationInfos != NULL) && (i < stationInfos->count()); ++i)
                    {
                        Array *services = ((Dictionary *)stationInfos->objectAtIndex(i))->arrayForKey(KEY_SERVICES);

                        for (uint j = 0; (services != NULL) &&  (j < 1); ++j)
                        {
                            Dictionary *service = (Dictionary *)services->objectAtIndex(j);

                            // テレビ放送かチェック
                            if ((service->stringForKey(KEY_SERVICE_TYPE) != NULL) && service->stringForKey(KEY_SERVICE_TYPE)->isEqualToString("1"))
                            {
                                String *name = service->stringForKey(KEY_NAME);
                                String *sid  = service->stringForKey(KEY_SERVICE_ID);
                                if ((name != NULL) && (sid != NULL))
                                {
                                    if ((service_id != NULL) && (service_id->isEqualToString(sid)))
                                    {
                                        String *tmp = String::stringWithFormat("<option value=\"%s\" selected>%s[%s]</option>", sid->cString(), name->cString(), sid->cString());
                                        controls += tmp->cString();
                                    }
                                    else
                                    {
                                        String *tmp = String::stringWithFormat("<option value=\"%s\">%s[%s]</option>", sid->cString(), name->cString(), sid->cString());
                                        controls += tmp->cString();
                                    }
                                }
                            }
                        }
                    }

                    controls += "</select>";
                    controls += "</span>";
                    controls += "</div>";

                    controls += "<div class=\"row\">";
                    controls += "<label>";
                    controls += LocalizedString(KEY_I18N_Start_Time, NULL)->cString();
                    controls += " : </label>";
                    controls += "<span>";
                    controls += "<select name=\"start_hour\" size=\"1\">";
                    controls += "<option value=\"-\">--</option>";
                    String *sh_str = kwd_inf->stringForKey(KEY_EPG_START);
                    for (int sh = 0; sh < 24; ++sh)
                    {
                        char sh_buf[64];
                        if ((sh_str != NULL) && (sh_str->length() == 5) && (sh_str->substringToIndex(2)->intValue() == sh))
                        {
                            sprintf_s(sh_buf, "<option value=\"%02d\" selected>%02d</option>", sh, sh);
                        }
                        else
                        {
                            sprintf_s(sh_buf, "<option value=\"%02d\">%02d</option>", sh, sh);
                        }
                        controls += sh_buf;
                    }
                    controls += "</select>";
                    controls += "<select name=\"start_min\" size=\"1\">";
                    controls += "<option value=\"-\">--</option>";
                    for (int sm = 0; sm < 60; ++sm)
                    {
                        char sm_buf[64];
                        if ((sh_str != NULL) && (sh_str->length() == 5) && (sh_str->substringFromIndex(3)->intValue() == sm))
                        {
                            sprintf_s(sm_buf, "<option value=\"%02d\" selected>%02d</option>", sm, sm);
                        }
                        else
                        {
                            sprintf_s(sm_buf, "<option value=\"%02d\">%02d</option>", sm, sm);
                        }
                        controls += sm_buf;
                    }
                    controls += "</select>";
                    controls += "</span>";
                    controls += "</div>";

                    controls += "</fieldset>";

                    controls += "<input type=\"submit\" class=\"whiteButton\" name=\"req_mod\" value=\"";
                    controls += LocalizedString(KEY_I18N_Registration, NULL)->cString();
                    controls += "\">";

                    controls += "<input type=\"submit\" class=\"redButton\" name=\"req_del\" value=\"";
                    controls += LocalizedString(KEY_I18N_Delete, NULL)->cString();
//                    controls += "\">";
                    controls += "\" onclick='return confirm(\"To delete. Is it ok?\");'>";

                    controls += "</form>";
                }
            }
        }

        contents += "</ul>";
        contents += controls;

        controls = "<form id=\"dialogForm\" title=\"";
        controls += LocalizedString(KEY_I18N_Keywords, NULL)->cString();
        controls += LocalizedString(KEY_I18N_Add, NULL)->cString();
        controls += "\" class=\"dialog\" target=\"_self\" action=\"add_keywords.cgi\" method=\"GET\">";
        controls += "<fieldset>";
        controls += "<h1>";
        controls += LocalizedString(KEY_I18N_Keywords, NULL)->cString();
        controls += LocalizedString(KEY_I18N_Add, NULL)->cString();
        controls += "</h1>";
        controls += "<a class=\"button leftButton\" type=\"cancel\">Cancel</a>";
        controls += "<a class=\"button blueButton\" type=\"submit\">Submit</a>";
        controls += "<label>Parm1:</label>";
        controls += "<input type=\"text\" name=\"keywords\" value=\"";
//        controls += LocalizedString(KEY_I18N_Keywords, NULL)->cString();
        controls += "\"/>";
        controls += "</fieldset>";
        controls += "<div class=\"spinner\"></div>";
        controls += "</form>";

        contents += controls;
        //
        // キーワード：ここまで
        //

        html = html->stringByReplacingOccurrencesOfString("%%CONTENTS%%", contents.c_str());
        if (html != NULL)
        {
            result = responseWithHTML(request, html);
        }

        break;
    }

    // unlock
    RaymUnlock(this);

    return result;
}

HTTPResponse *Controller::responseForStatus(HTTPRequest *request, SOCKADDR_IN *client)
{
    HTTPResponse *result = NULL;
    if ((request != NULL) && (client != NULL))
    {
        String *path = _httpd->rootPath()->stringByAppendingPathComponent("template2.html");
        String *html = String::stringWithContentsOfFile(path->cString(), UTF8StringEncoding);
        if (html != NULL)
        {
            html = html->stringByReplacingOccurrencesOfString("%%TITLE%%", LocalizedString(KEY_I18N_Tuner_Status, NULL));
            std::string contents;
            std::string status;
            contents += "<ul id=\"home\" title=\"";
            contents += LocalizedString(KEY_I18N_Tuner_Status, NULL)->cString();
            contents += "\" selected=\"true\">";
            DebugLog2("_tunerCount = %d", _tunerCount);
            for (int i = 0; i < _tunerCount; ++i)
            {
                char key[4];
                sprintf_s(key, sizeof(key), "%03d", i);

                if (isTunerInitialized(i))
                {
                    contents += "<li>";
                    contents += "<a href=\"#tuner";
                    contents += key;
                    contents += "\">";
                    contents += key;
                    contents += ": ";
                    contents += _tuners[i]->name();
                    contents += "</a></li>";

                    //
                    status += "<div id=\"";
                    status += "tuner";
                    status += key;
                    status += "\" title=\"";
                    status += _tuners[i]->name();
                    status += "\" class=\"panel\">";

                    status += "<h2>Status</h2>";
                    status += "<fieldset>";

                    // Tuner Type
                    status += "<div class=\"row\">";
                    status += "<label>Type</label>";
                    status += "<span>";
                    switch (_tuners[i]->type())
                    {
                    case Tuner::ISDB_S:
                        status += "ISDB-S";
                        break;
                    case Tuner::ISDB_T:
                        status += "ISDB-T";
                        break;
                    case Tuner::TYPE_NA:
                    default:
                        status += "N/A";
                        break;
                    }
                    status += "</span>";
                    status += "</div>";

                    // LnbPower
                    status += "<div class=\"row\">";
                    status += "<label>LnbPower</label>";
                    status += "<span>";
                    switch (_tuners[i]->lnbPower())
                    {
                    case Tuner::LNB_POWER_11V:
                        status += "11V";
                        break;
                    case Tuner::LNB_POWER_15V:
                        status += "15V";
                        break;
                    case Tuner::LNB_POWER_OFF:
                    default:
                        status += "OFF";
                        break;
                    }
                    status += "</span>";
                    status += "</div>";

                    // Channel
                    char tmpstr[32];
                    status += "<div class=\"row\">";
                    status += "<label>Channel</label>";
                    status += "<span>";
                    sprintf_s(tmpstr, sizeof(tmpstr), "%03d", _tuners[i]->channel());
                    status += tmpstr;
                    status += "</span>";
                    status += "</div>";

                    // C/N[dB] AGC xxx/255
                    uint32_t cn100 = 0;
                    uint32_t agc = 0;
                    uint32_t maxAgc = 0;
                    _tuners[i]->getCnAgc(&cn100, &agc, &maxAgc);

                    sprintf_s(tmpstr, sizeof(tmpstr), "%d.%02d[dB]", cn100 / 100, cn100 % 100);

                    status += "<div class=\"row\">";
                    status += "<label>C/N</label>";
                    status += "<span>";
                    status += tmpstr;
                    status += "</span>";
                    status += "</div>";

                    sprintf_s(tmpstr, sizeof(tmpstr), "%03d/%03d", agc, maxAgc);

                    status += "<div class=\"row\">";
                    status += "<label>AGC</label>";
                    status += "<span>";
                    status += tmpstr;
                    status += "</span>";
                    status += "</div>";

                    status += "</fieldset>";
                    status += "</div>";

                }
                else
                {
                    contents += "<li>";
                    contents += _tuners[i]->name();
                    contents += "[uninitialized]";
                    contents += "</li>";
                }
            }
            contents += "</ul>";
            contents += status;
            html = html->stringByReplacingOccurrencesOfString("%%CONTENTS%%", contents.c_str());
            result = responseWithHTML(request, html);
        }
        else
        {
            DebugLog2("responseForStatus() html is null\n");
        }
    }
    return result;
}

HTTPResponse *Controller::responseForRegistCGI(HTTPRequest *request, SOCKADDR_IN *client)
{
    DebugLog2("Controller::responseForRegistCGI()");

    HTTPResponse *result = NULL;

    // CGIリクエストとして解析
    Dictionary *cgi = request->parseAsCGI();
    if (cgi != NULL)
    {
        // CGIパスが一致しているか
        if ((cgi->stringForKey(HTTPRequest::KEY_CGI) != NULL) && (cgi->stringForKey(HTTPRequest::KEY_CGI)->isEqualToString("/regist.cgi")))
        {
            // パラメータがあるか
            Array *params = cgi->arrayForKey(HTTPRequest::KEY_PARAMS);
            if (params != NULL)
            {
                // パラメータ数が２か
                if (params->count() == 2)
                {
                    // パラメータのチェック
                    String *service_id = NULL;
                    String *event_id = NULL;

                    for (uint i = 0; i < params->count(); ++i)
                    {
                        Dictionary *param = (Dictionary *)params->objectAtIndex(i);
                        String *value = param->stringForKey("service_id");
                        if ((value != NULL) && value->isMatch("^\\d+$"))
                        {
                            service_id = value;
                        }
                        value = param->stringForKey("event_id");
                        if ((value != NULL) && value->isMatch("^\\d+$"))
                        {
                            event_id = value;
                        }
                    }

                    // 有効なパラメータか
                    if ((service_id != NULL) && (event_id != NULL))
                    {
                        DebugLog2("valid request");

                        result = responseByResult(request, reserve(service_id->intValue(), event_id->intValue()));
                    }
                }

                // パラメータ数が９か
                else if (params->count() == 9)
                {
                    // パラメータのチェック
                    String *service_id = NULL;
                    String *year       = NULL;
                    String *month      = NULL;
                    String *day        = NULL;
                    String *start_hour = NULL;
                    String *start_min  = NULL;
                    String *end_hour   = NULL;
                    String *end_min    = NULL;
                    String *repeat     = NULL;

                    struct {
                        const char *name;
                        String **variable;
                        const char *regex;
                    }
                    cgi[] =
                    {
                        {"service_id", &service_id, "^\\d+$"},
                        {"year",       &year,       "^\\d{4}$"},
                        {"month",      &month,      "^([1-9]|1[0-2])$"},
                        {"day",        &day,        "^([1-9]|[12][0-9]|3[01])$"},
                        {"start_hour", &start_hour, "^\\d{2}$"},
                        {"start_min",  &start_min,  "^\\d{2}$"},
                        {"end_hour",   &end_hour,   "^\\d{2}$"},
                        {"end_min",    &end_min,    "^\\d{2}$"},
                        {"repeat",     &repeat,     "^(off|everyday|weekly|weekday)$"},
                        {NULL, NULL, NULL}
                    };

                    for (uint i = 0; cgi[i].name != NULL; ++i)
                    {
                        for (uint j = 0; j < params->count(); ++j)
                        {
                            Dictionary *param = (Dictionary *)params->objectAtIndex(j);
                            String *value = param->stringForKey(cgi[i].name);
                            if ((value != NULL) && value->isMatch(cgi[i].regex))
                            {
                                *(cgi[i].variable) = value;
                                break;
                            }
                        }
                    }

                    // 有効なパラメータか
                    if ((service_id != NULL) && (year != NULL) && (month != NULL) && (day != NULL) &&
                        (start_hour != NULL) && (start_min != NULL) && (end_hour != NULL) && (end_min != NULL) && (repeat != NULL))
                    {
                        //
                        DebugLog1("valid param");

                        Dictionary *epg = Dictionary::dictionaryWithCapacity(0);

                        // 日付
                        epg->setString(String::stringWithFormat("%s/%02d/%02d", year->cString(), month->intValue(), day->intValue()), KEY_EPG_DATE);

                        // 開始時刻
                        epg->setString(String::stringWithFormat("%s:%s:00", start_hour->cString(), start_min->cString()), KEY_EPG_START);

                        // 終了時刻
                        epg->setString(String::stringWithFormat("%s:%s:00", end_hour->cString(), end_min->cString()), KEY_EPG_END);

                        // 繰り返し
                        epg->setString(repeat, KEY_EPG_REPEAT);

                        // Service ID
                        epg->setString(service_id, KEY_EPG_SERVICE_ID);

                        // Status
                        epg->setString("ready", KEY_EPG_STATUS);

                        result = responseByResult(request, reserve(epg));
                    }
                }
            }
        }
    }

    return result;
}

HTTPResponse *Controller::responseForCancelCGI(HTTPRequest *request, SOCKADDR_IN *client)
{
    DebugLog2("Controller::responseForCancelCGI()");

    HTTPResponse *result = NULL;

    // CGIリクエストとして解析
    Dictionary *cgi = request->parseAsCGI();
    if (cgi != NULL)
    {
        // CGIパスが一致しているか
        if ((cgi->stringForKey(HTTPRequest::KEY_CGI) != NULL) && (cgi->stringForKey(HTTPRequest::KEY_CGI)->isEqualToString("/cancel.cgi")))
        {
            // パラメータがあるか
            Array *params = cgi->arrayForKey(HTTPRequest::KEY_PARAMS);
            if (params != NULL)
            {
                // パラメータ数が１か
                if (params->count() == 1)
                {
                    Dictionary *param = (Dictionary *)params->objectAtIndex(0);
                    String *value = param->stringForKey("resv_id");
                    if ((value != NULL) && value->isMatch("^\\d{6}$"))
                    {
                        result = responseByResult(request, cancel(-1, value->intValue()));
                    }
                }
            }
        }
    }
    return result;
}

HTTPResponse *Controller::responseForAddKeywordsCGI(HTTPRequest *request, SOCKADDR_IN *client)
{
    DebugLog2("Controller::responseForAddKeywordsCGI()");

    HTTPResponse *result = NULL;

    // CGIリクエストとして解析
    Dictionary *cgi = request->parseAsCGI();
    if (cgi != NULL)
    {
        // CGIパスが一致しているか
        if ((cgi->stringForKey(HTTPRequest::KEY_CGI) != NULL) && (cgi->stringForKey(HTTPRequest::KEY_CGI)->isEqualToString("/add_keywords.cgi")))
        {
            // パラメータがあるか
            Array *params = cgi->arrayForKey(HTTPRequest::KEY_PARAMS);
            if (params != NULL)
            {
                // パラメータ数が１か
                if (params->count() == 1)
                {
                    Dictionary *param = (Dictionary *)params->objectAtIndex(0);
                    String *value = param->stringForKey("keywords");
                    if (value != NULL)
                    {
                        value = value->stringByReplacingOccurrencesOfString("+", " ");
                        if (value != NULL)
                        {
                            value = value->stringByRemovingPercentEncoding();
                        }
                    }
                    if (value != NULL)
                    {
                        // lock
                        RaymLock(this);

                        Dictionary *keywords_info = _reservations->dictionaryForKey(KEY_EPG_KEYWORDS);
                        if (keywords_info == NULL)
                        {
                            keywords_info = Dictionary::dictionaryWithCapacity(0);
                            _reservations->setObject(keywords_info, KEY_EPG_KEYWORDS);
                        }

                        if (keywords_info->dictionaryForKey(value) == NULL)
                        {
                            // 新規キーワードの場合
                            keywords_info->setObject(Dictionary::dictionaryWithCapacity(0), value);

                            // キーワード予約更新
                            updateKeywordsReservation();

                            // スケジュール更新
                            updateSchedule();

                            // 保存
                            _reservations->writeToFile(_reservations_path, true);
                        }

                        RaymUnlock(this);

                        result = responseForReloadURI(request, client, "/reservation.html#_keywords");
                    }
                }
            }
        }
    }
    return result;
}

HTTPResponse *Controller::responseForModKeywordsCGI(HTTPRequest *request, SOCKADDR_IN *client)
{
    DebugLog2("Controller::responseForModKeywordsCGI()");

    HTTPResponse *result = NULL;

    // CGIリクエストとして解析
    Dictionary *cgi = request->parseAsCGI();
    if (cgi != NULL)
    {
        DebugLog3("cgi != NULL");

        // CGIパスが一致しているか
        if ((cgi->stringForKey(HTTPRequest::KEY_CGI) != NULL) && (cgi->stringForKey(HTTPRequest::KEY_CGI)->isEqualToString("/mod_keywords.cgi")))
        {
            DebugLog3("CGI path OK.");

            // パラメータがあるか
            Array *params = cgi->arrayForKey(HTTPRequest::KEY_PARAMS);
            if (params != NULL)
            {
                DebugLog3("params != NULL");

                // パラメータのチェック
                String *keywords   = NULL;
                String *service_id = NULL;
                String *start_hour = NULL;
                String *start_min  = NULL;
                String *req_mod    = NULL;
                String *req_del    = NULL;

                struct {
                    const char *name;
                    String **variable;
                    const char *regex;
                }
                cgi[] =
                {
                    {"keywords",   &keywords,   ".*"},
                    {"service_id", &service_id, "^(\\d+|-)$"},
                    {"start_hour", &start_hour, "^(\\d{2}|-)$"},
                    {"start_min",  &start_min,  "^(\\d{2}|-)$"},
                    {"req_mod",    &req_mod,    ".*"},
                    {"req_del",    &req_del,    ".*"},
                    {NULL, NULL, NULL}
                };

                for (uint i = 0; cgi[i].name != NULL; ++i)
                {
                    for (uint j = 0; j < params->count(); ++j)
                    {
                        Dictionary *param = (Dictionary *)params->objectAtIndex(j);
                        String *value = param->stringForKey(cgi[i].name);
                        if ((value != NULL) && value->isMatch(cgi[i].regex))
                        {
                            *(cgi[i].variable) = value;
                            break;
                        }
                    }
                }

                if (keywords != NULL)
                {
                    keywords = keywords->stringByReplacingOccurrencesOfString("+", " ");
                    if (keywords != NULL)
                    {
                        keywords = keywords->stringByRemovingPercentEncoding();
                    }
                }

                if ((keywords != NULL) && (service_id != NULL) && (start_hour != NULL) && (start_min != NULL) &&
                    ((req_mod != NULL) && (req_del == NULL)) || ((req_mod == NULL) && (req_del != NULL)))
                {

                    // lock
                    RaymLock(this);

                    Dictionary *keywords_info = _reservations->dictionaryForKey(KEY_EPG_KEYWORDS);
                    if (keywords_info != NULL)
                    {
                        if (req_mod != NULL)
                        {
                            DebugLog0("keywords: %s", keywords->cString());
                            DebugLog0("service_id: %s", service_id->cString());
                            DebugLog0("start_hour: %s", start_hour->cString());
                            DebugLog0("start_min: %s", start_min->cString());
                            Dictionary *kwd_inf = keywords_info->dictionaryForKey(keywords);
                            if (kwd_inf != NULL)
                            {
                                if (!service_id->isEqualToString("-"))
                                {
                                    kwd_inf->setString(service_id, KEY_EPG_SERVICE_ID);
                                }
                                else
                                {
                                    kwd_inf->removeObjectForKey(KEY_EPG_SERVICE_ID);
                                }
                                if (!start_hour->isEqualToString("-") && !start_min->isEqualToString("-"))
                                {
                                    kwd_inf->setString(String::stringWithFormat("%s:%s", start_hour->cString(), start_min->cString()), KEY_EPG_START);
                                }
                                else
                                {
                                    kwd_inf->removeObjectForKey(KEY_EPG_START);
                                }
                            }
                        }
                        else if (req_del != NULL)
                        {
                            DebugLog0("keywords: %s", keywords->cString());
                            keywords_info->removeObjectForKey(keywords);
                        }

                        // キーワード予約更新
                        updateKeywordsReservation();

                        // スケジュール更新
                        updateSchedule();

                        // 保存
                        _reservations->writeToFile(_reservations_path, true);
                    }

                    // unlock
                    RaymUnlock(this);

                    result = responseForReloadURI(request, client, "/reservation.html#_keywords");
                }
            }
        }
    }
    return result;
}

HTTPResponse *Controller::responseForReloadURI(NET::HTTPRequest *request, SOCKADDR_IN *client, const char *uri, int sec)
{
    HTTPResponse *result = NULL;

    if (uri != NULL)
    {
        std::string contents;
        contents = "<html>";
        contents += "<head>";
#if 0
        contents += "<meta http-equiv=\"refresh\" content=\"0;URL=";
#else
        contents += "<meta http-equiv=\"refresh\" content=\"";
        char tmp[16];
        sprintf_s(tmp, "%d", sec);
        contents += tmp;
        contents += ";URL=";
#endif
        contents += uri;
        contents += "\">";
        contents += "</head>";
        contents += "</html>";
        String *html = String::stringWithUTF8String(contents.c_str());
        if (html != NULL)
        {
            result = responseWithHTML(request, html);
        }
    }

    return result;
}

HTTPResponse *Controller::responseForPlaylist(HTTPRequest *request, SOCKADDR_IN *client)
{
    HTTPResponse *result = NULL;

    char hostname[NI_MAXHOST];
    if (getnameinfo((SOCKADDR *)client, sizeof(SOCKADDR_IN), hostname, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
    {
        if (_props->stringForKey(KEY_HTTP_HOST)->isEqualToString(hostname) || strcmp("127.0.0.1", hostname) == 0)
        {
            if (_iptv_m3u8_local != NULL)
            {
                result = responseWithUTF8Text(request, _iptv_m3u8_local);
            }
        }
        else
        {
            if (_iptv_m3u8_remote != NULL)
            {
                result = responseWithUTF8Text(request, _iptv_m3u8_remote);
            }
        }
    }

    return result;
}

HTTPResponse *Controller::responseForXmltv(HTTPRequest *request, SOCKADDR_IN *client)
{
    HTTPResponse *result = NULL;

    std::string str_xmltv_xml;
    str_xmltv_xml =  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n";
    str_xmltv_xml += "<!DOCTYPE tv SYSTEM \"xmltv.dtd\">\r\n";
    str_xmltv_xml += "<tv generator-info-name=\"iPTd_R2\" generator-info-url=\"http://localhost/\">\r\n";
    str_xmltv_xml += _xmltv_channels;

    Dictionary *temp_data = _epgs->dictionaryForKey(KEY_TEMP_DATA);
    if (temp_data != NULL)
    {
        Array *keys = temp_data->allKeys();
        for (uint i = 0; i < keys->count(); ++i)
        {
            Dictionary *temp_service = temp_data->dictionaryForKey((String *)keys->objectAtIndex(i));
            if (temp_service != NULL)
            {
                String *xmltv_programs = temp_service->stringForKey(KEY_PROGRAMS);
                if (xmltv_programs != NULL)
                {
                    str_xmltv_xml += xmltv_programs->cString();
                }
            }
        }
    }

    str_xmltv_xml += "</tv>\r\n";

    String *tmp = String::stringWithUTF8String(str_xmltv_xml.c_str());
    if (tmp != NULL)
    {
        result = responseWithUTF8Text(request, tmp);
    }

    return result;
}

/**
 * @brief HTTP Live Streaming制御
 *
 *     http://hogehoge/チューナ番号_サービスID/streaming[-プリセット名].m3u8 がリクエスト(プリセットはオプション)され、
 *     チューナ番号／サービスID／プリセット名(ある場合)が有効値の場合にコールされる
 *
 * @param [in] request    HTTPリクエスト
 * @param [in] client     リクエストしたクライアントのアドレス
 * @param [in] tuner      チューナ番号
 * @param [in] service_id チャンネル番号
 * @param [in] preset     プリセット（URLで省略された場合 "default" ）
 */
HTTPResponse *Controller::responseForHLSControl(HTTPRequest *request, SOCKADDR_IN *client, int tuner, int service_id, String *preset)
{
    DebugLog2("Controller::responseForHLSControl()");

    HTTPResponse *result = NULL;

    // client からホスト名を取得
    char hostname[NI_MAXHOST];
    if (getnameinfo((SOCKADDR *)client, sizeof(SOCKADDR_IN), hostname, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
    {
        // ストリーミング制御情報からHLS情報を取得
        Dictionary *hls_info = _streaming_ctrls->dictionaryForKey(KEY_HLS_INFO);
        if (hls_info == NULL)
        {
            hls_info = Dictionary::dictionaryWithCapacity(0);
            _streaming_ctrls->setObject(hls_info, KEY_HLS_INFO);
        }

        // HLS情報から指定チューナの情報を取得
        Dictionary *hls_info_tuner = hls_info->dictionaryForKey(_tuners[tuner]->name());
        if (hls_info_tuner == NULL)
        {
            hls_info_tuner = Dictionary::dictionaryWithCapacity(0);
            hls_info->setObject(hls_info_tuner, _tuners[tuner]->name());
        }

        // 指定チューナの情報からホスト名を確認
        if ((hls_info_tuner->stringForKey(KEY_HOSTNAME) == NULL) || hls_info_tuner->stringForKey(KEY_HOSTNAME)->isEqualToString(hostname))
        {
             // ホスト名を設定
            hls_info_tuner->setString(hostname, KEY_HOSTNAME);

            RaymLock(this);
            if (!_tuners[tuner]->isRecording() && !_tuners[tuner]->isStreaming() && _tuners[tuner]->isLocked())
            {
                if (_tuners[tuner]->type() == Tuner::ISDB_S)
                {
                    _cancel_epg_collect_s = true;
                }
                else
                {
                    _cancel_epg_collect_t = true;
                }
            }
            RaymUnlock(this);

            // HLS制御インスタンス取得
            HTTPLiveStreaming *hls = (HTTPLiveStreaming *)hls_info_tuner->objectForKey(KEY_HLS_INSTANCE);
            if (hls == NULL)
            {
                // 生成
                hls = HTTPLiveStreaming::alloc()->init()->autorelease();
                hls_info_tuner->setObject(hls, KEY_HLS_INSTANCE);
            }

            char tuner_and_service_id[10];
            sprintf_s(tuner_and_service_id, "%03d_%d", tuner, service_id);

            // 異チャンネルへのリクエスト、または、プリセット変更の場合
            if (((hls_info_tuner->stringForKey(KEY_CHANNEL) != NULL) && !hls_info_tuner->stringForKey(KEY_CHANNEL)->isEqualToString(tuner_and_service_id)) ||
                ((hls_info_tuner->stringForKey(KEY_PRESET) != NULL) && !hls_info_tuner->stringForKey(KEY_PRESET)->isEqualToString(preset)))
            {
                // 停止
                hls->stop();

                // チャンネルとプリセットは一旦削除
                hls_info_tuner->removeObjectForKey(KEY_CHANNEL);
                hls_info_tuner->removeObjectForKey(KEY_PRESET);
            }

            // 初回リクエスト or チャンネル／プリセット変更 か？
            if ((hls_info_tuner->stringForKey(KEY_CHANNEL) == NULL) && (hls_info_tuner->stringForKey(KEY_PRESET) == NULL))
            {
                // UDPポートを検索
                Dictionary *mapping = _streaming_ctrls->dictionaryForKey(KEY_MAPPING_UDP_TO_TUNER_SERVICE_ID);
                if (mapping != NULL)
                {
                    Array *ports = mapping->allKeys();
                    if (ports != NULL)
                    {
                        for (uint i = 0; i < ports->count(); ++i)
                        {
                            String *port = (String *)ports->objectAtIndex(i);
                            String *tmp = mapping->stringForKey(port);
                            if (tmp->isEqualToString(tuner_and_service_id))
                            {
                                DebugLog0("udp mapping %d -> %s", port->intValue(), tuner_and_service_id);

                                // ソースを設定
                                hls->setSource(String::stringWithFormat("udp://@0.0.0.0:%d", port->intValue()));

                                // トランスコード
//                                    hls->setTranscode(_props->dictionaryForKey(KEY_PRESETS)->dictionaryForKey(preset));

                                // 出力先を設定
                                String *outpath = _props->stringForKey(KEY_CACHE_PATH)->stringByAppendingPathComponent(String::stringWithFormat("%03d", tuner));
                                _mkdir(outpath->cString());
                                hls->setOutputPath(outpath);

                                // インデックス名
                                hls->setIndexName(String::stringWithFormat("live_%s", tuner_and_service_id));

                                // 開始
                                if (hls->start())
                                {
                                    DebugLog0("hls->start() success");

                                    // チャンネルを保存
                                    hls_info_tuner->setString(tuner_and_service_id, KEY_CHANNEL);

                                    // プリセットを保存
                                    hls_info_tuner->setString(preset, KEY_PRESET);
                                }

                                break;
                            }
                        }
                    }
                }
            }

            RaymUnlock(this);

            String *index_path = hls->indexPath();
            FileManager *fm = FileManager::defaultManager();
            int count = 0;
            while (count++ < 5)
            {
                bool isDirectory = false;
                if (fm->fileExistsAtPath(index_path, &isDirectory))
                {
                    if (!isDirectory)
                    {
                        DebugLog0("file exists");
                        bool done = false;
                        while (!done)
                        {
                            RaymLock(this);
                            result = _httpd->responseWithPath(index_path, request);
                            RaymUnlock(this);
                            if (result != NULL)
                            {
                                break;
                            }
                            ::Sleep(100);
                        }
                        break;
                    }
                }
                ::Sleep(1000);
            }

            RaymLock(this);

            hls_info_tuner->setInteger(0, KEY_COUNTER);

            if (result == NULL)
            {
                DebugLog0("file no exists");
                result = responseForReloadURI(request, client, request->URI()->cString(), 10);
            }
        }
        else
        {
            // 他ホストが使用中
        }
    }

    return result;
}

HTTPResponse *Controller::requestTunerControl(HTTPRequest *request, SOCKADDR_IN *client, int tuner)
{
    DebugLog2("%s\n", __FUNCTION__);

    HTTPResponse *result = NULL;

    // lock
    RaymLock(this);

    // URI取得
    String *uri = request->URI();
    while (uri != NULL)
    {
        // CGIリクエストとして解析
        Dictionary *cgi = request->parseAsCGI();
        if (cgi != NULL)
        {
            uri = cgi->stringForKey(HTTPRequest::KEY_CGI);
            if (uri == NULL)
            {
                break;
            }
        }

        //
        // チューナ情報
        //
        if (uri->isMatch("^/[0-9]{3}/info.xml$") && (cgi == NULL))
        {
        }

        //
        // チャンネル設定
        //   /ttt/channel=nnn
        //
        else if (uri->isMatch("^/[0-9]{3}/channel=[0-9]{1,3}$") && (cgi == NULL))
        {
DebugLog0("ch set");
            String *ch = uri->substringFromIndex(13);
            if (ch == NULL)
            {
                break;
            }
            int channel = ch->intValue();
DebugLog0("set %d channel:%d(%s)\n", tuner, channel, ch->cString());
            DebugLog2("set channel:%d(%s)\n", channel, ch->cString());
            if (setChannel(tuner, channel))
            {
                // success
                DebugLog2("success.\n");
                result = responseForSuccess(request);
            }
            else
            {
                // failed
                DebugLog2("failed.\n");
                result = responseForFailed(request);
            }
        }

        //
        // 録画開始（最大23:59まで）
        //   /ttt/recording=on?hour=hh&min=mm[&channel=nnn]
        //
        else if (uri->isMatch("^/[0-9]{3}/recording=on$") && (cgi != NULL))
        {
            // パラメータがあるか
            Array *params = cgi->arrayForKey(HTTPRequest::KEY_PARAMS);
            if (params == NULL)
            {
                break;
            }

            // パラメータ数は２〜３か
            if ((params->count() != 2) && (params->count() != 3))
            {
                break;
            }

            // パラメータのチェック
            String *p_hour    = NULL;
            String *p_min     = NULL;
            String *p_channel = NULL;

            struct {
                const char *name;
                String **variable;
                const char *regex;
            }
            cgi[] =
            {
                {"hour",    &p_hour,    "^[0-2][0-9]$"},
                {"min",     &p_min,     "^[0-5][0-9]$"},
                {"channel", &p_channel, "^[0-9]{3}$"},
                {NULL, NULL, NULL}
            };

            for (uint i = 0; cgi[i].name != NULL; ++i)
            {
                *(cgi[i].variable) = NULL;
                for (uint j = 0; j < params->count(); ++j)
                {
                    Dictionary *param = (Dictionary *)params->objectAtIndex(j);
                    String *value = param->stringForKey(cgi[i].name);
                    if ((value != NULL) && value->isMatch(cgi[i].regex))
                    {
                        *(cgi[i].variable) = value;
                    }
                }
            }

            // パラメータは有効か
            if ((p_hour == NULL) || (p_min == NULL))
            {
                break;
            }

            // チャンネル設定
            int channel = 0;
            if (p_channel != NULL)
            {
                channel = p_channel->intValue();
            }
            else
            {
                channel = _tuners[tuner]->channel();
            }

            if (channel >= 0)
            {
                // recording on
                int hour = p_hour->intValue();
                int min = p_min->intValue();
                if (hour < 24)
                {
                    // EPG生成
                    Dictionary *epg = Dictionary::dictionaryWithCapacity(0);
                    while (true)
                    {
                        time_t now;
                        time(&now);
                        now += 1; // margin
                        TM tm;
                        if (localtime_s(&tm, &now) != 0)
                        {
                            epg = NULL;
                            break;
                        }
                        TM end;
                        end = tm;
                        end.tm_hour += hour;
                        end.tm_min += min;
                        end.tm_sec += 1; // margin
                        if (mktime(&end) == -1)
                        {
                            epg = NULL;
                            break;
                        }

                        char tmp[16];

                        // Date
                        sprintf_s(tmp, sizeof(tmp), "%04d/%02d/%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
                        epg->setString(tmp, KEY_EPG_DATE);

                        // Start
                        sprintf_s(tmp, sizeof(tmp), "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
                        epg->setString(tmp, KEY_EPG_START);

                        // End
                        sprintf_s(tmp, sizeof(tmp), "%02d:%02d:%02d", end.tm_hour, end.tm_min, end.tm_sec);
                        epg->setString(tmp, KEY_EPG_END);

                        // Channel
                        sprintf_s(tmp, sizeof(tmp), "%d", channel);
                        epg->setString(tmp, KEY_EPG_CHANNEL);

                        // 繰り返し
                        epg->setString("off", KEY_EPG_REPEAT); 

                        // Status
                        epg->setString("ready", KEY_EPG_STATUS);

                        break;
                    }
                
                    if (epg != NULL)
                    {
                        // 録画開始＆結果生成
                        result = responseByResult(request, reserve(tuner, epg));
                    }
                }
            }
        }

        //
        // 録画停止
        //   /ttt/recording=off
        //
        else if (uri->isMatch("^/[0-9]{3}/recording=off$") && (cgi == NULL))
        {
            // recording off
            DebugLog2("recording off: %s\n", uri->cString());
            if (cancel(tuner, -1))
            {
                // success
                DebugLog2("success.\n");
                result = responseForSuccess(request);
            }
            else
            {
                // failed
                DebugLog2("failed.\n");
                result = responseForFailed(request);
            }
        }

        //
        // ストリーミング開始
        //   /ttt/streaming=on?udp=nnnnn(&host=aaaaaa)
        //
        else if (uri->isMatch("^/[0-9]{3}/streaming=on$") && (cgi != NULL))
        {
            // パラメータがあるか
            Array *params = cgi->arrayForKey(HTTPRequest::KEY_PARAMS);
            if (params == NULL)
            {
                break;
            }

            // パラメータ数は１〜２か
            if ((params->count() != 1) && (params->count() != 2))
            {
                break;
            }

            // パラメータのチェック
            String *p_udp  = NULL;
            String *p_host = NULL;

            struct {
                const char *name;
                String **variable;
                const char *regex;
            }
            cgi[] =
            {
                {"udp",  &p_udp,  "^[0-9]{1,5}$"},
                {"host", &p_host, "^.+$"},
                {NULL, NULL, NULL}
            };

            for (uint i = 0; cgi[i].name != NULL; ++i)
            {
                *(cgi[i].variable) = NULL;
                for (uint j = 0; j < params->count(); ++j)
                {
                    Dictionary *param = (Dictionary *)params->objectAtIndex(j);
                    String *value = param->stringForKey(cgi[i].name);
                    if ((value != NULL) && value->isMatch(cgi[i].regex))
                    {
                        *(cgi[i].variable) = value;
                    }
                }
            }

            // パラメータチェック
            if (p_udp == NULL)
            {
                break;
            }

            SOCKADDR_IN dst_addr;
            
            if (p_host != NULL)
            {
    #if 0
                std::string host = udpstr.substr(idx + 5);
                udpstr = udpstr.substr(0, idx - 1);
                DebugLog2("udp: %s\n", udpstr.c_str());
                DebugLog2("host: %s\n", host.c_str());
                struct hostent *ent = gethostbyname(host.c_str());
    #endif
            }
            else
            {
                memcpy(&dst_addr, client, sizeof(SOCKADDR_IN));
            }
            dst_addr.sin_port = htons(p_udp->intValue());
            
            if (_tuners[tuner]->startStreaming(&dst_addr))
            {
                // success
                DebugLog2("success.\n");
                result = responseForSuccess(request);
            }
            else
            {
                // failed
                DebugLog2("failed.\n");
                result = responseForFailed(request);
            }
        }

        //
        // ストリーミング停止
        //   /ttt/streaming=off(?host=aaaa)
        //
        else if (uri->isMatch("^/[0-9]{3}/streaming=off$"))
        {
            // パラメータ
            String *p_host = NULL;

            // パラメータがあるか
            if (cgi != NULL)
            {
                Array *params = cgi->arrayForKey(HTTPRequest::KEY_PARAMS);
                if (params == NULL)
                {
                    break;
                }

                // パラメータ数は０〜１か
                if ((params->count() != 0) && (params->count() != 1))
                {
                    break;
                }

                struct {
                    const char *name;
                    String **variable;
                    const char *regex;
                }
                cgi[] =
                {
                    {"host", &p_host, "^.+$"},
                    {NULL, NULL, NULL}
                };

                for (uint i = 0; cgi[i].name != NULL; ++i)
                {
                    *(cgi[i].variable) = NULL;
                    for (uint j = 0; j < params->count(); ++j)
                    {
                        Dictionary *param = (Dictionary *)params->objectAtIndex(j);
                        String *value = param->stringForKey(cgi[i].name);
                        if ((value != NULL) && value->isMatch(cgi[i].regex))
                        {
                            *(cgi[i].variable) = value;
                        }
                    }
                }
            }

            SOCKADDR_IN dst_addr;
            if (p_host != NULL)
            {
            }
            else
            {
            }

            _tuners[tuner]->stopStreaming();
            
            // success
            DebugLog2("success.\n");
            result = responseForSuccess(request);
        }

        //
        // HLS制御
        //
        else if (uri->isMatch("^/[0-9]{3}/[0-9]+/streaming(-[^\\.]+)?.m3u8$") && (cgi == NULL))
        {
            // URIからサービスIDを抽出
            Range r = uri->rangeOfString("/streaming");
            int service_id = uri->substringToIndex(r.location)->substringFromIndex(5)->intValue();

            // presetが指定されている場合、presetを抽出
            String *preset = NULL;
            if (uri->isMatch("streaming-"))
            {
                preset = uri->substringFromIndex(r.location + 11);
                preset = preset->substringToIndex(preset->length() - 5);
            }
            else
            {
                // なければ "default"
                preset = String::stringWithUTF8String(KEY_DEFAULT);
            }

            // チャンネル／presetが有効か確認
            Dictionary *mapping = _streaming_ctrls->dictionaryForKey(KEY_MAPPING_TUNER_SERVICE_ID_TO_UDP);
            if ((mapping != NULL) && (mapping->stringForKey(String::stringWithFormat("%03d_%d", tuner, service_id)) != NULL) &&
                (_props->dictionaryForKey(KEY_PRESETS) != NULL) &&
                (_props->dictionaryForKey(KEY_PRESETS)->objectForKey(preset) != NULL))
            {
                // 
                result = responseForHLSControl(request, client, tuner, service_id, preset);
            }
            else
            {
                result = responseForFailed(request);
            }
        }
        else if (uri->isMatch("^/[0-9]{3}/[0-9]+/streaming-[0-9]+.ts$") && (cgi == NULL))
        {
            // URIからサービスIDを抽出
            Range r = uri->rangeOfString("/streaming");
            int service_id = uri->substringToIndex(r.location)->substringFromIndex(5)->intValue();

            // client からホスト名を取得
            char hostname[NI_MAXHOST];
            if (getnameinfo((SOCKADDR *)client, sizeof(SOCKADDR_IN), hostname, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0)
            {
                Dictionary *hls_info = _streaming_ctrls->dictionaryForKey(KEY_HLS_INFO);
                if (hls_info != NULL)
                {
                    Dictionary *hls_info_tuner = hls_info->dictionaryForKey(_tuners[tuner]->name());
                    if (hls_info_tuner != NULL)
                    {
                        if (hls_info_tuner->stringForKey(KEY_HOSTNAME)->isEqualToString(hostname) &&
                            hls_info_tuner->stringForKey(KEY_CHANNEL)->isEqualToString(String::stringWithFormat("%03d_%d", tuner, service_id)))
                        {
                            String *path = ((HTTPLiveStreaming *)hls_info_tuner->objectForKey(KEY_HLS_INSTANCE))->outputPath();
                            if (path != NULL)
                            {
                                result = _httpd->responseWithPath(path->stringByAppendingPathComponent(uri->substringFromIndex(r.location + 1)), request);
                            }
                        }
                    }
                }
            }
        }

        break;
    }

    // unlock
    RaymUnlock(this);

    return result;
}

HTTPResponse *Controller::requestRTSP(RTSPRequest *request, SOCKET sock, SOCKADDR_IN *client)
{
    HTTPResponse *result = NULL;

    static int streaming_port = 0;

    String *uri = request->URI();
    if (uri->isMatch("^rtsp://[^/]+(:\\d+)/[0-9]{3}/[0-9]+/streaming.sdp/?$"))
    {
        if (request->method()->isEqualToString("OPTIONS"))
        {
            result = HTTPResponse::alloc()->init();
            result->setVersion(request->version());
            result->setStatus(HTTP_STATUS_OK);
            result->setReason(HTTPDaemon::reasonForStatus(HTTP_STATUS_OK));

            InternetTextMessageHeader *header =InternetTextMessageHeader::alloc()->init();
            header->setFieldBodyWithName(request->message()->header()->fieldBodyForName("CSeq"), String::stringWithUTF8String("CSeq"));
            header->setFieldBodyWithName(String::stringWithUTF8String("DESCRIBE, SETUP, TEARDOWN, PLAY"), String::stringWithUTF8String("Public"));

            InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, NULL);
            RELEASE(header);

            result->setMessage(message);
            result->autorelease();
            RELEASE(message);
        }
        else if (request->method()->isEqualToString("DESCRIBE"))
        {
            String *accept = request->message()->header()->fieldBodyForName("Accept");
            if ((accept != NULL) && accept->isMatch("application/sdp"))
            {
                result = HTTPResponse::alloc()->init();
                result->setVersion(request->version());
                result->setStatus(HTTP_STATUS_OK);
                result->setReason(HTTPDaemon::reasonForStatus(HTTP_STATUS_OK));

                InternetTextMessageHeader *header =InternetTextMessageHeader::alloc()->init();
                header->setFieldBodyWithName(request->message()->header()->fieldBodyForName("CSeq"), "CSeq");
                header->setFieldBodyWithName("23 Jan 2007 15:35:06 JST", "Date");
                header->setFieldBodyWithName("application/sdp", "Content-Type");

                std::string str_body;
                str_body =  "v=0\r\n";                                    // プロトコルのバージョン
                str_body += "o=hoge 1234 5678 IN IP4 172.19.29.9\r\n";   // 発信元およびセッション識別子
                str_body += "s=test\r\n";                                // セッション名
                str_body += "c=IN IP4 172.19.29.9\r\n";                                // 
                str_body += "t=0 0\r\n";
                str_body += "a=control:*\r\n";
                str_body += "a=range:npt=0-\r\n";
                str_body += "m=video 0 RAW/RAW/UDP 33\r\n";

                // Content-Length
                header->setFieldBodyWithName(String::stringWithFormat("%I64u", str_body.length()), "Content-Length");

                InternetTextMessageBody *body = InternetTextMessageBody::alloc()->initWithString(String::stringWithUTF8String(str_body.c_str()));

                InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, body);
                RELEASE(header);
                RELEASE(body);

                result->setMessage(message);
                result->autorelease();
                RELEASE(message);
            }
        }
        else if (request->method()->isEqualToString("SETUP"))
        {
            String *transport = request->message()->header()->fieldBodyForName("Transport");
            if ((transport != NULL) && transport->isMatch("UDP.*;unicast;client_port=\\d+"))
            {
                Range r = transport->rangeOfString("_port=");
                String *port = transport->substringFromIndex(r.location + 6);
                r = port->rangeOfString("-");
                port = port->substringToIndex(r.location);
                DebugLog0("port: %s", port->cString());
                streaming_port = port->intValue();

                result = HTTPResponse::alloc()->init();
                result->setVersion(request->version());
                result->setStatus(HTTP_STATUS_OK);
                result->setReason(HTTPDaemon::reasonForStatus(HTTP_STATUS_OK));

                InternetTextMessageHeader *header =InternetTextMessageHeader::alloc()->init();
                header->setFieldBodyWithName(request->message()->header()->fieldBodyForName("CSeq"), "CSeq");
                header->setFieldBodyWithName("12345678", "Session");
                String *tr = transport->stringByAppendingString(";server_port=50020-50021");
                header->setFieldBodyWithName(tr, "Transport");

                InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, NULL);
                RELEASE(header);

                result->setMessage(message);
                result->autorelease();
                RELEASE(message);
            }
        }
        else if (request->method()->isEqualToString("PLAY"))
        {
            String *range = request->message()->header()->fieldBodyForName("Range");
            if (range != NULL)
            {
                result = HTTPResponse::alloc()->init();
                result->setVersion(request->version());
                result->setStatus(HTTP_STATUS_OK);
                result->setReason(HTTPDaemon::reasonForStatus(HTTP_STATUS_OK));

                InternetTextMessageHeader *header =InternetTextMessageHeader::alloc()->init();
                header->setFieldBodyWithName(request->message()->header()->fieldBodyForName("CSeq"), "CSeq");
                header->setFieldBodyWithName(request->message()->header()->fieldBodyForName("Session"), "Session");
                header->setFieldBodyWithName(range, "Range");

                InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, NULL);
                RELEASE(header);

                result->setMessage(message);
                result->autorelease();
                RELEASE(message);

                SOCKADDR_IN dst_addr;

                dst_addr.sin_family = AF_INET;
                dst_addr.sin_addr.s_addr = htonl(0xac131d01);
                dst_addr.sin_port = htons(streaming_port);

                if (_tuners[1]->startStreaming(&dst_addr))
                {
                    DebugLog0("streaming start ok");
                }
            }
        }
        else if (request->method()->isEqualToString("TEARDOWN"))
        {
            result = HTTPResponse::alloc()->init();
            result->setVersion(request->version());
            result->setStatus(HTTP_STATUS_OK);
            result->setReason(HTTPDaemon::reasonForStatus(HTTP_STATUS_OK));

            InternetTextMessageHeader *header =InternetTextMessageHeader::alloc()->init();
            header->setFieldBodyWithName(request->message()->header()->fieldBodyForName("CSeq"), String::stringWithUTF8String("CSeq"));

            InternetTextMessage *message = InternetTextMessage::alloc()->initWithHeaderAndBody(header, NULL);
            RELEASE(header);

            result->setMessage(message);
            result->autorelease();
            RELEASE(message);
        }
    }
    else
    {
        DebugLog0("no match");
    }

    if (result == NULL)
    {
        result = HTTPResponse::alloc()->init();
        result->setVersion(request->version());
        result->setStatus(HTTP_STATUS_NOT_FOUND);
        result->setReason(HTTPDaemon::reasonForStatus(HTTP_STATUS_NOT_FOUND));
        result->autorelease();
    }

    return result;
}

HTTPResponse *Controller::request(HTTPRequest *request, SOCKET sock, SOCKADDR_IN *client)
{
    DebugLog2("%s\n", __FUNCTION__);

    // 初期化チェック
    bool flag = false;
    RaymLock(this);
    flag = _initialized;
    RaymUnlock(this);
    if (!flag)
    {
        return NULL;
    }

    if (request->version()->isEqualToString("RTSP/1.0"))
    {
        return requestRTSP((RTSPRequest *)request, sock, client);
    }

    HTTPResponse *response = NULL;

    if (request->method()->isEqualToString("GET") ||
        request->method()->isEqualToString("HEAD"))
    {
        // URI
        String *uri = request->URI();
        DebugLog0("request(%d): %s\n", sock, uri->cString());

        //
        //
        //
        if (uri->isMatch("^(/|/index\\.(htm|html))$"))
        {
            response = responseForMain(request, client);
        }

        else if (uri->isMatch("^/status.html$"))
        {
            response = responseForStatus(request, client);
        }

        //
        else if (uri->isMatch("^/info.xml$"))
        {
        }
        else if (uri->isMatch("^/video.xml$"))
        {
        }

        //
        else if (uri->isMatch("^/suspend.xml$"))
        {
            if (canSuspend())
            {
//                response = responseForSuccess(request);
//                delaySuspend();
            }
            else
            {
//                response = responseForFailed(request);
            }
        }

        //
        else if (uri->isMatch("^/exec_vlc.html$"))
        {
//            _vlc->execute();
//            return responseForRefreshMain(request, client);
        }

        //
        // tuner control
        //
        else if (uri->isMatch("^/[0-9]{3}/"))
        {
            // String::substringWithRange() を実装するのを後回しにするので。。
            std::string s = uri->cString();
            int tuner = atoi(s.substr(1, 3).c_str());
            if ((0 <= tuner) && (tuner < _tunerCount))
            {
                response = requestTunerControl(request, client, tuner);
            }
        }

        //
        // reservation control
        //
        else if (uri->isMatch("^/reservation.html$"))
        {
            response = responseForReservation(request, client);
        }
        else if (uri->isMatch("^/regist.cgi"))
        {
            response = responseForRegistCGI(request, client);
        }
        else if (uri->isMatch("^/cancel.cgi"))
        {
            response = responseForCancelCGI(request, client);
        }
        else if (uri->isMatch("^/add_keywords.cgi"))
        {
            response = responseForAddKeywordsCGI(request, client);
        }
        else if (uri->isMatch("^/mod_keywords.cgi"))
        {
            response = responseForModKeywordsCGI(request, client);
        }
        else if (uri->isMatch("^/reservation.xml$"))
        {
        }
        else if (uri->isMatch("^/[0-9]{6}/"))
        {
//            DebugLog2("reservation: %s\n", uri.c_str());
        }

        //
        // video control
        //
        else if (uri->isMatch("^/[0-9]{9}/"))
        {
//            DebugLog2("video: %s\n", uri.c_str());
        }

        //
        // program
        //
        else if (uri->isMatch("^/programs.*\\.html$"))
        {
            response = responseForPrograms(request, client);
        }
        else if (uri->isMatch("^/programs.xml$"))
        {
//            collectEPG();
//            return responseWithDictionary(request, _programs);
        }

        //
        //
        else if (uri->isMatch("^/tv.html$"))
        {
//            return responseForTV(request, client);
        }

        // IPTV対応
        else if (uri->isMatch("^/iptv.m3u8$"))
        {
            return responseForPlaylist(request, client);
        }
        else if (uri->isMatch("^/xmltv.xml$"))
        {
            return responseForXmltv(request, client);
        }

        //
        else if (uri->isMatch("^/iptd.log$"))
        {
            String *path = _system_path->stringByAppendingPathComponent("log");
            if (path != NULL)
            {
                path = path->stringByAppendingPathComponent("iptd.log");
                if (path != NULL)
                {
                    response = _httpd->responseWithPath(path, request);
                    if (response != NULL)
                    {
                        if (response->message() != NULL)
                        {
                            if (response->message()->header() != NULL)
                            {
                                response->message()->header()->setFieldBodyWithName("text/plane; charset=UTF-8", "Content-Type");
                            }
                        }
                    }
                }
            }
        }
    }
    else if (request->method()->isEqualToString("POST"))
    {
        // URI
        String *uri = request->URI();
        DebugLog1("POST: %s\n", uri->cString());
        InternetTextMessage *message = request->message();
        if (message != NULL)
        {
            DebugLog3("message: ");
            InternetTextMessageHeader *header = message->header();
            if (header != NULL)
            {
                DebugLog3("header: ");
            }
            InternetTextMessageBody *body = message->body();
            if (body != NULL)
            {
                DebugLog3("body: ");
            }
        }
    }

    return response;
}

HTTPRequest * Controller::readRequest(SOCKET sock)
{
    return RTSPRequest::requestWithSocket(sock);
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- プロパティ取得 -------
#endif

bool Controller::isTunerInitialized(int tuner)
{
    DebugLog2("Controller::isTunerInitialized()");

    bool result = false;

    if ((0 <= tuner) && (tuner < _tunerCount))
    {
        // lock
        RaymLock(this);

        Dictionary *tunersInfo = _props->dictionaryForKey(KEY_TUNERS);
        if (tunersInfo != NULL)
        {
            Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[tuner]->name());
            if (tunerInfo != NULL)
            {
                result = tunerInfo->boolForKey(KEY_INITIALIZED);
            }
        }

        // unlock
        RaymUnlock(this);
    }

    return result;
}

bool Controller::isTunerEnabled(int tuner)
{
    DebugLog2("Controller::isTunerEnabled()");

    bool result = false;

    if ((0 <= tuner) && (tuner < _tunerCount))
    {
        // lock
        RaymLock(this);

        Dictionary *tunersInfo = _props->dictionaryForKey(KEY_TUNERS);
        if (tunersInfo != NULL)
        {
            Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[tuner]->name());
            if (tunerInfo != NULL)
            {
                result = tunerInfo->boolForKey(KEY_ENABLED);
            }
        }

        // unlock
        RaymUnlock(this);
    }

    return result;
}

bool Controller::isChannelEnabled(int tuner, int channel)
{
    DebugLog2("Controller::isChannelEnabled()");

    bool result = false;

    if ((0 <= tuner) && (tuner < _tunerCount) && (0 <= channel) && (channel <= Tuner::MAX_CHANNELS_ISDB_T))
    {
        // lock
        RaymLock(this);

        Dictionary *tunersInfo = _props->dictionaryForKey(KEY_TUNERS);
        if (tunersInfo != NULL)
        {
            Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[tuner]->name());
            if (tunerInfo != NULL)
            {
                Dictionary *channels = tunerInfo->dictionaryForKey(KEY_CHANNELS);
                if (channels != NULL)
                {
                    char key[4];
                    sprintf_s(key, "%03d", channel);
                    Dictionary *chInfo = channels->dictionaryForKey(key);
                    if (chInfo != NULL)
                    {
                        result = chInfo->boolForKey(KEY_ENABLED);
                    }
                }
            }
        }

        // unlock
        RaymUnlock(this);
    }

    return result;
}

Array *Controller::stationInfos(Tuner::Type type)
{
    DebugLog2("Controller::stationInfosForISDB_T()");

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

    // lock
    RaymLock(this);

    Dictionary *tunersInfo = _props->dictionaryForKey(KEY_TUNERS);
    if (tunersInfo != NULL)
    {
        for (int i = 0; i < _tunerCount; ++i)
        {
            if (_tuners[i]->type() == type)
            {
                Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[i]->name());
                if (tunerInfo != NULL)
                {
                    Dictionary *channels = tunerInfo->dictionaryForKey(KEY_CHANNELS);
                    if (channels != NULL)
                    {
                        for (uint ch = 0; ch <= (type == Tuner::ISDB_T ? Tuner::MAX_CHANNELS_ISDB_T : Tuner::MAX_CHANNELS_ISDB_S); ++ch)
                        {
                            char chkey[4];
                            sprintf_s(chkey, "%03d", ch);
                            Dictionary *channel = channels->dictionaryForKey(chkey);
                            if ((channel != NULL) && channel->boolForKey(KEY_ENABLED))
                            {
                                result->addObject(channel);
                            }
                        }
                    }
                }
                break;
            }
        }
    }

    // unlock
    RaymUnlock(this);

    return result;
}

String *Controller::stationName(Tuner::Type type, int channel)
{
    DebugLog2("Controller::stationName()");

    String *result = NULL;

    // lock
    RaymLock(this);

    Dictionary *tunersInfo = _props->dictionaryForKey(KEY_TUNERS);
    if (tunersInfo != NULL)
    {
        for (int i = 0; i < _tunerCount; ++i)
        {
            if (_tuners[i]->type() == type)
            {
                Dictionary *tunerInfo = tunersInfo->dictionaryForKey(_tuners[i]->name());
                if (tunerInfo != NULL)
                {
                    Dictionary *channels = tunerInfo->dictionaryForKey(KEY_CHANNELS);
                    if (channels != NULL)
                    {
                        char chkey[4];
                        sprintf_s(chkey, "%03d", channel);
                        Dictionary *channel = channels->dictionaryForKey(chkey);
                        if ((channel != NULL) && channel->boolForKey(KEY_ENABLED))
                        {
                            result = channel->stringForKey(KEY_NAME);
                        }
                    }
                }
                break;
            }
        }
    }

    // unlock
    RaymUnlock(this);

    return result;
}

Array *Controller::programsForServices(Array *services)
{
    DebugLog2("Controller::programsForServices()");

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

    // lock
    RaymLock(_epgs);

    for (uint i = 0; i < services->count(); ++i)
    {
        Dictionary *service = (Dictionary *)services->objectAtIndex(i);
        Dictionary *services = _epgs->dictionaryForKey(KEY_SERVICES);
        Array *epgs = services->arrayForKey(service->stringForKey(KEY_SERVICE_ID));
        if (epgs != NULL)
        {
            result->addObjectsFromArray(epgs);
        }
    }

    // sort
    result = result->sortedArrayUsingFunction(compareFunction, this);

    // unlock
    RaymUnlock(_epgs);

    return result;
}

String *Controller::stationNameForServiceID(String *service_id)
{
    DebugLog2("Controller::stationNameForServiceID()");

    String *result = NULL;

    if (service_id != NULL)
    {
        // lock
        RaymLock(this);

        Dictionary *tunersInfo = _props->dictionaryForKey(KEY_TUNERS);
        if (tunersInfo != NULL)
        {
            Array *tuners_key = tunersInfo->allKeys();

            for (uint i = 0; (result == NULL) && (i < tuners_key->count()); ++i)
            {
                Dictionary *tunerInfo = tunersInfo->dictionaryForKey((String *)tuners_key->objectAtIndex(i));
                if (tunerInfo != NULL)
                {
                    Dictionary *channels = tunerInfo->dictionaryForKey(KEY_CHANNELS);
                    if (channels != NULL)
                    {
                        Array *chkey = channels->allKeys();
                        for (uint ch = 0; (result == NULL) && (ch < chkey->count()); ++ch)
                        {
                            Dictionary *channel = channels->dictionaryForKey((String *)chkey->objectAtIndex(ch));
                            if (channel != NULL)
                            {
                                Array *services = channel->arrayForKey(KEY_SERVICES);
                                if (services != NULL)
                                {
                                    for (uint j = 0; j < services->count(); ++j)
                                    {
                                        Dictionary *service = (Dictionary *)services->objectAtIndex(j);
                                        String *sid = service->stringForKey(KEY_SERVICE_ID);
                                        if (sid != NULL)
                                        {
                                            if (sid->isEqualToString(service_id))
                                            {
                                                result = service->stringForKey(KEY_NAME);
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        // unlock
        RaymUnlock(this);
    }

    return result;
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- タイマディスパッチャ -------
#endif

void Controller::timerExpired(Timer *timer, void *userInfo)
{
    DebugLog2("Controller::timerExpired()");

    //

    switch ((long long)userInfo)
    {
    case CMD_RESTART:
        if (restart() > 0)
        {
            // 初期化成功
            DebugLog2("tuner initialize success.");

            _cancel_epg_collect_s = false;
            _cancel_epg_collect_t = false;

            // EPG収集用タイマ起動(ISDB-S)
            _timer_epg_s = Timer::alloc()->initWithTimeInterval(DEF_COLLECT_EPG_DELAY, this, (void *)CMD_COLLECT_EPG_ISDB_S, true);
            if (_timer_epg_s != NULL)
            {
                _timer_epg_s->fire();
            }

            // EPG収集用タイマ起動(ISDB-T)
            _timer_epg_t = Timer::alloc()->initWithTimeInterval(DEF_COLLECT_EPG_DELAY, this, (void *)CMD_COLLECT_EPG_ISDB_T, true);
            if (_timer_epg_t != NULL)
            {
                _timer_epg_t->fire();
            }
        }
        else
        {
            // 失敗
            DebugLog0("tuner initialize failed.");
        }
        break;

    case CMD_SUSPEND:
        if (canSuspend())
        {
            suspend();
        }
        break;

    case CMD_PERIODIC:                                  // 周期処理
        periodic();
        break;

    case CMD_PERIODIC_2:                                // 周期処理２
        periodic_2();
        break;

    case CMD_COLLECT_EPG_ISDB_S:                        // EPG収集(ISDB-S)
        // 番組情報収集(ISDB-S)
        if (collectEPGs(Tuner::ISDB_S))
        {
            // 終了(繰り返し無し)
            _timer_epg_s->setRepeats(false);
        }
        else
        {
            // リトライ
            _timer_epg_s->setTimeInterval(DEF_COLLECT_EPG_RETRY);
        }
        break;

    case CMD_COLLECT_EPG_ISDB_T:                        // 番組情報収集(ISDB-T)
        if (collectEPGs(Tuner::ISDB_T))
        {
            // 終了(繰り返し無し)
            _timer_epg_t->setRepeats(false);
        }
        else
        {
            // リトライ
            _timer_epg_t->setTimeInterval(DEF_COLLECT_EPG_RETRY);
        }
        break;
    }
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- 起動／停止関連 -------
#endif

// システム状態をチェックしてサスペンド可能かを返す
bool Controller::canSuspend()
{
    return isIdleState(true);
}

bool Controller::canTerminate()
{
    return isIdleState(false);
}

bool Controller::isIdleState(bool suspend)
{
    DebugLog2("Controller::isIdleState() start.");

    bool result = false;

    // lock
    RaymLock(this);

    // 初期化済みか
    while (_initialized)
    {
        // EPG(ISDB-S)収集中か
        if (suspend && (_timer_epg_s != NULL) && _timer_epg_s->valid())
        {
            // サスペンド不可
            break;
        }

        // EPG(ISDB-T)収集中か
        if (suspend && (_timer_epg_t != NULL) && _timer_epg_t->valid())
        {
            // サスペンド不可
            break;
        }

        // サスペンド可に設定
        result = true;

        // 予約情報をチェック
        // 10分後以内に開始の予約があったらサスペンド不可

        // 現在時刻取得
        time_t now = time(NULL);

        for (int tuner = 0; tuner < _tunerCount; ++tuner)
        {
            Array *array = _reservations->arrayForKey(_tuners[tuner]->name());
            if ((array == NULL) || (array->count() == 0))
            {
                // next tuner
                continue;
            }

            //
            Dictionary *epg = (Dictionary *)array->objectAtIndex(0);
            time_t start;
            time_t end;
            getTimeWithEPG(epg, &start, &end);

            if (start + OFFSET_OF_SUPPRESSION_TIME <= now)
            {
                // サスペンド不可
                result = false;
                break;
            }
        }

        break;
    }

    // unlock
    RaymUnlock(this);

    DebugLog2("Controller::isIdleState() end.");

    return result;
}

/*
 * TrayApp::WndProc() からコールされる
 *
 *   スレッドコンテキスト：メインスレッド
 */
void Controller::systemWillSuspend()
{
    DebugLog2("Controller::systemWillSuspend() start");

    RaymLock(this);
    _cancel_epg_collect_s = true;
    _cancel_epg_collect_t = true;
    RaymUnlock(this);

    // タイマ停止
    if ((_timer_restart != NULL) && _timer_restart->valid())
    {
        _timer_restart->invalidate();
    }
    if ((_timer_epg_s != NULL) && _timer_epg_s->valid())
    {
        _timer_epg_s->invalidate();
    }
    if ((_timer_epg_t != NULL) && _timer_epg_t->valid())
    {
        _timer_epg_t->invalidate();
    }
    if ((_timer_periodic != NULL) && _timer_periodic->valid())
    {
        _timer_periodic->invalidate();
    }
    if ((_timer_periodic_2 != NULL) && _timer_periodic_2->valid())
    {
        _timer_periodic_2->invalidate();
    }

    // lock
    RaymLock(this);

    // タイマ解放
    RELEASE(_timer_restart);
    RELEASE(_timer_epg_s);
    RELEASE(_timer_epg_t);
    RELEASE(_timer_periodic);
    RELEASE(_timer_periodic_2);

    // スケジュール更新
    updateSchedule();

    // 未初期化に設定
    _initialized = false;

    // チューナ解放
    for (int i = 0; i < _tunerCount; ++i)
    {
        if (_tuners[i] != NULL)
        {
            delete _tuners[i];
            _tuners[i] = NULL;
        }
    }

    DebugLog0("system will suspend...");

    // unlock
    RaymUnlock(this);

#ifdef OBJC_MEMORY_CHECK
    DebugLog0("global_objc_count_ = %d", Raym::global_objc_count_);
#endif

    DebugLog2("Controller::systemWillSuspend() end");
}

/*
 * TrayApp::WndProc() からコールされる
 *
 *   スレッドコンテキスト：メインスレッド
 */
void Controller::systemResumed()
{
    DebugLog2("Controller::systemResumed() start");

    DebugLog0("system resumed.");

    if (_timer_restart == NULL)
    {
        // 再開タイマ起動
        _timer_restart = Timer::alloc()->initWithTimeInterval(1.0, this, (void *)CMD_RESTART, false);
        _timer_restart->fire();
    }

    DebugLog2("Controller::systemResumed()");
}

/*
 * TrayApp::WndProc() からコールされる
 *
 *   スレッドコンテキスト：メインスレッド
 */
void Controller::detectIdle()
{
    DebugLog2("Controller::detectIdle()");

    // ここはメインスレッドなのでARPを用意する
    AutoreleasePool *pool = AutoreleasePool::alloc()->init();

    // lock
    RaymLock(this);

    // サスペンド可能か確認
    if (canSuspend())
    {
        if (_idle_count == 0)
        {
            DebugLog0("detect idle...");
        }

        // アイドルカウンタを更新
        ++_idle_count;

        // 起動中アプリと休止状態抑止アプリのチェック
        bool found = false;

        Array *dont_in_suspend = _props->arrayForKey(KEY_DO_NOT_IN_SUSPEND);
        Array *running_apps = Workspace::sharedWorkspace()->runningApplications();
        for (uint i = 0; (i < running_apps->count()) && !found; ++i)
        {
            RunningApplication *ra = (RunningApplication *)running_apps->objectAtIndex(i);

            // 実行中でなければ次へ
            if (!ra->isRunning())
            {
                continue;
            }

            // 実行ファイルのチェック
            String *path = ra->executePath();
            if ((path == NULL) || (path->length() == 0))
            {
                continue;
            }
            DebugLog3("exec path: %s", path->cString());

            // 休止状態抑止アプリリストのチェック
            for (uint j = 0; (j < dont_in_suspend->count()) && !found; ++j)
            {
                found = path->isMatch((String *)dont_in_suspend->objectAtIndex(j));
            }
        }

        // 抑止有効なら KEY_FORCED_SUSPEND_TIME、無効なら KEY_SUSPEND_TIME で取得
        int limit = _props->integerForKey(found ? KEY_FORCED_SUSPEND_TIME : KEY_SUSPEND_TIME);
        DebugLog3("found: %d, _idle_count: %d, limit: %d", found, _idle_count, limit);
        if (_idle_count >= limit)
        {
            // unlock
            RaymUnlock(this);

            bool shutdown = _props->boolForKey(KEY_SHUTDOWN);
            bool hibernation = _props->boolForKey(KEY_HIBERNATION);

            // サスペンド前にARPを解放しておく
            pool->release();

            // 下記の TrayApp::suspend() を使用する場合、自アプリへsuspendメッセージが
            // ブロードキャストされないため、自分でコールしておく
            systemWillSuspend();

            //
            if (shutdown)
            {
                Application::shutdown();
            }
            else if (hibernation)
            {
                Application::suspend();
            }
            else
            {
                Application::sleep();
            }

            // 再度 ARPを用意
            pool = AutoreleasePool::alloc()->init();

            // lock
            RaymLock(this);

            // アイドルカウンタをクリア
            _idle_count = 0;
        }
    }
    else
    {
        // アイドルカウンタをクリア
        _idle_count = 0;
    }

    // unlock
    RaymUnlock(this);

    // ARP解放
    pool->release();
}

void Controller::detectNonIdle()
{
    DebugLog2("Controller::detectNonIdle()");

    // lock
    RaymLock(this);

    if (_idle_count > 0)
    {
        DebugLog0("detect non idle...");
    }
    _idle_count = 0;

    // unlock
    RaymUnlock(this);
}

int Controller::restart()
{
    // lock
    RaymLock(this);

    // 未初期化に設定
    _initialized = false;

    // チューナ解放
    for (int i = 0; i < _tunerCount; ++i)
    {
        if (_tuners[i] != NULL)
        {
            delete _tuners[i];
            _tuners[i] = NULL;
        }
    }

    // 古い予約情報の破棄


    // チューナ初期化
    _tunerCount = ry0::device::TunerFactory::scan(_tuners, _multi2_dll);

    // unlock
    RaymUnlock(this);

    // チューナでループ
    for (int i = 0; i < _tunerCount; ++i)
    {
        // チューナが初期化済みか
        if (!isTunerInitialized(i))
        {
            // チャンネルスキャン
            scanChannel(i);
        }

        if (isTunerEnabled(i))
        {
            // チャンネル設定
            setChannel(i, getChannel(i));
        }
    }

    Dictionary *tunerInfos = _props->dictionaryForKey(KEY_TUNERS);

    // EPG未収集の場合
    //   最大チューナ番号の各サービスにIPTV有効フラグを立てる
    if (_epgs->count() == 0)
    {
        Dictionary *flags = Dictionary::dictionaryWithCapacity(0);
        for (int idx = _tunerCount - 1; idx >= 0; --idx)
        {
            Dictionary *tunerInfo = tunerInfos->dictionaryForKey(_tuners[idx]->name());
            if (tunerInfo->boolForKey(KEY_ENABLED))
            {
                int ch_max = (_tuners[idx]->type() == Tuner::Type::ISDB_T) ? Tuner::MAX_CHANNELS_ISDB_T : Tuner::MAX_CHANNELS_ISDB_S;
                for (int ch = 0; ch < ch_max; ++ch)
                {
                    char key[8];
                    sprintf_s(key, "%03d", ch);
                    Dictionary *channelInfo = tunerInfo->dictionaryForKey(KEY_CHANNELS)->dictionaryForKey(key);
                    if ((channelInfo != NULL) && channelInfo->boolForKey(KEY_ENABLED))
                    {
                        Array *services = channelInfo->arrayForKey(KEY_SERVICES);
                        for (uint service_idx = 0; service_idx < services->count(); ++service_idx)
                        {
                            Dictionary *service = (Dictionary *)services->objectAtIndex(service_idx);
                            if (!flags->boolForKey(service->stringForKey(KEY_SERVICE_ID)))
                            {
                                service->setBool(true, KEY_IPTV_ENABLED);
                                flags->setBool(true, service->stringForKey(KEY_SERVICE_ID));
                            }
                        }
                    }
                }
            }
        }
        _props->writeToFile(_props_path, true);
    }

    // IPTV用データの準備／UDPポートマッピング
    Dictionary *temp_data = Dictionary::dictionaryWithCapacity(0);
    _epgs->setObject(temp_data, KEY_TEMP_DATA);

    _xmltv_channels = "";
    std::string str_iptv_m3u8_local;
    std::string str_iptv_m3u8_remote;

    str_iptv_m3u8_local = "#EXTM3U\r\n";
    str_iptv_m3u8_local += "\r\n";
    str_iptv_m3u8_remote = "#EXTM3U\r\n";
    str_iptv_m3u8_remote += "\r\n";

    int udpport = _props->integerForKey(KEY_BEGIN_UDP_PORT);
    char http_port_str[10];
    sprintf_s(http_port_str, "%d", _props->integerForKey(KEY_HTTP_PORT));
    int idx_isdb_t = 0;
    int idx_isdb_s = 0;
    Tuner::Type type = Tuner::Type::ISDB_T;
    while ((idx_isdb_t < _tunerCount) || (idx_isdb_s < _tunerCount))
    {
        int *idx = (type == Tuner::Type::ISDB_T) ? &idx_isdb_t : &idx_isdb_s;
        while (*idx < _tunerCount)
        {
            if (_tuners[*idx]->type() == type)
            {
                Dictionary *tunerInfo = tunerInfos->dictionaryForKey(_tuners[*idx]->name());
                if (tunerInfo->boolForKey(KEY_ENABLED))
                {
                    int ch_max = (type == Tuner::Type::ISDB_T) ? Tuner::MAX_CHANNELS_ISDB_T : Tuner::MAX_CHANNELS_ISDB_S;
                    for (int ch = 0; ch < ch_max; ++ch)
                    {
                        char key[8];
                        sprintf_s(key, "%03d", ch);
                        Dictionary *channelInfo = tunerInfo->dictionaryForKey(KEY_CHANNELS)->dictionaryForKey(key);
                        if ((channelInfo != NULL) && channelInfo->boolForKey(KEY_ENABLED))
                        {
                            Array *services = channelInfo->arrayForKey(KEY_SERVICES);
                            for (uint service_idx = 0; service_idx < services->count(); ++service_idx)
                            {
                                Dictionary *service = (Dictionary *)services->objectAtIndex(service_idx);
                                if (service->boolForKey(KEY_IPTV_ENABLED))
                                {
                                    char channel_name[32];
                                    sprintf_s(channel_name, "%03d_%s", *idx, service->stringForKey(KEY_SERVICE_ID)->cString());
                                    char channel_service_id[32];
                                    sprintf_s(channel_service_id, "%03d/%s", *idx, service->stringForKey(KEY_SERVICE_ID)->cString());

                                    Dictionary *temp_service = temp_data->dictionaryForKey(service->stringForKey(KEY_SERVICE_ID));
                                    if (temp_service == NULL)
                                    {
                                        temp_service = Dictionary::dictionaryWithCapacity(0);
                                        temp_data->setObject(temp_service, service->stringForKey(KEY_SERVICE_ID));
                                    }
                                    Array *ch_list = temp_service->arrayForKey(KEY_CHANNELS);
                                    if (ch_list == NULL)
                                    {
                                        ch_list = Array::arrayWithCapacity(0);
                                        temp_service->setObject(ch_list, KEY_CHANNELS);
                                    }
                                    ch_list->addObject(String::stringWithUTF8String(channel_name));

                                    Dictionary *udp_to_tuner_service_id = _streaming_ctrls->dictionaryForKey(KEY_MAPPING_UDP_TO_TUNER_SERVICE_ID);
                                    if (udp_to_tuner_service_id == NULL)
                                    {
                                        udp_to_tuner_service_id = Dictionary::dictionaryWithCapacity(0);
                                        _streaming_ctrls->setObject(udp_to_tuner_service_id, KEY_MAPPING_UDP_TO_TUNER_SERVICE_ID);
                                    }

                                    Dictionary *tuner_service_id_to_udp = _streaming_ctrls->dictionaryForKey(KEY_MAPPING_TUNER_SERVICE_ID_TO_UDP);
                                    if (tuner_service_id_to_udp == NULL)
                                    {
                                        tuner_service_id_to_udp = Dictionary::dictionaryWithCapacity(0);
                                        _streaming_ctrls->setObject(tuner_service_id_to_udp, KEY_MAPPING_TUNER_SERVICE_ID_TO_UDP);
                                    }

                                    Dictionary *tuner_service_id_to_channel = _streaming_ctrls->dictionaryForKey(KEY_MAPPING_TUNER_SERVICE_ID_TO_CHANNEL);
                                    if (tuner_service_id_to_channel == NULL)
                                    {
                                        tuner_service_id_to_channel = Dictionary::dictionaryWithCapacity(0);
                                        _streaming_ctrls->setObject(tuner_service_id_to_channel, KEY_MAPPING_TUNER_SERVICE_ID_TO_CHANNEL);
                                    }

                                    char port_str[10];
                                    sprintf_s(port_str, "%d", udpport++);

                                    udp_to_tuner_service_id->setString(channel_name, port_str);
                                    tuner_service_id_to_udp->setString(port_str, channel_name);
                                    tuner_service_id_to_channel->setInteger(ch, channel_name);


                                    _xmltv_channels += "  <channel id=\"";
                                    _xmltv_channels += channel_name;
                                    _xmltv_channels += "\"";
                                    // transport_stream_id
                                    // original_network_id
                                    _xmltv_channels += " service_id=\"";
                                    _xmltv_channels += service->stringForKey(KEY_SERVICE_ID)->cString();
                                    _xmltv_channels += "\">\r\n";

                                    _xmltv_channels += "    <display-name lang=\"ja_JP\">";
                                    _xmltv_channels += service->stringForKey(KEY_NAME)->cString();
                                    _xmltv_channels += "</display-name>\r\n";

                                    _xmltv_channels += "  </channel>\r\n";

                                    str_iptv_m3u8_local += "#EXTINF:-1 tvg-id=\"";
                                    str_iptv_m3u8_local += channel_name;
                                    str_iptv_m3u8_local += "\" tvg-logo=\"logo_";
                                    str_iptv_m3u8_local += channel_name;
                                    str_iptv_m3u8_local += "\" group-title=\"";
                                    str_iptv_m3u8_local += _tuners[*idx]->name();
                                    str_iptv_m3u8_local += "\", ";
                                    str_iptv_m3u8_local += service->stringForKey(KEY_NAME)->cString();
                                    str_iptv_m3u8_local += "\r\n";
                                    str_iptv_m3u8_local += "udp://0.0.0.0:";
                                    str_iptv_m3u8_local += port_str;
                                    str_iptv_m3u8_local += "\r\n";

                                    str_iptv_m3u8_remote += "#EXTINF:-1 tvg-id=\"";
                                    str_iptv_m3u8_remote += channel_name;
                                    str_iptv_m3u8_remote += "\" tvg-logo=\"logo_";
                                    str_iptv_m3u8_remote += channel_name;
                                    str_iptv_m3u8_remote += "\" group-title=\"";
                                    str_iptv_m3u8_remote += _tuners[*idx]->name();
                                    str_iptv_m3u8_remote += "\", ";
                                    str_iptv_m3u8_remote += service->stringForKey(KEY_NAME)->cString();
                                    str_iptv_m3u8_remote += "\r\n";
#if 0
                                    str_iptv_m3u8_remote += "http://";
                                    str_iptv_m3u8_remote += _props->stringForKey(KEY_HTTP_HOST)->cString();
                                    str_iptv_m3u8_remote += ":";
                                    str_iptv_m3u8_remote += http_port_str;
                                    str_iptv_m3u8_remote += "/";
                                    str_iptv_m3u8_remote += channel_service_id;
                                    str_iptv_m3u8_remote += "/streaming.m3u8\r\n";
#else
//                                    str_iptv_m3u8_remote += "rtsp://";
                                    str_iptv_m3u8_remote += "sip://";
                                    str_iptv_m3u8_remote += _props->stringForKey(KEY_HTTP_HOST)->cString();
                                    str_iptv_m3u8_remote += ":";
                                    str_iptv_m3u8_remote += http_port_str;
                                    str_iptv_m3u8_remote += "/";
                                    str_iptv_m3u8_remote += channel_service_id;
                                    str_iptv_m3u8_remote += "/streaming.sdp\r\n";
#endif
                                }
                            }
                        }
                    }
                }
                break;
            }
            ++(*idx);
        }
        ++(*idx);
        type = (type == Tuner::Type::ISDB_T) ? Tuner::Type::ISDB_S : Tuner::Type::ISDB_T;
    }

    RELEASE(_iptv_m3u8_local);
    _iptv_m3u8_local  = String::alloc()->initWithUTF8String(str_iptv_m3u8_local.c_str());
    RELEASE(_iptv_m3u8_remote);
    _iptv_m3u8_remote = String::alloc()->initWithUTF8String(str_iptv_m3u8_remote.c_str());

    // 過去の番組データは削除
    removePastEPGs();

    // 周期タイマ起動
    _timer_periodic = Timer::alloc()->initWithTimeInterval(1.0, this, (void *)CMD_PERIODIC, true);
    _timer_periodic->fire();

    // 周期タイマ２起動
    _timer_periodic_2 = Timer::alloc()->initWithTimeInterval(1.0, this, (void *)CMD_PERIODIC_2, true);
    _timer_periodic_2->fire();

    // lock
    RaymLock(this);

    // 初期化済みに設定
    _initialized = true;

    // unlock
    RaymUnlock(this);

    DebugLog0("initialize done.");

    return (_tunerCount > 0);
}

int Controller::start()
{
    // ログファイル数設定
    //   設定以前にログ出力しないこと
    Raym::LOG_NUM_MAX = 8;

#if 0
    AutoreleasePool *epg_pool = AutoreleasePool::alloc()->init();

    class EPGTest : public Raym::TimerDelegate
    {
    public:
        Analyzer *  _an;
        Timer *     _timer;

        EPGTest(Analyzer *an)
        {
            _an = an;

            _timer = Timer::alloc()->initWithTimeInterval(3.0, this, NULL, false);
            _timer->fire();
        }

        ~EPGTest()
        {
            RELEASE(_timer);
        }

        void timerExpired(Raym::Timer *timer, void *userInfo)
        {
            AutoreleasePool *pool = AutoreleasePool::alloc()->init();
            Data *data = Data::dataWithContentsOfFile("./mini.ts");
            if (data != NULL)
            {
                DebugLog0("open ok");
                _an->put((uint8_t *)data->bytes(), data->length());
            }
            else
            {
                DebugLog0("open ng");
            }
            pool->release();
        }
    };

    Analyzer *an = Analyzer::alloc()->init();
    EPGTest test(an);

    Array *collected = an->collectEPGs(60);
    if (collected != NULL)
    {
        DebugLog0("collected: %d", collected->count());

        Dictionary *dict = Dictionary::dictionaryWithCapacity(0);
        dict->setObject(collected, "Array");
        if (dict->writeToFile("./hoge.plist", false))
        {
            DebugLog0("write ng.");
        }

        for (uint i = 0; i < collected->count(); ++i)
        {
            Dictionary *epg = (Dictionary *)collected->objectAtIndex(i);
            DebugLog0("EPG: %s", epg->toString().c_str());
        }
    }

    an->release();
    epg_pool->release();
    return 0;
#endif

#if 1
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    if (hProcess != NULL)
    {
        if (SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS) == FALSE)
        {
            DebugLog0("SetPriorityClass failed.");
        }
        CloseHandle(hProcess);
    }
#endif

#ifdef RAYM_MEMORY_CHECK
    DebugLog0("");
    DebugLog0("global_raym_count_ = %d", Raym::global_raym_count_);
#endif

    //
    AutoreleasePool *pool = AutoreleasePool::alloc()->init();

    // メンバ初期化
    _system_path        = NULL;
    _props_path         = NULL;
    _props              = NULL;
    _status_path        = NULL;
    _status             = NULL;
    _epgs_path          = NULL;
    _epgs               = NULL;
    _store_path         = NULL;
    _reservations       = NULL;
    _reservations_path  = NULL;
    _timer_restart      = NULL;
    _timer_periodic     = NULL;
    _timer_periodic_2   = NULL;
    _timer_epg_s        = NULL;
    _timer_epg_t        = NULL;
    _multi2_dll         = NULL;
    _streaming_ctrls    = NULL;

    _idle_count         = 0;
    _initialized        = false;
    _reservation_seq_id = 0;
    _cancel_epg_collect_s = false;
    _cancel_epg_collect_t = false;

    _iptv_m3u8_local    = NULL;
    _iptv_m3u8_remote   = NULL; 

    _tunerCount = 0;
    for (int i = 0; i < ry0::device::MAX_TUNERS; ++i)
    {
        _tuners[i] = NULL;
    }

    DebugLog0("");
    DebugLog0("------------------------------------------------------------------------");
    DebugLog0("iPTd ver %s (rev.%d)", VERSION, REVISION);
    DebugLog0("initialize...");

    int result = 0;

    // 初期化
    while (true)
    {
        bool updated = false;

        // システムパス設定
        _system_path = String::stringWithFormat("%s", GetExecutePath());
        if (_system_path == NULL)
        {
            DebugLog0("error: GetExecutePath()");
            result = -1;
            break;
        }
        _system_path = _system_path->stringByReplacingOccurrencesOfString("iPTd.exe", "");
        _system_path->retain();
        DebugLog2("_system_path: %s", _system_path->cString());

        // プロパティファイルのパス設定
        _props_path = String::alloc()->initWithFormat("%s%s.iptd.plist", _system_path->cString(), PLIST_PREFIX);
        if (_props_path == NULL)
        {
            DebugLog0("error: set property file path.");
            result = -1;
            break;
        }

        // プロパティの読み込み
        _props = Dictionary::alloc()->initWithContentsOfFile(_props_path);
        if (_props == NULL)
        {
            DebugLog1("property file: %s (created)", _props_path->cString());
            _props = Dictionary::alloc()->initWithCapacity(0);
        }
        else
        {
            DebugLog1("property file: %s", _props_path->cString());
        }

        // ステータスファイルのパス設定
        _status_path = String::alloc()->initWithFormat("%s%s.iptd.status.plist", _system_path->cString(), PLIST_PREFIX);
        if (_status_path == NULL)
        {
            DebugLog0("error: set status file path.");
            result = -1;
            break;
        }

        // ステータスの読み込み
        _status = Dictionary::alloc()->initWithContentsOfFile(_status_path);
        if (_status == NULL)
        {
            DebugLog1("status file: %s (created)", _status_path->cString());
            _status = Dictionary::alloc()->initWithCapacity(0);
        }
        else
        {
            DebugLog1("status file: %s", _status_path->cString());
        }

        // システム名
        if (_props->stringForKey(KEY_NAME) == NULL)
        {
            _props->setString(DEF_NAME, KEY_NAME);
            updated = true;
        }

        // ホスト名
        if (_props->stringForKey(KEY_HOSTNAME) == NULL)
        {
            _props->setString(DEF_HOSTNAME, KEY_HOSTNAME);
            updated = true;
        }

        // multi2デコード可否判定: multi2.dll が使えるか確認する
        _multi2_dll = ::LoadLibrary(L"multi2.dll");
        while (_multi2_dll != NULL)
        {
            // b25: 関数アドレス取得
            ARIB_STD_B25 *(*func_b25)();
            func_b25 = reinterpret_cast<ARIB_STD_B25 *(*)()>(::GetProcAddress(_multi2_dll, "create_arib_std_b25"));
            if (func_b25 == NULL)
            {
                // 関数アドレス取得NG
                FreeLibrary(_multi2_dll);
                _multi2_dll = NULL;
                break;
            }

            // b25: インスタンス生成
            ARIB_STD_B25 * _b25;
            _b25 = func_b25();
            if (_b25 == NULL)
            {
                // インスタンス生成NG
                FreeLibrary(_multi2_dll);
                _multi2_dll = NULL;
                break;
            }
            _b25->release(_b25);

            // bcas: 関数アドレス取得
            B_CAS_CARD *(*func_bcas)();
            func_bcas = reinterpret_cast<B_CAS_CARD *(*)()>(::GetProcAddress(_multi2_dll, "create_b_cas_card"));
            if (func_bcas == NULL)
            {
                // 関数アドレス取得NG
                FreeLibrary(_multi2_dll);
                _multi2_dll = NULL;
                break;
            }

            // bcas: インスタンス生成
            B_CAS_CARD * _bcas;
            _bcas = func_bcas();
            if (_bcas == NULL)
            {
                // インスタンス生成NG
                FreeLibrary(_multi2_dll);
                _multi2_dll = NULL;
                break;
            }
            if (_bcas->init(_bcas) != 0)
            {
                // bcas初期化NG
                FreeLibrary(_multi2_dll);
                _multi2_dll = NULL;
                break;
            }
            _bcas->release(_bcas);

            // デコード可
            break;
        }

        // HTTP ポート
        if (_props->integerForKey(KEY_HTTP_PORT) == 0)
        {
            _props->setInteger(DEF_HTTP_PORT, KEY_HTTP_PORT);
            updated = true;
        }

        if (_props->stringForKey(KEY_HTTP_HOST) == NULL)
        {
            char hostname[NI_MAXHOST];
            if (gethostname(hostname, sizeof(hostname)) == 0)
            {
                addrinfo ai;
                ai.ai_family = PF_INET;
                ai.ai_flags = AI_CANONNAME;
                addrinfo *p = NULL;
                if (getaddrinfo(hostname, 0, &ai, &p) == 0)
                {
                    char hostaddr[40];
                    if (getnameinfo(p->ai_addr, (int)p->ai_addrlen, hostaddr, sizeof(hostaddr), 0, 0, NI_NUMERICHOST) == 0)
                    {
                        _props->setString(hostaddr, KEY_HTTP_HOST);
                        updated = true;
                    }
                }
            }
        }

        // 休止までの時間
        if ((_props->integerForKey(KEY_SUSPEND_TIME) < DEF_SUSPEND_TIME) ||
            (_props->integerForKey(KEY_SUSPEND_TIME) >= _props->integerForKey(KEY_FORCED_SUSPEND_TIME)))
        {
            _props->setInteger(DEF_SUSPEND_TIME, KEY_SUSPEND_TIME);
            updated = true;
        }

        // 強制休止までの時間
        if ((_props->integerForKey(KEY_FORCED_SUSPEND_TIME) == 0) ||
            (_props->integerForKey(KEY_FORCED_SUSPEND_TIME) <= _props->integerForKey(KEY_SUSPEND_TIME)))
        {
            _props->setInteger(DEF_FORCED_SUSPEND_TIME, KEY_FORCED_SUSPEND_TIME);
            updated = true;
        }

        // UDPポートの開始番号
        if (_props->integerForKey(KEY_BEGIN_UDP_PORT) == 0)
        {
            _props->setInteger(DEF_BEGIN_UDP_PORT, KEY_BEGIN_UDP_PORT);
            updated = true;
        }

        // EPGを収集する時間
        if ((_props->stringForKey(KEY_COLLECT_EPG_TIME) == NULL) ||
            !_props->stringForKey(KEY_COLLECT_EPG_TIME)->isMatch(String::stringWithUTF8String("^\\d\\d:\\d\\d:\\d\\d$")))
        {
            _props->setString(DEF_COLLECT_EPG_TIME, KEY_COLLECT_EPG_TIME);
            updated = true;
        }

        // 休止状態抑止アプリリスト
        if (_props->arrayForKey(KEY_DO_NOT_IN_SUSPEND) == NULL)
        {
            Array *apps = Array::arrayWithCapacity(0);
            apps->addObject(String::stringWithUTF8String("PIXELACORPORATION.*DtvView\\.exe$"));
            apps->addObject(String::stringWithUTF8String("Common7\\\\IDE\\\\devenv\\.exe$"));
            apps->addObject(String::stringWithUTF8String("Kodi\\\\Kodi\\.exe$"));
            _props->setObject(apps, KEY_DO_NOT_IN_SUSPEND);
            updated = true;
        }

        // 録画データ格納先の確認
        _store_path = _props->stringForKey(KEY_STORE_PATH);
        if (_store_path ==  NULL)
        {
            // プロパティに未設定の場合
            //   <Public Directory>/Videos を設定
            const char *public_dir = GetPublicDirectory();
            if (public_dir == NULL)
            {
                DebugLog0("error: GetPublicDirectory().");
                result = -1;
                break;
            }
            _store_path = String::alloc()->initWithFormat("%s\\Videos", public_dir);
            _props->setString(_store_path, KEY_STORE_PATH);

            // 更新フラグ
            updated = true;
        }
        else
        {
            _store_path->retain();
        }

        // 実際にディレクトリが存在しているか確認
        FileManager *fm = FileManager::defaultManager();
        bool isDir = false;
        if (!fm->fileExistsAtPath(_store_path, &isDir))
        {
            isDir = false;
        }
        if (!isDir)
        {
            DebugLog0("error: \"%s\" is not exists.", _store_path->cString());
            result = -1;
            break;
        }

        // HLS Preset
        if (_props->dictionaryForKey(KEY_PRESETS) == NULL)
        {
            Dictionary *presets = Dictionary::dictionaryWithCapacity(0);
            _props->setObject(presets, KEY_PRESETS);

// とりあえずこのまま
// プリセットは、あとで実装。。。            
            Array *preset;

            // "default"
            preset = STR_ARRAY("-vcodec",         "libx264",
                              "-b:v",            "768k",
                              "-s",              "640x360",
                              "-acodec",         "libfaac",
                              "-b:a",            "96k",
                              "-ar",             "44100",
                              "-flags",          "+loop-global_header",
                              "-map",            "0",
                              "-bsf",            "h264_mp4toannexb",
                              "-f",              "segment",
                              "-segment_format", "mpegts",
                              "-segment_time",   "10",
                              NULL);
            presets->setObject(preset, KEY_DEFAULT);

            updated = true;
        }

        // キャッシュ(HLSの一時ファイル格納先)パス
        if (_props->stringForKey(KEY_CACHE_PATH) == NULL)
        {
            _props->setString(_store_path->stringByAppendingPathComponent("Cache"), KEY_CACHE_PATH);
            _mkdir(_props->stringForKey(KEY_CACHE_PATH)->cString());
            updated = true;
        }
        isDir = false;
        if (!fm->fileExistsAtPath(_props->stringForKey(KEY_CACHE_PATH), &isDir))
        {
            isDir = false;
        }
        if (!isDir)
        {
            DebugLog0("error: \"%s\" is not exists.", _props->stringForKey(KEY_CACHE_PATH)->cString());
            result = -1;
            break;
        }

        if (_props->boolForKey(KEY_SHUTDOWN))
        {
            if (_props->stringForKey(KEY_POWER_MANAGER) == NULL)
            {
                _props->setBool(false, KEY_SHUTDOWN);
                updated = true;
            }
        }

        // プロパティファイルを保存
        if (updated)
        {
            DebugLog0("props updated.");
            if (!_props->writeToFile(_props_path, true))
            {
                DebugLog0("Can't write property file.");
                result = -1;
                break;
            }
        }

        // プロパティの確認
        DebugLog0("  Name                : %s", _props->stringForKey(KEY_NAME)->cString());
        DebugLog0("  Decode Enabled      : %s", (_multi2_dll != NULL) ? "true" : "false");
        DebugLog0("  HTTP Port           : %d", _props->integerForKey(KEY_HTTP_PORT));
        DebugLog0("  Suspend Time        : %d min", _props->integerForKey(KEY_SUSPEND_TIME));
        DebugLog0("  Forced Suspend Time : %d min", _props->integerForKey(KEY_FORCED_SUSPEND_TIME));
        if (_props->boolForKey(KEY_SHUTDOWN))
        {
            DebugLog0("  Power Manager       : %s", _props->stringForKey(KEY_POWER_MANAGER)->cString());
        }
        Array *apps = _props->arrayForKey(KEY_DO_NOT_IN_SUSPEND);
        if (apps != NULL)
        {
            DebugLog1("  Do not in suspend   :");
            for (uint i = 0; i < apps->count(); ++i)
            {
                DebugLog1("    RegExp[%02d]        : %s", i, ((String *)apps->objectAtIndex(i))->cString());
            }
        }

        // 番組データファイルのパス設定
        _epgs_path = String::alloc()->initWithFormat("%s%s.iptd.epgs.plist", _system_path->cString(), PLIST_PREFIX);
        if (_epgs_path == NULL)
        {
            DebugLog0("error: set epgs file path.");
            result = -1;
            break;
        }

        // 番組データの読み込み
        _epgs = Dictionary::alloc()->initWithContentsOfFile(_epgs_path);
        if (_epgs == NULL)
        {
            DebugLog1("epgs file: %s (created)", _epgs_path->cString());
            _epgs = Dictionary::alloc()->initWithCapacity(0);
        }
        else
        {
            DebugLog1("epgs file: %s", _epgs_path->cString());
        }

        // 予約データファイルのパス設定
        _reservations_path = String::alloc()->initWithFormat("%s%s.iptd.reservations.plist", _system_path->cString(), PLIST_PREFIX);
        if (_reservations_path == NULL)
        {
            DebugLog0("error: set reservations file path.");
            result = -1;
            break;
        }

        // 予約データの読み込み
        _reservations = Dictionary::alloc()->initWithContentsOfFile(_reservations_path);
        if (_reservations == NULL)
        {
            DebugLog1("reservations file: %s (created)", _reservations_path->cString());
            _reservations = Dictionary::alloc()->initWithCapacity(0);
        }
        else
        {
            DebugLog1("reservations file: %s", _reservations_path->cString());

            // 予約情報シーケンスID
            _reservation_seq_id = _reservations->integerForKey(KEY_EPG_LAST_RESV_ID);
        }

        // ストリーミング制御情報格納用
        _streaming_ctrls = Dictionary::alloc()->initWithCapacity(0);

        // httpdのルートパス
        String *rootPath = _system_path->stringByAppendingPathComponent("iptd_html");
        if (!fm->fileExistsAtPath(rootPath, &isDir))
        {
            isDir = false;
        }
        if (!isDir)
        {
            DebugLog0("error: \"%s\" is not exists.", rootPath->cString());
            result = -1;
            break;
        }

        // httpd開始
        int port = _props->integerForKey(KEY_HTTP_PORT);
        _httpd = NET::HTTPDaemon::alloc()->initWithPort(port, 10);
        _httpd->setRootPath(rootPath);
        _httpd->setDelegate(this);
        if (!_httpd->start())
        {
            DebugLog0("Can't start httpd.");
            result = -1;
            break;
        }

        // 再開タイマ起動
        _timer_restart = Timer::alloc()->initWithTimeInterval(1.0, this, (void *)CMD_RESTART, false);
        _timer_restart->fire();

        break;
    }

    // 初期化エラーがなければイベント待ちへ
    if (result == 0)
    {
        result = Application::start();

        // httpd終了待ち
        _httpd->stop();
    }

    RaymLock(this);
    _cancel_epg_collect_s = true;
    _cancel_epg_collect_t = true;
    RaymUnlock(this);

    // タイマ停止
    if ((_timer_restart != NULL) && _timer_restart->valid())
    {
        _timer_restart->invalidate();
    }
    if ((_timer_epg_s != NULL) && _timer_epg_s->valid())
    {
        _timer_epg_s->invalidate();
    }
    if ((_timer_epg_t != NULL) && _timer_epg_t->valid())
    {
        _timer_epg_t->invalidate();
    }
    if ((_timer_periodic != NULL) && _timer_periodic->valid())
    {
        _timer_periodic->invalidate();
    }
    if ((_timer_periodic_2 != NULL) && _timer_periodic_2->valid())
    {
        _timer_periodic_2->invalidate();
    }

    // チューナ解放
    for (int i = 0; i < _tunerCount; ++i)
    {
        if (_tuners[i] != NULL)
        {
            delete _tuners[i];
            _tuners[i] = NULL;
        }
    }

    // スケジュールリセット
    resetWakeSchedule();

    // 解放
    RELEASE(_timer_restart);
    RELEASE(_timer_epg_s);
    RELEASE(_timer_epg_t);
    RELEASE(_timer_periodic);
    RELEASE(_timer_periodic_2);
    RELEASE(_streaming_ctrls);
    RELEASE(_httpd);
    RELEASE(_system_path);
    RELEASE(_props_path);
    RELEASE(_props);
    RELEASE(_status_path);
    RELEASE(_status);
    RELEASE(_epgs_path);
    RELEASE(_epgs);
    RELEASE(_store_path);
    RELEASE(_reservations_path);
    RELEASE(_reservations);
    RELEASE(_iptv_m3u8_local);
    RELEASE(_iptv_m3u8_remote);

    if (_multi2_dll != NULL)
    {
        FreeLibrary(_multi2_dll);
    }

    pool->release();

    // 終了
    DebugLog0("finished.");

#ifdef RAYM_MEMORY_CHECK
    DebugLog0("global_raym_count_ = %d", Raym::global_raym_count_);
#endif

    return result;
}

#ifndef _WIN32
#pragma mark '
#pragma mark ------- コンストラクタ／デストラクタ -------
#endif

Controller::Controller()
{
}

Controller::~Controller()
{
}

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

#ifndef _WIN32
#pragma mark '
#pragma mark ------- その他 -------
#endif

//
// HH:MM:SS 形式の文字列から time_t に変換
// 現在時刻が指定文字列の時刻以前の場合、当日の時刻。指定文字列の時刻よりも過ぎている場合、翌日の時刻を返す。
//
void Controller::getTimeWithString(String *str, time_t *time_var)
{
    if ((str != NULL) && str->isMatch(String::stringWithUTF8String("^\\d\\d:\\d\\d:\\d\\d$")) && (time_var != NULL))
    {
        // 時：分：秒 を int型に分解
        std::string time_str = str->cString();
        int hour = atoi(time_str.substr(0, 2).c_str());
        int min  = atoi(time_str.substr(3, 2).c_str());
        int sec  = atoi(time_str.substr(6, 2).c_str());
        DebugLog2("%02d:%02d:%02d", hour, min, sec);

        // 現在時刻取得
        time_t now = time(NULL);
        TM now_tm;
        if (localtime_s(&now_tm, &now) == 0)
        {
            int now_sec = now_tm.tm_hour * 3600 + now_tm.tm_min * 60 + now_tm.tm_sec;
            int col_sec = hour * 3600 + min * 3600 + sec;
            if (now_sec > col_sec)
            {
                ++now_tm.tm_mday;
            }
            now_tm.tm_hour = hour;
            now_tm.tm_min  = min;
            now_tm.tm_sec  = sec;

            *time_var = mktime(&now_tm);
        }
    }
}

void Controller::getTimeWithEPG(Dictionary *epg, time_t *start, time_t *end)
{
    if ((epg == NULL) || (start == NULL) || (end == NULL))
    {
        return;
    }
    String *date = epg->stringForKey(KEY_EPG_DATE);
    String *st   = epg->stringForKey(KEY_EPG_START);
    String *ed   = epg->stringForKey(KEY_EPG_END);
    if ((date == NULL) || (st == NULL) || (ed == NULL))
    {
        return;
    }

    std::string dateStr = date->cString();
    std::string stStr = st->cString();
    std::string edStr = ed->cString();
    TM tm_start;
    tm_start.tm_year = atoi(dateStr.substr(0, 4).c_str()) - 1900;
    tm_start.tm_mon  = atoi(dateStr.substr(5, 2).c_str()) - 1;
    tm_start.tm_mday = atoi(dateStr.substr(8, 2).c_str());
    tm_start.tm_hour = atoi(stStr.substr(0, 2).c_str());
    tm_start.tm_min  = atoi(stStr.substr(3, 2).c_str());
    tm_start.tm_sec  = atoi(stStr.substr(6, 2).c_str());

    TM tm_end;
    tm_end.tm_year = atoi(dateStr.substr(0, 4).c_str()) - 1900;
    tm_end.tm_mon  = atoi(dateStr.substr(5, 2).c_str()) - 1;
    tm_end.tm_mday = atoi(dateStr.substr(8, 2).c_str());
    tm_end.tm_hour = atoi(edStr.substr(0, 2).c_str());
    tm_end.tm_min  = atoi(edStr.substr(3, 2).c_str());
    tm_end.tm_sec  = atoi(edStr.substr(6, 2).c_str());
    if (stStr > edStr)
    {
        tm_end.tm_mday += 1;
    }
    *start = mktime(&tm_start);
    *end = mktime(&tm_end);
}

} // iPTd
} // ry0

