import os
import sgf
import itertools
import numpy as np
from AlphaGo import go
from AlphaGo.go import GameState

# for board location indexing
LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'


def confirm(prompt=None, resp=False):
    """
       prompts for yes or no response from the user. Returns True for yes and
       False for no.
       'resp' should be set to the default value assumed by the caller when
       user simply types ENTER.
       created by:
       http://code.activestate.com/recipes/541096-prompt-the-user-for-confirmation/
    """

    if prompt is None:
        prompt = 'Confirm'

    if resp:
        prompt = '%s [%s]|%s: ' % (prompt, 'y', 'n')
    else:
        prompt = '%s [%s]|%s: ' % (prompt, 'n', 'y')

    while True:
        ans = raw_input(prompt)
        if not ans:
            return resp
        if ans not in ['y', 'Y', 'n', 'N']:
            print 'please enter y or n.'
            continue
        if ans == 'y' or ans == 'Y':
            return True
        if ans == 'n' or ans == 'N':
            return False


def flatten_idx(position, size):
    (x, y) = position
    return x * size + y


def unflatten_idx(idx, size):
    return divmod(idx, size)


def _parse_sgf_move(node_value):
    """
       Given a well-formed move string, return either PASS_MOVE or the (x, y) position
    """

    if node_value == '' or node_value == 'tt':
        return go.PASS
    else:
        # GameState expects (x, y) where x is column and y is row
        col = LETTERS.index(node_value[0].upper())
        row = LETTERS.index(node_value[1].upper())
        return (col, row)


def _sgf_init_gamestate(sgf_root):
    """
       Helper function to set up a GameState object from the root node
       of an SGF file
    """

    props = sgf_root.properties
    s_size = int(props.get('SZ', ['19'])[0])
    s_player = props.get('PL', ['B'])[0]
    # init board with specified size
    gs = GameState(size=s_size)
    # handle 'add black' property
    if 'AB' in props:
        for stone in props['AB']:
            gs.place_handicap_stone(_parse_sgf_move(stone), go.BLACK)
    # handle 'add white' property
    if 'AW' in props:
        for stone in props['AW']:
            gs.place_handicap_stone(_parse_sgf_move(stone), go.WHITE)
    # setup done; set player according to 'PL' property
    gs.set_current_player(go.BLACK if s_player == 'B' else go.WHITE)
    return gs


def sgf_to_gamestate(sgf_string):
    """
       Creates a GameState object from the first game in the given collection
    """

    # Don't Repeat Yourself; parsing handled by sgf_iter_states
    for (gs, move, player) in sgf_iter_states(sgf_string, True):
        pass
    # gs has been updated in-place to the final state by the time
    # sgf_iter_states returns
    return gs


def save_gamestate_to_sgf(gamestate, path, filename, black_player_name='Unknown',
                          white_player_name='Unknown', size=19, komi=7.5, result=None):
    """
       Creates a simplified sgf for viewing playouts or positions
    """

    str_list = []
    # Game info
    str_list.append('(;GM[1]FF[4]CA[UTF-8]')
    str_list.append('SZ[{}]'.format(size))
    str_list.append('KM[{}]'.format(komi))
    str_list.append('PB[{}]'.format(black_player_name))
    str_list.append('PW[{}]'.format(white_player_name))

    if result is not None:
        str_list.append('RE[{}]'.format(result))

    cycle_string = 'BW'
    # Handle handicaps
    if len(gamestate.get_handicaps()) > 0:
        cycle_string = 'WB'
        str_list.append('HA[{}]'.format(len(gamestate.get_handicaps())))
        str_list.append(';AB')
        for handicap in gamestate.get_handicaps():
            str_list.append('[{}{}]'.format(LETTERS[handicap[0]].lower(),
                                            LETTERS[handicap[1]].lower()))
    # Move list
    for move, color in zip(gamestate.get_history(), itertools.cycle(cycle_string)):
        # Move color prefix
        str_list.append(';{}'.format(color))
        # Move coordinates
        if move is go.PASS:
            str_list.append('[tt]')
        else:
            str_list.append('[{}{}]'.format(LETTERS[move[0]].lower(), LETTERS[move[1]].lower()))
    str_list.append(')')
    with open(os.path.join(path, filename), "w") as f:
        f.write(''.join(str_list))


