<?php
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
session_start();

require_once dirname(__FILE__) . '/../../../../../include/chatengine/chatengine.php';
require_once CHEN_DIR.'/chatroom_class.php';
require_once CHEN_DIR.'/chatuser_class.php';
require_once CHEN_DIR.'/talkconverter.php';
require_once CHEN_DIR.'/transaction.php';
require_once CHEN_DIR.'/transaction/add_room.php';
require_once CHEN_DIR.'/transaction/add_user.php';

//現在はinit.phpへの干渉を最小限にしているため、initを先にロードするとroom_classの依存性が解決できない。
require_once dirname(__FILE__) . '/../../../../../include/init.php';
$INIT_CONF->LoadClass('ROOM_CONF', 'MESSAGE');
require_once JINRO_INC.'/request_class.php';
require_once JINRO_INC.'/user_class.php';
require_once JINRO_INC.'/room_class.php';

/**
 * Test class for ChatRoom.
 * Generated by PHPUnit on 2011-01-20 at 03:06:16.
 */
class ChatRoomTest extends PHPUnit_Framework_TestCase {

  /**
   * @var ChatRoom
   */
  protected $object;
  private $chen;
  private $users = array();

  /**
   * Sets up the fixture, for example, opens a network connection.
   * This method is called before a test is executed.
   */
  protected function setUp() {
    $this->chen = new ChatEngine('localhost', 'jinrou', 'grayran', 'satorituri');
    $this->chen->beginTransaction();
    $add_room = new AddRoomTransaction();
    $this->object = $add_room->_CreateRoom($this->chen, 'testLoadTalk', '発言機能のテスト', 17, '', '');
    $room = $this->object;

    $_time = time();
    $this->addUser('alice', 1, 'アリス', 'female', $_time++);
    $this->addUser('bob', 2, 'ボブ', 'male', $_time++, 'wolf');
    $this->addUser('carol', 3, 'キャロル', 'female', $_time++);
    $this->addUser('dave', 4, 'デーブ', 'male', $_time++, 'mage');
    $this->addUser('eve', 5, 'イブ', 'female', $_time++, 'mad');
    $this->kickUser('carol');
  }

  private function addUser($key, $user_no, $name, $sex, $session_seed, $role = '') {
    $password = $sex == 'male' ? 'john doe' : 'jane doe';
    $this->chen->exec(<<<SQL
INSERT INTO jinrou_users (room, uname, passwd, session_id, ip_address)
VALUES ({$this->object->id}, '{$key}', MD5('{$password}'), MD5({$session_seed}), '127.0.0.1')
SQL
    );
    $uid = intval($this->chen->lastInsertId());
    $this->assertNotSame(false, $uid, "{$name}のユーザー登録に失敗しました。");
    $this->chen->exec(<<<SQL
INSERT INTO jinrou_players (user, user_no, handle_name, profile, sex, role, icon_no)
VALUES ({$uid}, {$user_no}, '{$name}', '名無しさん', 'female', '{$role}', 1)
SQL
    );
    $pid = intval($this->chen->lastInsertId());
    $this->assertNotSame(false, $pid, "{$name}のプレイヤー設定に失敗しました。");
    $this->users[$key] = array(intval($uid), intval($pid), $user_no);
  }

  function kickUser($user) {
    $pid = $this->users[$user][1];
    $this->chen->exec("UPDATE jinrou_players SET live = 'kick' WHERE id = {$pid}");
  }

  /**
   * Tears down the fixture, for example, closes a network connection.
   * This method is called after a test is executed.
   */
  protected function tearDown() {
    if (isset($this->chen)) {
      $this->chen->rollBack();
    }
    unset($this->chen);
  }

  /**
   * @todo Implement test__set().
   */
  public function test__set() {
    // Remove the following lines when you implement this test.
    $this->markTestIncomplete(
            'This test has not been implemented yet.'
    );
  }

  /**
   * @todo Implement test__get().
   */
  public function test__get() {
    // Remove the following lines when you implement this test.
    $this->markTestIncomplete(
            'This test has not been implemented yet.'
    );
  }

  /**
   * @todo Implement testUpdateSessionInfo().
   */
  public function testUpdateSessionInfo() {
    // Remove the following lines when you implement this test.
    $this->markTestIncomplete(
            'This test has not been implemented yet.'
    );
  }

