import unittest
import os
from databaseservice import initialize_database, connect_database, DatabaseService
from dataaccess import User, Room, Exit, Player, ItemUnlockingItem, ExitUnlockingItem

class DatabaseServiceTest(unittest.TestCase):
  """
  These tests talk to an actual database, so they might run a bit slower.
  """
  def setUp(self):
    self.database_file = 'test.db'
    with open('schema.sql') as schema:
      initialize_database(self.database_file, schema)
    self.database_connection = connect_database(self.database_file)
    self.database_cursor = self.database_connection.cursor()
    self.database = DatabaseService(self.database_connection,
        self.database_cursor)

    # Test data
    self.test_user = User('rahulraj', 'fake_hash', 'fake_salt')
    self.second_user = User('second', 'fake_hash2', 'fake_salt2')

    self.test_room = Room('A test room',
        'This room was created for testing')
    self.second_room = Room('A second room',
        'This room was created for more complex tests')
    self.third_room = Room('A third room',
        'This room was created for even more complex tests')

  def test_user_not_initially_in_database(self):
    self.assertFalse( \
        self.database.has_user_with_name(self.test_user.get_username()))

  def test_create_user_and_find(self):
    self.database.add_user(self.test_user)
    self.assertTrue(self.database.has_user_with_name( \
        self.test_user.get_username()))
    user = self.database.find_user_by_name(self.test_user.get_username())
    self.assertEquals(self.test_user.get_hashed_password(),
        user.get_hashed_password())
    self.assertEquals(self.test_user.get_salt(), user.get_salt())

  def test_last_row_id_is_correct(self):
    added_user = self.database.add_user(self.test_user)
    row_id = added_user.get_id()
    self.database_cursor.execute( \
        'select * from users where id=:id', {'id': row_id})
    result_row = self.database_cursor.fetchone()
    self.assertEquals(row_id, result_row[0])
    self.assertTrue(result_row[0] is not None)

  def test_add_room(self):
    self.database.add_room(self.test_room)
    room = self.database.find_room_by_name(self.test_room.get_name())
    self.assertEquals(self.test_room.get_description(), room.get_description())

  def add_rooms_with_exit(self, locked=False):
    first_room = self.database.add_room(self.test_room)
    second_room = self.database.add_room(self.second_room)
    description = "An Exit from first to second"
    exit = Exit(
        name="Exit 1",
        description=description,
        from_room=first_room.get_id(),
        to_room=second_room.get_id(),
        locked=locked)
    added_exit = self.database.add_exit(exit)
    return (first_room, second_room, description, added_exit)

  def test_room_with_exit(self):
    (first_room, _, description, _) = self.add_rooms_with_exit()
    result = self.database.find_exits_from_room(first_room.get_id())
    self.assertEquals(1, len(result))
    self.assertEquals(description, result[0].get_description())

  def test_multiple_exits_from_a_room(self):
    first_room = self.database.add_room(self.test_room)
    second_room = self.database.add_room(self.second_room)
    third_room = self.database.add_room(self.third_room)
    first_description = "An Exit from first to second"
    first_exit = Exit(
        name="Exit 1",
        description=first_description,
        from_room=first_room.get_id(),
        to_room=second_room.get_id(),
        locked=False)
    self.database.add_exit(first_exit)

    second_description = "An Exit from first to third"
    second_exit = Exit(
        name="Exit 2",
        description=second_description,
        from_room=first_room.get_id(),
        to_room=third_room.get_id(),
        locked=False)
    self.database.add_exit(second_exit)

    result = self.database.find_exits_from_room(first_room.get_id())
    self.assertEquals(2, len(result))
    self.assertEquals(first_description, result[0].get_description())
    self.assertEquals(second_description, result[1].get_description())

  def test_add_player(self):
    user_id = 2
    current_room_id = 5
    player = Player(user_id, current_room_id)
    result_player = self.database.add_player(player)
    self.database_cursor.execute(
        'select * from players where id=:id', {'id': result_player.get_id()})
    database_output = self.database_cursor.fetchone()
    self.assertEquals(user_id, database_output[1])
    self.assertEquals(current_room_id, database_output[2])

  def test_no_items_in_a_room_initially(self):
    first_room = self.database.add_room(self.test_room)
    items = self.database.find_unlocked_items_in_room( \
        first_room.get_id())
    self.assertEquals(0, len(items))
    
  def make_item(self, constructor, item_name, room_id, locked):
    return constructor(
        item_name, 'Item description for %s' % (item_name),
        'You used %s' % (item_name), None, room_id, locked, 5)

  def test_find_item_in_a_room(self):
    first_room = self.database.add_room(self.test_room)
    first_item_name = 'First item'
    first_item = self.make_item(ItemUnlockingItem,
        first_item_name, first_room.get_id(), locked=False)
    self.database.add_item_unlocking_item(first_item)
    result = self.database.find_unlocked_items_in_room( \
        first_room.get_id())
    self.assertEquals(1, len(result))
    self.assertEquals(first_item_name, result[0].get_name())

  def test_multiple_items_in_a_room(self):
    first_room = self.database.add_room(self.test_room)
    first_item_name = 'First item'
    first_item = self.make_item(ItemUnlockingItem,
        first_item_name, first_room.get_id(), locked=False) 
    self.database.add_item_unlocking_item(first_item)
    second_item_name = 'Second item'
    second_item = self.make_item(ExitUnlockingItem,
        second_item_name, first_room.get_id(), locked=False)
    self.database.add_exit_unlocking_item(second_item)
    result = self.database.find_unlocked_items_in_room( \
        first_room.get_id()) 
    self.assertEquals(2, len(result))
    self.assertEquals(first_item_name, result[0].get_name())
    self.assertEquals(second_item_name, result[1].get_name())

  def test_finding_items_ignores_locked_items(self):
    first_room = self.database.add_room(self.test_room)
    first_item_name = 'First item'
    first_item = self.make_item(ItemUnlockingItem,
        first_item_name, first_room.get_id(), locked=False) 
    self.database.add_item_unlocking_item(first_item)
    second_item_name = 'Second item'
    second_item = self.make_item(ExitUnlockingItem,
        second_item_name, first_room.get_id(), locked=True)
    self.database.add_exit_unlocking_item(second_item)
    result = self.database.find_unlocked_items_in_room( \
        first_room.get_id()) 
    self.assertEquals(1, len(result))
    self.assertEquals(first_item_name, result[0].get_name())

  def test_find_locked_items_in_room(self):
    first_room = self.database.add_room(self.test_room)
    first_item_name = 'First item'
    first_item = self.make_item(ItemUnlockingItem,
        first_item_name, first_room.get_id(), locked=False) 
    self.database.add_item_unlocking_item(first_item)
    second_item_name = 'Second item'
    second_item = self.make_item(ExitUnlockingItem,
        second_item_name, first_room.get_id(), locked=True)
    self.database.add_exit_unlocking_item(second_item)
    result = self.database.find_locked_items_in_room( \
        first_room.get_id())
    self.assertEquals(1, len(result))
    self.assertEquals(second_item_name, result[0].get_name())

  def test_player_picks_up_items(self):
    user_id = 2
    current_room_id = 5
    player = Player(user_id, current_room_id)
    first_player = self.database.add_player(player)
    make_item1 = self.make_item(ItemUnlockingItem,
        'First item', current_room_id, locked=False) 
    first_item = self.database.add_item_unlocking_item(make_item1)
    make_item2 = self.make_item(ExitUnlockingItem,
        'Second item', current_room_id, locked=False)
    self.database.add_exit_unlocking_item(make_item2)
    self.database.move_item_to_player(first_item.get_id(), first_player.get_id())
    result = self.database.find_items_owned_by_player(first_player.get_id())
    self.assertEquals(1, len(result))
    self.assertEquals(first_item.get_id(), result[0].get_id())

  def test_player_room_occupied(self):
    test_room = self.database.add_room(self.test_room)
    player = Player(created_by_user=1,
        currently_in_room=test_room.get_id())
    test_player = self.database.add_player(player)
    room_result = self.database.find_room_occupied_by_player( \
        test_player.get_id())
    self.assertEquals(test_room.get_id(), room_result.get_id())

  def test_player_in_final_room(self):
    test_room =  self.database.add_room(self.test_room)
    player = Player(created_by_user=1,
        currently_in_room=test_room.get_id())
    test_player = self.database.add_player(player)
    self.assertFalse(self.database.player_in_final_room(test_player.get_id()))

    self.second_room.final_room = True
    second_room = self.database.add_room(self.second_room)
    self.database.move_player(test_player.get_id(), second_room.get_id())
    self.assertTrue(self.database.player_in_final_room(test_player.get_id()))

  def test_move_player(self):
    test_room = self.database.add_room(self.test_room)
    second_room = self.database.add_room(self.second_room)
    player = Player(created_by_user=1,
        currently_in_room=test_room.get_id())
    test_player = self.database.add_player(player)
    self.database.move_player(test_player.get_id(), second_room.get_id())
    result = self.database.find_room_occupied_by_player( \
        test_player.get_id()) 
    self.assertEquals(second_room.get_id(), result.get_id())

  def test_unlock_exit(self):
    (first_room, _, _, exit) = self.add_rooms_with_exit(locked=True)
    self.database.unlock_exit(exit.get_id())
    exit_result = self.database.find_exits_from_room(first_room.get_id())[0]
    self.assertFalse(exit_result.is_locked())

  def test_unlock_item(self):
    (first_room, _, _, _) = self.add_rooms_with_exit()
    exit_item = self.make_item(ExitUnlockingItem,
        'To unlock', first_room.get_id(), locked=True)
    locked_item = self.database.add_exit_unlocking_item(exit_item)
    self.database.unlock_item(locked_item.get_id())
    items = self.database.find_unlocked_items_in_room(first_room.get_id())
    self.assertEquals(1, len(items))
    self.assertEquals(locked_item.get_id(), items[0].get_id())

  def test_delete_item(self):
    room_id = 2
    item = self.make_item(ItemUnlockingItem,
        'First item', room_id, locked=False)
    added_item = self.database.add_item_unlocking_item(item)
    self.database.delete_item(added_item.get_id())
    in_room = self.database.find_unlocked_items_in_room(room_id)
    self.assertEquals(0, len(in_room))

  def tearDown(self):
    os.unlink(self.database_file)

if __name__ == '__main__':
  unittest.main()