def sgf_iter_states(sgf_string, include_end=True):
    """
       Iterates over (GameState, move, player) tuples in the first game of the given SGF file.

       Ignores variations - only the main line is returned.  The state object is
       modified in-place, so don't try to, for example, keep track of it through
       time

       If include_end is False, the final tuple yielded is the penultimate state,
       but the state will still be left in the final position at the end of
       iteration because 'gs' is modified in-place the state. See sgf_to_gamestate
    """

    collection = sgf.parse(sgf_string)
    game = collection[0]
    gs = _sgf_init_gamestate(game.root)
    if game.rest is not None:
        for node in game.rest:
            props = node.properties
            if 'W' in props:
                move = _parse_sgf_move(props['W'][0])
                player = go.WHITE
            elif 'B' in props:
                move = _parse_sgf_move(props['B'][0])
                player = go.BLACK
            yield (gs, move, player)
            # update state to n+1
            gs.do_move(move, player)
    if include_end:
        yield (gs, go.PASS, None)


def plot_network_output(scores, board, history, out_directory, output_file,
                        should_plot=False, western_column_notation=True):
    try:
        import matplotlib
        import matplotlib.pyplot as plt
        import matplotlib.cm as cm
    except ImportError as e:
        print(
            'Failed to import matplotlib. This is an optional dependency of ' +
            'the RocAlphaGo project, so it is not included in the requirements file. ' +
            'You must install matplotlib yourself to use the plotting functions.')
        raise e

    from distutils.version import StrictVersion
    matplotlib_version = matplotlib.__version__
    if StrictVersion(matplotlib_version) < StrictVersion('1.5.1'):
        print('Your version of matplotlib might not support our use of it')

    # Initial matplotlib setup
    fig, ax = plt.subplots(figsize=(10, 10))
    plt.xlim([0, board.size + 1])
    plt.ylim([0, board.size + 1])

    # Wooden background color
    ax.set_axis_bgcolor('#fec97b')
    plt.gca().invert_yaxis()

    # Setup ticks
    ax.tick_params(axis='both', length=0, width=0)
    # Western notation has the origin at the lower-left
    if western_column_notation:
        plt.xticks(range(1, board.size + 1), range(1, board.size + 1))
        plt.yticks(range(1, board.size + 1), reversed(range(1, board.size + 1)))
    # Traditional has the origin at the upper-left and uses letters minus 'I' along the top
    else:
        ax.xaxis.tick_top()
        plt.xticks(range(1, board.size + 1), [x for x in LETTERS[:board.size + 1] if x != 'I'])
        plt.yticks(range(1, board.size + 1), range(1, board.size + 1))

    # Draw grid
    for i in range(board.size):
        plt.plot([1, board.size], [i + 1, i + 1], lw=1, color='k', zorder=0)
    for i in range(board.size):
        plt.plot([i + 1, i + 1], [1, board.size], lw=1, color='k', zorder=0)

    # Display network heat plots
    reshaped = np.reshape(scores, (board.size, board.size))
    score_x_coords = []
    score_y_coords = []
    score_values = []
    for i in range(board.size):
        for j in range(board.size):
            if reshaped[i][j] * 100 >= 0.1:
                score_x_coords.append(i + 1)
                score_y_coords.append(j + 1)
                score_values.append(reshaped[i][j])
    min_seen = np.amin(scores)
    max_seen = np.amax(scores)
    norm = matplotlib.colors.Normalize(vmin=min_seen, vmax=max_seen)
    coloring = cm.ScalarMappable(norm=norm, cmap=cm.cool).to_rgba(score_values)
    plt.scatter(score_x_coords, score_y_coords, marker='o', s=700,
                c=coloring, edgecolor='k', zorder=1)

    # Display network scores on heat plots
    for i, txt in enumerate(score_values):
        ax.annotate('{0:.1f}'.format(txt * 100), (score_x_coords[i], score_y_coords[i]),
                    color='k', ha='center',
                    va='center', size=10, zorder=3)

    # Display stones already played
    stone_x_coords = []
    stone_y_coords = []
    stone_colors = []
    for i in range(board.size):
        for j in range(board.size):
            if board[i][j] != go.EMPTY:
                stone_x_coords.append(i + 1)
                stone_y_coords.append(j + 1)
                if board[i][j] == go.BLACK:
                    stone_colors.append(plt.to_rgb('black'))
                else:
                    stone_colors.append(plt.to_rgb('white'))
    plt.scatter(stone_x_coords, stone_y_coords, marker='o', edgecolors='k',
                s=700, c=stone_colors, zorder=4)

    # Place red marker on last move if it exists
    if len(history) != 0:
        # If last move was not pass
        if history[-1] != go.PASS:
            last_move = history[-1]
            x_coord = last_move[0] + 1
            y_coord = last_move[1] + 1
            last_move = (x_coord, y_coord)
            plt.scatter(last_move[0], last_move[1], marker='s', color='r',
                        edgecolors='k', s=100, zorder=5)

    if output_file is not None:
        plt.savefig(os.path.join(out_directory, output_file), bbox_inches='tight')
    if should_plot:
        plt.show()
    plt.close()