  /**
   * @todo Implement testGetAllPlayers().
   */
  public function testGetAllPlayers() {
    // Remove the following lines when you implement this test.
    $this->markTestIncomplete(
            'This test has not been implemented yet.'
    );
  }

  /**
   * @dataProvider dp_testCompileFilter
   */
  public function testCompileFilter($filters, $expected) {
    foreach ($filters as $filter) {
      list($channel, $type, $method) = $filter;
      $this->object->AddFilter($channel, $type, $method);
    }
    $result = $this->object->_CompileFilter();
    echo 'expected = ', print_r($expected, true), "\n";
    echo 'result = ', print_r($result, true), "\n";
    $this->assertEquals($expected, $result);
  }
  public static function dp_testCompileFilter() {
    return array(
        array(
            array(
                array('_', '*', 'Through'),
            ),
            array(
                'methods' => "    WHEN talk.channel = :channel0 THEN 'Through'\n",
                'bind_methods' => array(':channel0' => '_'),
                'channels' => array('_'),
            )
        ),
        array(
            array(
                array('_', 'objection', 'Claim'),
                array('_', 'system', 'Notify'),
                array('_', '*', 'Through'),
            ),
            array(
                'methods' => "    WHEN talk.channel = :channel0 AND talk.type = :type0 THEN 'Claim'\n    WHEN talk.channel = :channel1 AND talk.type = :type1 THEN 'Notify'\n    WHEN talk.channel = :channel2 THEN 'Through'\n",
                'bind_methods' => array(':channel0' => '_', ':type0' => 'objection', ':channel1' => '_', ':type1' => 'system', ':channel2' => '_'),
                'channels' => array('_'),
            )
        ),
    );
  }

  private function insertTalk($player, $internal_time, $channel, $type, $volume, $sentence) {
    $room = $this->object;
    $player_id = $this->users[$player][1];
    $posted_time = time();
    $insert = <<<SQL
INSERT INTO jinrou_talk (room, player, internal_time, posted_time, channel, type, volume, sentence)
VALUES ({$room->id}, {$player_id}, {$internal_time}, {$posted_time}, '{$channel}', '{$type}', '{$volume}', '{$sentence}')

SQL;
    if (($result = $this->chen->exec($insert)) !== false) {
      return true;
    }
    $this->assertTrue($result, "データの挿入に失敗しました。\n".print_r($insert, true));
  }

