#-*- coding:utf-8 -*-

#  Copyright © 2009-2017  B. Clausius <barcc@gmx.de>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.


import os
import pickle
from contextlib import suppress

from .model import empty_model
from . import cubestate
from .moves import MoveQueue


class GameState:
    def __init__(self):
        self.mark_before = False
        self.initial_state = cubestate.CubeState(empty_model)
        self.initial_state.set_solved()
        self.current_state = self.initial_state.copy()
        self.move_sequence = MoveQueue()
        
    def copy(self):
        game = GameState()
        game.mark_before = self.mark_before
        game.initial_state = self.initial_state.copy()
        game.current_state = self.current_state.copy()
        game.move_sequence = self.move_sequence.copy()
        return game
        
    def set_state(self, model, blocks, moves, position):
        self.initial_state = cubestate.CubeState(model)
        try:
            self.initial_state.parse_block(blocks)
        except ValueError as e:
            print('Invalid block list:', *e.args)
            print('  start with solved game')
            self.initial_state.set_solved()
        self.current_state = self.initial_state.copy()
        self.move_sequence = MoveQueue()
        if moves.startswith('native:'):
            moves = moves.split(':', 1)[1].lstrip()
        else:
            moves = ''
        pos, unused_cpos = self.move_sequence.parse(moves, position, model)
        self.goto_next_pos(pos)
        
    def get_state(self):
        model = self.initial_state.model
        blocks = self.initial_state.format_block()
        moves, position = self.move_sequence.format(model)
        return model, blocks, 'native: ' + moves, position
        
    @staticmethod
    def _load_game_file(filename, mtype):
        data = None
        if filename is not None:
            try:
                with suppress(FileNotFoundError):
                    with open(filename, 'rb') as datafile:
                        data = pickle.load(datafile)
            except Exception as e:
                print('error while loading game {0}: {1.__class__.__name__}: {1}'.format(mtype, e))
        if type(data) is not dict:
            data = {}
            
        data_games = None
        with suppress(KeyError):
            data_games = data['games']
        if type(data_games) is not dict:
            data_games = {}
            data['games'] = data_games
            
        data_type = None
        with suppress(KeyError):
            data_type = data_games[mtype]
        if type(data_type) is not dict:
            data_type = {}
            data_games[mtype] = data_type
        return data, data_type
        
    def load(self, filename, model):
        unused_data, data_type = self._load_game_file(filename, model.type)
        blocks, moves, position = data_type.get(model.size, ('solved', '', 0))
        self.set_state(model, blocks, moves, position)
        
    def save(self, filename):
        if filename is None:
            return
        model, blocks, moves, position = self.get_state()
        if model is empty_model:
            return
        basename = os.path.basename(filename)
        dirname = os.path.dirname(filename)
        if dirname and not os.path.exists(dirname):
            os.makedirs(dirname)
        data, data_type = self._load_game_file(filename, model.type)
        data_type[model.size] = blocks, moves, position
        import tempfile
        try:
            with tempfile.NamedTemporaryFile('wb', prefix=basename+'-temp-', dir=dirname, delete=False) as datafile:
                pickle.dump(data, datafile, protocol=4)
            os.rename(datafile.name, filename)
        except Exception as e:
            print('error while saving game {0}: {1.__class__.__name__}: {1}'.format(model.type, e))
            
    @classmethod
    def migrate_1to2(self, filename, mtype, msize, blocks, moves, position):
        data, data_type = self._load_game_file(filename, mtype)
        data_type[msize] = blocks, moves, position
        try:
            with open(filename, 'wb') as datafile:
                pickle.dump(data, datafile, protocol=4)
        except Exception as e:
            print('error during migrantion of {0}: {1.__class__.__name__}: {1}'.format(mtype, e))
            
    def random(self, count=-1):
        self.initial_state.set_solved()
        self.initial_state.random(count)
        self.current_state = self.initial_state.copy()
        self.move_sequence.reset()
        
    ### Funktions to control the cube for use in callbacks and plugins
    
    def step_back(self):
        '''One step back in the sequence of moves'''
        if self.move_sequence.at_start():
            return None
        self.mark_before = self.move_sequence.is_mark_current()
        self.move_sequence.retard()
        move = self.move_sequence.current().inverted()
        self.current_state.rotate_slice(move)
        return move
        
    def step_next(self):
        '''One step forward in the sequence of moves'''
        move = self.move_sequence.current()
        if move:
            self.mark_before = self.move_sequence.is_mark_current()
            self.current_state.rotate_slice(move)
            self.move_sequence.advance()
        return move
        
    def set_next(self, move):
        '''Make one new move.'''
        self.move_sequence.push_current(move)
        
    def goto_prev_mark(self):
        if self.move_sequence.at_start():
            return False
        while True:
            self.move_sequence.retard()
            move = self.move_sequence.current().inverted()
            self.current_state.rotate_slice(move)
            if self.move_sequence.is_mark_current():
                break
        return True
        
    def goto_start(self):
        self.current_state = self.initial_state.copy()
        self.move_sequence.rewind_start()
            
    def goto_next_mark(self):
        move = self.move_sequence.current()
        while move:
            self.current_state.rotate_slice(move)
            self.move_sequence.advance()
            if self.move_sequence.is_mark_current():
                break
            move = self.move_sequence.current()
        
    def goto_next_pos(self, pos):
        move = self.move_sequence.current()
        while move:
            if pos == self.move_sequence.current_place:
                break
            self.current_state.rotate_slice(move)
            self.move_sequence.advance()
            move = self.move_sequence.current()
        
    def set_code(self, code, pos):
        self.current_state = self.initial_state.copy()
        self.move_sequence.reset()
        pos, unused_cpos = self.move_sequence.parse(code, pos, self.current_state.model)
        self.goto_next_pos(pos)
        
    def add_moves(self, moves):
        for move in moves:
            self.current_state.rotate_slice(move)
            self.move_sequence.push(move)
            
    def add_code(self, code):
        for res in self.move_sequence.parse_iter(code, len(code), self.current_state.model):
            self.current_state.rotate_slice(res[0])
            
    def set_plugin_mode(self, plugin_mode):
        ''' Set plugin mode
            append:     append moves from the plugin to the game
            replace:    replace initial state and moves
            challenge:  replace initial state and clear moves, set the unsolved flag
        '''
        self.plugin_mode = plugin_mode
        
    def recalc_current_state(self):
        self.current_state = self.initial_state.copy()
        pos = self.move_sequence.current_place
        self.move_sequence.rewind_start()
        self.goto_next_pos(pos)
        
    # game transformations
        
    def set_as_initial_state(self):
        self.initial_state = self.current_state.copy()
        self.move_sequence.truncate_before()
        
    # cube editing
    
    def swap_block(self, blockpos, maxis, mslice, mdir):
        self.initial_state.swap_block(blockpos, maxis, mslice, mdir)
        self.goto_start()
        
    def rotate_block(self, blockpos, rdir):
        self.initial_state.rotate_block(blockpos, rdir)
        self.goto_start()
        