import os
import sgf
import itertools
import numpy as np
from AlphaGo import go
from AlphaGo.go import GameState

# for board location indexing
LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'


def confirm(prompt=None, resp=False):
    """
       prompts for yes or no response from the user. Returns True for yes and
       False for no.
       'resp' should be set to the default value assumed by the caller when
       user simply types ENTER.
       created by:
       http://code.activestate.com/recipes/541096-prompt-the-user-for-confirmation/
    """

    if prompt is None:
        prompt = 'Confirm'

    if resp:
        prompt = '%s [%s]|%s: ' % (prompt, 'y', 'n')
    else:
        prompt = '%s [%s]|%s: ' % (prompt, 'n', 'y')

    while True:
        ans = raw_input(prompt)
        if not ans:
            return resp
        if ans not in ['y', 'Y', 'n', 'N']:
            print 'please enter y or n.'
            continue
        if ans == 'y' or ans == 'Y':
            return True
        if ans == 'n' or ans == 'N':
            return False


def flatten_idx(position, size):
    (x, y) = position
    return x * size + y


def unflatten_idx(idx, size):
    return divmod(idx, size)


def _parse_sgf_move(node_value):
    """
       Given a well-formed move string, return either PASS_MOVE or the (x, y) position
    """

    if node_value == '' or node_value == 'tt':
        return go.PASS
    else:
        # GameState expects (x, y) where x is column and y is row
        col = LETTERS.index(node_value[0].upper())
        row = LETTERS.index(node_value[1].upper())
        return (col, row)


def _sgf_init_gamestate(sgf_root):
    """
       Helper function to set up a GameState object from the root node
       of an SGF file
    """

    props = sgf_root.properties
    s_size = int(props.get('SZ', ['19'])[0])
    s_player = props.get('PL', ['B'])[0]
    # init board with specified size
    gs = GameState(size=s_size)
    # handle 'add black' property
    if 'AB' in props:
        for stone in props['AB']:
            gs.place_handicap_stone(_parse_sgf_move(stone), go.BLACK)
    # handle 'add white' property
    if 'AW' in props:
        for stone in props['AW']:
            gs.place_handicap_stone(_parse_sgf_move(stone), go.WHITE)
    # setup done; set player according to 'PL' property
    gs.set_current_player(go.BLACK if s_player == 'B' else go.WHITE)
    return gs


def sgf_to_gamestate(sgf_string):
    """
       Creates a GameState object from the first game in the given collection
    """

    # Don't Repeat Yourself; parsing handled by sgf_iter_states
    for (gs, move, player) in sgf_iter_states(sgf_string, True):
        pass
    # gs has been updated in-place to the final state by the time
    # sgf_iter_states returns
    return gs