  /**
   * @dataProvider dp_testLoadTalk
   */
  public function testLoadTalk($time_from, $time_to, $success, $filters, $expected) {
    $room = $this->object;

    //テスト用発言の挿入
    //TODO:通常会話用の共通チャンネル名が決まっていない(2011-02-08 enogu)
    $this->insertTalk('alice', 0, '_', 'talk', '', '0:開始前');
    $this->insertTalk('bob', 1, '_', 'talk', '', '1:OP,感想戦の仮想時間の進み方は1発言1秒とする。');
    $this->insertTalk('alice', 235959, '_', 'talk', '', '2:OP,感想戦の発言数は暫定的に86,400を上限とする。');
    $this->insertTalk('bob', 10000000, 'self[2]', 'talk', '', '3:一日目昼の発言。OP村オプションがなければ発生はしない。この発言はチャンネルが間違っている。');
    $this->insertTalk('alice', 11000000, 'self[1]', 'talk', '', '4:一日目夜の発言。');
    $this->insertTalk('bob', 11000000, 'wolf', 'howl', '', '5:男は狼なのーよー♪なお、仮想時間が完全に衝突した場合はidの昇順で並べる。');
    $this->insertTalk('alice', 20000000, '_', 'talk', '', '6:二日目昼の発言。');
    $this->insertTalk('bob', 20101500, '_', 'talk', '', '7:二日目昼の発言。');
    $this->insertTalk('alice', 30000000, '_', 'talk', '', '8:二日目夜の発言。この発言は通常チャンネルに対してなされているが、通常はこのようなデータは存在しない。');
    $this->insertTalk('bob', 30115959, 'wolf', 'howl', '', '9:二日目夜の発言。');
    $rows = $this->chen->query('SELECT * FROM jinrou_talk WHERE room = '.$room->id);
    $this->assertEquals(10, $rows->rowCount(), 'テストデータは正しく挿入されていません:'.print_r($this->chen->errorInfo(), true));
    foreach ($filters as $filter) {
      list($channel, $type, $method) = $filter;
      $this->object->AddFilter($channel, $type, $method);
    }

    $this->assertTrue($this->object->PrepareLoad(), 'ステートメントの準備に失敗しました。'.print_r($this->chen->errorInfo(), true));
    $result = $this->object->LoadTalk($time_from, $time_to);
    if ($success) {
      $this->assertTrue($result, 'レコードの取得に失敗しました。'.print_r($this->chen->errorInfo(), true));
      $log = $this->object->log;
      $this->assertEquals(count($expected), count($log), '予期しない数のレコードが取得されました。');
      for ($i = 0; $i < count($expected); $i++) {
        extract($expected[$i], EXTR_PREFIX_ALL, 'ex');
        $pid = $this->users[$ex_1][1];
        extract($log[$i], EXTR_PREFIX_ALL, 'log');
        $this->assertEquals($ex_0, $log_0);
        $this->assertEquals($pid, $log_1);
        $this->assertEquals($ex_2, $log_2);
        $this->assertEquals($ex_3, array_shift(explode(':', $log_3)));
      }
    }
    else {
      $this->assertFalse($result);
    }
  }
  public static function dp_testLoadTalk() {
    global $MESSAGE;
    $filters_normal = array(
      array('_', '*', 'Through'),
      array('wolf', 'howl', 'Howl'),
      array('common', 'whisper', 'Whisper'),
    );
    $filters_play_day = array(
      array('_', '*', 'Through')
    );
    $filters_play_night_alice = array(
      array('_', '*', 'Through'), //このフィルタ条件は夜間でもGMや公開者によって発生する場合がある
      array('self[1]', '*', 'Through'),
      array('wolf', 'howl', 'Howl'),
      array('common', 'whisper', 'Whisper'),
    );
    echo 'dp_testLoadTalk';
    return array(
      array(null, null, true, array(), array(
        array(null, 'alice', '', 0),
        array(null, 'bob', '', 1),
        array(null, 'alice', '', 2),
        array(null, 'bob', '', 3),
        array(null, 'alice', '', 4),
        array(null, 'bob', '', 5),
        array(null, 'alice', '', 6),
        array(null, 'bob', '', 7),
        array(null, 'alice', '', 8),
        array(null, 'bob', '', 9),
      )),
      array(null, null, true, $filters_normal, array(
        array(null, 'alice', '', 0),
        array(null, 'bob', '', 1),
        array(null, 'alice', '', 2),
        array('wolf-howl', 'bob', '', $MESSAGE->wolf_howl),
        array(null, 'alice', '', 6),
        array(null, 'bob', '', 7),
        array(null, 'alice', '', 8),
        array('wolf-howl', 'bob', '', $MESSAGE->wolf_howl),
      )),
      array(null, null, true, $filters_play_day, array(
        array(null, 'alice', '', 0),
        array(null, 'bob', '', 1),
        array(null, 'alice', '', 2),
        array(null, 'alice', '', 6),
        array(null, 'bob', '', 7),
        array(null, 'alice', '', 8),
      )),
      array(null, null, true, $filters_play_night_alice, array(
        array(null, 'alice', '', 0),
        array(null, 'bob', '', 1),
        array(null, 'alice', '', 2),
        array(null, 'alice', '', 4),
        array('wolf-howl', 'bob', '', $MESSAGE->wolf_howl),
        array(null, 'alice', '', 6),
        array(null, 'bob', '', 7),
        array(null, 'alice', '', 8),
        array('wolf-howl', 'bob', '', $MESSAGE->wolf_howl),
      )),
      array(11000000, 11240000, true, $filters_play_night_alice, array(
        array(null, 'alice', '', 4),
        array('wolf-howl', 'bob', '', $MESSAGE->wolf_howl),
      )),
    );
  }

  public function testLoadPlayers() {
    $result = $this->object->LoadPlayers();
    $this->assertTrue($result, 'メソッドの実行に失敗しました:'.print_r($this->chen->errorInfo(), true));
    $this->assertEquals(4, count($this->object->players));
    $this->_testLoadedPlayer('alice');
    $this->_testLoadedPlayer('bob');
    $this->_testLoadedPlayer('dave');
    $this->_testLoadedPlayer('eve');
  }
  private function _testLoadedPlayer($target) {
    list($uid, $pid, $uno) = $this->users[$target];
    $this->assertTrue(isset($this->object->players[$uno]));
    $this->assertEquals($pid, $this->object->players[$uno]->id);
  }
}
