__author__ = 'Alastair Kerr'
# -*- coding: utf-8 -*-

from bson.objectid import ObjectId
import json
import time
from itertools import izip

import deck
import hands
import helpers
import OFCP_AI
import db_backend


def handle_game_logic(game_state, game_id):
    '''
    Take in game state from frontend and game id as inputs - use game id to get record from database
    Compare game state passed to game state from db - validate legitimacy
    Based off who went first and cards placed so far determine next action e.g. deal cards, AI, score board
    Update game state changes and store in database, call AI helper functions to determine optimal play
    Return appropriate response to server e.g. updated game state, cards to be placed, scores array
    '''

    current_milli_time = lambda: int(round(time.time() * 1000))

    # retrieve database for this game
    games = db_backend.get_database_collection()
    state =	games.find_one({'_id': ObjectId(game_id)})

    # find if player acted first this round and how many cards have been placed in order to work out current game position
    count = 0
    playerFirst = state["playerFirst"]
    for i in range(0,13):
        for j in range(0,2):
            if game_state['properties'+str(j+1)]['cards']['items']['position'+str(i+1)] is not None:
                count += 1

    print "\nCurrent game state:\n", game_state, "\nCards placed:", count

    if playerFirst:

        # Special case for player's first turn
        if count == 0:
            cdeck = deck.Deck()     # creates deck
            cards = cdeck.deal_n(5)
            state['deck'] = {}      # initialise deck dic within game state
            state['deck']['cards'] = cdeck.cards
            state['deck']['current-position'] = cdeck.current_position

            state['first5cards'] = cards

            # update game state in database
            update = games.update({
                '_id': ObjectId(game_id)
                }, {
                '$set': state
            })

            return json.dumps(cards)

        # Special case for AI's first turn
        elif count == 5:

            # remove first 5 cards entry from db now that these have been placed on the board
            del state['first5cards']

            # recreate deck from deck info in database
            cdeck = deck.Deck(state['deck']['cards'], state['deck']['current-position'])

            # validate game state
            state = validate_and_update_state(cdeck, count, game_state, state)
            if state == 0:
                return 0  # raise cherrypy 500 error

            # calculate AI's first 5 card placements
            cards = cdeck.deal_n(5)
            OFCP_AI.loop_elapsed = 0

            stime = current_milli_time()

            iterations_timer = 4000   # Sets time in ms to spend simulating games.
            # As iterations increases diverges to optimal solution
            cardPlacementsAI = OFCP_AI.chooseMove(game_state, cards, iterations_timer)

            print "\nTime taken calculating 5 placements:", current_milli_time() - stime, "ms"
            print "Total time spent scoring hands: ", OFCP_AI.loop_elapsed, "ms"

            state = zip_placements_cards(cardPlacementsAI, cards, state)
            if state == 1:
                # Error with AI placement
                return 1

            # deal player's next card
            card = cdeck.deal_one()

            # update game state with deck info
            state['deck']['cards'] = cdeck.cards
            state['deck']['current-position'] = cdeck.current_position

            # records game state in database + remove first5cards key
            state['cardtoplace'] = card
            update = games.update({
                '_id': ObjectId(game_id)
                }, {
                '$set': state
                ,'$unset': {
                'first5cards': 1
            }
            })

            del state['deck'] # don't return deck info to frontend
            del state['_id']
            # state.pop('_id', None)

            return json.dumps(state)

        # general case for every other round after initial 5 placements by player and AI
        elif count < 26:

            cdeck = deck.Deck(state['deck']['cards'], state['deck']['current-position'])

            # validate game state
            state = validate_and_update_state(cdeck, count, game_state, state)
            if state == 0:
                return 0  # raise cherrypy 500 error

            # AI's next move
            card = str(cdeck.deal_one())
            OFCP_AI.loop_elapsed = 0

            stime = current_milli_time()
            iterations_timer = 3000
            AI_placement = OFCP_AI.chooseMove(game_state,card,iterations_timer)

            print "\nTime taken calculating 1 placement:", current_milli_time() - stime, "ms"
            print "Total time spent scoring hands: ", OFCP_AI.loop_elapsed, "ms"

            state = zip_placements_cards([AI_placement], [card], state)
            if state == 1:
                # Error with AI placement
                return 1

            # deal player's next card
            card = cdeck.deal_one()

            # update game state with deck info
            state['deck']['cards'] = cdeck.cards
            state['deck']['current-position'] = cdeck.current_position

            # records game state in database
            state['cardtoplace'] = card
            update = games.update({
                '_id': ObjectId(game_id)
                }, {
                '$set': state
            })

            # print "\nStored game state in database:", state

            del state['deck'] # don't return deck info to frontend
            del state['_id']

            # print "\nReturning state to player:", state

            return json.dumps(state)

        else:
            # game over - validate and score game board

            cdeck = deck.Deck(state['deck']['cards'], state['deck']['current-position'])

            # validate game state
            state = validate_and_update_state(cdeck, count, game_state, state)
            if state == 0:
                return 0 # raise cherrypy 500 error

            # update game state with deck info
            state['deck']['cards'] = cdeck.cards
            state['deck']['current-position'] = cdeck.current_position

            # score game board
            scores_array = helpers.scoring_helper(game_state)

            # store p1's score in database
            state['score'] += helpers.scores_arr_to_int(scores_array)
            print "\nState score recorded as:", state['score'], "!\n"
            scores_array.append([state['score']])
            print "scores_array was", scores_array

            # records game state in database
            del state['cardtoplace']
            update = games.update({
                '_id': ObjectId(game_id)
                }, {
                '$set': state
            })

            OFCP_AI.reset() # reset AI variables/ states

            # scores_array format [ [winnerid, winners_bottom_royalty, losers_bottom_royalty],
            #    [winnerid, winners_middle_royalty, losers_middle_royalty] ,
            #    [winnerid, winners_top_royalty, losers_top_royalty] ],
            #    [p1foulbool, p2foulbool],
            #    [p1_existing_score]

            print '\n   Scores -->', scores_array, '\n'

            return json.dumps(scores_array)

    else:
        if count == 0:
            # AI acts first, but return player's first 5 cards first while AI calculates
            if 'first5cards' not in state:
                cdeck = deck.Deck()     # creates deck
                cards_5_player = cdeck.deal_n(5) # player's first 5 cards
                state['deck'] = {}      # initialise deck dic within game state
                state['deck']['cards'] = cdeck.cards
                state['deck']['current-position'] = cdeck.current_position

                # validate game state
                state = validate_and_update_state(cdeck, count, game_state, state)
                if state == 0:
                    return 0 # raise cherrypy 500 error

                state['first5cards'] = cards_5_player

                # update game state in database
                update = games.update({
                    '_id': ObjectId(game_id)
                    }, {
                    '$set': state
                })

                return json.dumps(cards_5_player)

            else:
                cdeck = deck.Deck(state['deck']['cards'], state['deck']['current-position'])
                cards_5_AI = cdeck.deal_n(5)

                # validate game state
                state = validate_and_update_state(cdeck, count, game_state, state)
                if state == 0:
                    return 0 # raise cherrypy 500 error

                # calculate AI's first 5 card placements
                OFCP_AI.loop_elapsed = 0

                stime = current_milli_time()

                iterations_timer = 4000   # Sets time in ms to spend simulating games.
                # As iterations increases diverges to optimal solution
                cardPlacementsAI = OFCP_AI.chooseMove(game_state,cards_5_AI,iterations_timer)

                print "\nTime taken calculating 5 placements:", current_milli_time() - stime, "ms"
                print "Total time spent scoring hands: ", OFCP_AI.loop_elapsed, "ms"

                state = zip_placements_cards(cardPlacementsAI, cards_5_AI, state)
                if state == 1:
                    # Error with AI placement
                    return 1

                # update game state with deck info
                state['deck']['cards'] = cdeck.cards
                state['deck']['current-position'] = cdeck.current_position

                # records game state in database + remove first5cards key
                update = games.update({
                    '_id': ObjectId(game_id)
                    }, {
                    '$set': state
                })

                del state['deck'] # don't return deck info to frontend
                del state['_id']
                # state.pop('_id', None)

                return json.dumps(state)

        # general case for every other round after initial 5 placements by player and AI
        elif count < 26:

            cdeck = deck.Deck(state['deck']['cards'], state['deck']['current-position'])

            # validate game state
            state = validate_and_update_state(cdeck, count, game_state, state)
            if state == 0:
                return 0 # raise cherrypy 500 error

            # AI's next move
            card = str(cdeck.deal_one())
            OFCP_AI.loop_elapsed = 0

            stime = current_milli_time()
            iterations_timer = 3000
            AI_placement = OFCP_AI.chooseMove(game_state,card,iterations_timer)

            print "\nTime taken calculating 1 placement:", current_milli_time() - stime, "ms"
            print "Total time spent scoring hands: ", OFCP_AI.loop_elapsed, "ms"

            state = zip_placements_cards([AI_placement], [card], state)
            if state == 1:
                # Error with AI placement
                return 1

            # deal player's next card
            card = cdeck.deal_one()

            # update game state with deck info
            state['deck']['cards'] = cdeck.cards
            state['deck']['current-position'] = cdeck.current_position

            # records game state in database
            state['cardtoplace'] = card
            update = games.update({
                '_id': ObjectId(game_id)
                }, {
                '$set': state
            })

            # print "\nStored game state in database:", state

            del state['deck'] # don't return deck info to frontend
            del state['_id']

            # print "\nReturning state to player:", state

            return json.dumps(state)

        else:
            # game over - validate and score game board

            cdeck = deck.Deck(state['deck']['cards'], state['deck']['current-position'])

            # validate game state
            state = validate_and_update_state(cdeck, count, game_state, state)
            if state == 0:
                return 0 # raise cherrypy 500 error

            # update game state with deck info
            state['deck']['cards'] = cdeck.cards
            state['deck']['current-position'] = cdeck.current_position

            # score game board
            scores_array = helpers.scoring_helper(game_state)

            # store p1's score in database
            state['score'] += helpers.scores_arr_to_int(scores_array)
            print "\nState score recorded as:", state['score'], "!\n"
            scores_array.append([state['score']])
            print "scores_array was", scores_array

            # records game state in database
            del state['cardtoplace']
            update = games.update({
                '_id': ObjectId(game_id)
                }, {
                '$set': state
            })

            OFCP_AI.reset()  # reset AI variables/ states

            #    scores_array format [ [winnerid, winners_bottom_royalty, losers_bottom_royalty],
            #    [winnerid, winners_middle_royalty, losers_middle_royalty] ,
            #    [winnerid, winners_top_royalty, losers_top_royalty] ],
            #    [p1foulbool, p2foulbool],
            #    [p1_existing_score]

            print '\n   Scores -->', scores_array, '\n'

            return json.dumps(scores_array)