def save_gamestate_to_sgf(gamestate, path, filename, black_player_name='Unknown',
                          white_player_name='Unknown', size=19, komi=7.5, result=None):
    """
       Creates a simplified sgf for viewing playouts or positions
    """

    str_list = []
    # Game info
    str_list.append('(;GM[1]FF[4]CA[UTF-8]')
    str_list.append('SZ[{}]'.format(size))
    str_list.append('KM[{}]'.format(komi))
    str_list.append('PB[{}]'.format(black_player_name))
    str_list.append('PW[{}]'.format(white_player_name))

    if result is not None:
        str_list.append('RE[{}]'.format(result))

    cycle_string = 'BW'
    # Handle handicaps
    if len(gamestate.get_handicaps()) > 0:
        cycle_string = 'WB'
        str_list.append('HA[{}]'.format(len(gamestate.get_handicaps())))
        str_list.append(';AB')
        for handicap in gamestate.get_handicaps():
            str_list.append('[{}{}]'.format(LETTERS[handicap[0]].lower(),
                                            LETTERS[handicap[1]].lower()))
    # Move list
    for move, color in zip(gamestate.get_history(), itertools.cycle(cycle_string)):
        # Move color prefix
        str_list.append(';{}'.format(color))
        # Move coordinates
        if move is go.PASS:
            str_list.append('[tt]')
        else:
            str_list.append('[{}{}]'.format(LETTERS[move[0]].lower(), LETTERS[move[1]].lower()))
    str_list.append(')')
    with open(os.path.join(path, filename), "w") as f:
        f.write(''.join(str_list))


def sgf_iter_states(sgf_string, include_end=True):
    """
       Iterates over (GameState, move, player) tuples in the first game of the given SGF file.

       Ignores variations - only the main line is returned.  The state object is
       modified in-place, so don't try to, for example, keep track of it through
       time

       If include_end is False, the final tuple yielded is the penultimate state,
       but the state will still be left in the final position at the end of
       iteration because 'gs' is modified in-place the state. See sgf_to_gamestate
    """

    collection = sgf.parse(sgf_string)
    game = collection[0]
    gs = _sgf_init_gamestate(game.root)
    if game.rest is not None:
        for node in game.rest:
            props = node.properties
            if 'W' in props:
                move = _parse_sgf_move(props['W'][0])
                player = go.WHITE
            elif 'B' in props:
                move = _parse_sgf_move(props['B'][0])
                player = go.BLACK
            yield (gs, move, player)
            # update state to n+1
            gs.do_move(move, player)
    if include_end:
        yield (gs, go.PASS, None)