def validate_and_update_state(cdeck, dealt_count, game_state, state):
    '''
    validate game state. Return updated state with player's placements if valid, else return 0
    '''

    dealt_cards = cdeck.cards[:dealt_count]

    # prune AI's cards from list
    for i in range(1,14):
        t = state['properties2']['cards']['items']['position' + str(i)]
        if t is not None:
            if t in dealt_cards:
                dealt_cards.remove(t)
            else:
                print "One of the Agent's cards wasn't found in dealt_cards!"
                return 0  # will raise cherrypy 500 error in server.py

    if dealt_count == 5 or dealt_count == 10:
        # special case validating first round placements (5 cards rather than 1)
        existing_cards = []
        pending_cards = dealt_cards[:]
    else:
        # general case. Existing cards: all but last (last card placed this round)
        existing_cards = dealt_cards[:-1]
        pending_cards = dealt_cards[-1:]

    print "Existing cards:", existing_cards, "... Pending cards:", pending_cards

    # iterate through each position. If card in existing state ensure same placement. If new make sure dealt card was placed
    for i in range(1,14):
        t = game_state['properties1']['cards']['items']['position' + str(i)]
        if t is not None:
            # check consistency of existing placements from previous round(s)
            if t in existing_cards:
                existing_cards.remove(t)
                # ensure placement is consistent with database records
                if t != state['properties1']['cards']['items']['position' + str(i)]:
                    return 0 # will raise cherrypy 500 error in server.py
            else:
                if t not in pending_cards:
                    return 0 # will raise cherrypy 500 error in server.py
                pending_cards.remove(t)

                # update state with this new placement
                state['properties1']['cards']['items']['position' + str(i)] = t

    # ensure all dealt cards for player have been placed
    if len(pending_cards) > 0:
        return 0 # will raise cherrypy 500 error in server.py

    return state

def zip_placements_cards(placements, card_list, state):
    ''' zip optimal placements -> appropriate cards, and update state dic with each placement '''

    for placement, card in izip(placements, card_list):
        # row 1 (bottom) pos 1-5, row 2 (middle) pos 6-10, row 3 (top) pos 11-13
        if placement == 1:
            start = 1
            end = 5
        elif placement == 2:
            start = 6
            end = 10
            pass
        elif placement == 3:
            start = 11
            end = 13
            pass
        else:
            print "Invalid row returned from OFCP_AI.chooseMove(...):", placements
            return 1

        for i in range(start, end+1):
            if state['properties2']['cards']['items']['position'+str(i)] is None:
                state['properties2']['cards']['items']['position'+str(i)] = card
                break

    return state