def plot_network_output(scores, board, history, out_directory, output_file,
                        should_plot=False, western_column_notation=True):
    try:
        import matplotlib
        import matplotlib.pyplot as plt
        import matplotlib.cm as cm
    except ImportError as e:
        print(
            'Failed to import matplotlib. This is an optional dependency of ' +
            'the RocAlphaGo project, so it is not included in the requirements file. ' +
            'You must install matplotlib yourself to use the plotting functions.')
        raise e

    from distutils.version import StrictVersion
    matplotlib_version = matplotlib.__version__
    if StrictVersion(matplotlib_version) < StrictVersion('1.5.1'):
        print('Your version of matplotlib might not support our use of it')

    # Initial matplotlib setup
    fig, ax = plt.subplots(figsize=(10, 10))
    plt.xlim([0, board.size + 1])
    plt.ylim([0, board.size + 1])

    # Wooden background color
    ax.set_axis_bgcolor('#fec97b')
    plt.gca().invert_yaxis()

    # Setup ticks
    ax.tick_params(axis='both', length=0, width=0)
    # Western notation has the origin at the lower-left
    if western_column_notation:
        plt.xticks(range(1, board.size + 1), range(1, board.size + 1))
        plt.yticks(range(1, board.size + 1), reversed(range(1, board.size + 1)))
    # Traditional has the origin at the upper-left and uses letters minus 'I' along the top
    else:
        ax.xaxis.tick_top()
        plt.xticks(range(1, board.size + 1), [x for x in LETTERS[:board.size + 1] if x != 'I'])
        plt.yticks(range(1, board.size + 1), range(1, board.size + 1))

    # Draw grid
    for i in range(board.size):
        plt.plot([1, board.size], [i + 1, i + 1], lw=1, color='k', zorder=0)
    for i in range(board.size):
        plt.plot([i + 1, i + 1], [1, board.size], lw=1, color='k', zorder=0)

    # Display network heat plots
    reshaped = np.reshape(scores, (board.size, board.size))
    score_x_coords = []
    score_y_coords = []
    score_values = []
    for i in range(board.size):
        for j in range(board.size):
            if reshaped[i][j] * 100 >= 0.1:
                score_x_coords.append(i + 1)
                score_y_coords.append(j + 1)
                score_values.append(reshaped[i][j])
    min_seen = np.amin(scores)
    max_seen = np.amax(scores)
    norm = matplotlib.colors.Normalize(vmin=min_seen, vmax=max_seen)
    coloring = cm.ScalarMappable(norm=norm, cmap=cm.cool).to_rgba(score_values)
    plt.scatter(score_x_coords, score_y_coords, marker='o', s=700,
                c=coloring, edgecolor='k', zorder=1)

    # Display network scores on heat plots
    for i, txt in enumerate(score_values):
        ax.annotate('{0:.1f}'.format(txt * 100), (score_x_coords[i], score_y_coords[i]),
                    color='k', ha='center',
                    va='center', size=10, zorder=3)

    # Display stones already played
    stone_x_coords = []
    stone_y_coords = []
    stone_colors = []
    for i in range(board.size):
        for j in range(board.size):
            if board[i][j] != go.EMPTY:
                stone_x_coords.append(i + 1)
                stone_y_coords.append(j + 1)
                if board[i][j] == go.BLACK:
                    stone_colors.append(plt.to_rgb('black'))
                else:
                    stone_colors.append(plt.to_rgb('white'))
    plt.scatter(stone_x_coords, stone_y_coords, marker='o', edgecolors='k',
                s=700, c=stone_colors, zorder=4)

    # Place red marker on last move if it exists
    if len(history) != 0:
        # If last move was not pass
        if history[-1] != go.PASS:
            last_move = history[-1]
            x_coord = last_move[0] + 1
            y_coord = last_move[1] + 1
            last_move = (x_coord, y_coord)
            plt.scatter(last_move[0], last_move[1], marker='s', color='r',
                        edgecolors='k', s=100, zorder=5)

    if output_file is not None:
        plt.savefig(os.path.join(out_directory, output_file), bbox_inches='tight')
    if should_plot:
        plt.show()
    plt.close()

# for board location indexing prompts for yes or no response from the user. Returns True for yes and        False for no.        'resp' should be set to the default value assumed by the caller when        user simply types ENTER.        created by:        http://code.activestate.com/recipes/541096-prompt-the-user-for-confirmation/ Given a well-formed move string, return either PASS_MOVE or the (x, y) position # GameState expects (x, y) where x is column and y is row Helper function to set up a GameState object from the root node        of an SGF file # init board with specified size # handle 'add black' property # handle 'add white' property # setup done; set player according to 'PL' property Creates a GameState object from the first game in the given collection # Don't Repeat Yourself; parsing handled by sgf_iter_states # gs has been updated in-place to the final state by the time # sgf_iter_states returns Creates a simplified sgf for viewing playouts or positions # Game info # Handle handicaps # Move list # Move color prefix # Move coordinates Iterates over (GameState, move, player) tuples in the first game of the given SGF file.         Ignores variations - only the main line is returned.  The state object is        modified in-place, so don't try to, for example, keep track of it through        time         If include_end is False, the final tuple yielded is the penultimate state,        but the state will still be left in the final position at the end of        iteration because 'gs' is modified in-place the state. See sgf_to_gamestate # update state to n+1 # Initial matplotlib setup # Wooden background color # Setup ticks # Western notation has the origin at the lower-left # Traditional has the origin at the upper-left and uses letters minus 'I' along the top # Draw grid # Display network heat plots # Display network scores on heat plots # Display stones already played # Place red marker on last move if it exists # If last move was not pass