#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
# ---------------------------------------------------------------------------##
#
# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
# Copyright (C) 2003 Mt. Hood Playing Card Co.
# Copyright (C) 2005-2009 Skomoroh
#
# 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 sys
import traceback

import configobj

import pysollib.settings
from pysollib.mfxutil import print_err
from pysollib.mygettext import _
from pysollib.pysoltk import TOOLBAR_BUTTONS, TOOLKIT
from pysollib.resource import CSI

import six

import validate

# ************************************************************************
# * Options
# ************************************************************************


configspec = '''
[general]
player = string
confirm = boolean
update_player_stats = boolean
autofaceup = boolean
autodrop = boolean
autodeal = boolean
quickplay = boolean
shuffle = boolean
undo = boolean
bookmarks = boolean
hint = boolean
highlight_piles = boolean
highlight_cards = boolean
highlight_samerank = boolean
highlight_not_matching = boolean
mahjongg_show_removed = boolean
mahjongg_create_solvable = integer(0, 2)
shisen_show_hint = boolean
shisen_show_matching = boolean
animations = integer(0, 5)
redeal_animation = boolean
win_animation = boolean
flip_animation = boolean
compact_stacks = boolean
shadow = boolean
shade = boolean
shrink_face_down = boolean
shade_filled_stacks = boolean
demo_logo = boolean
tile_theme = string
default_tile_theme = string
toolbar = integer(0, 4)
toolbar_style = string
toolbar_relief = string
toolbar_compound = string
toolbar_size = integer(0, 1)
statusbar = boolean
statusbar_game_number = boolean
statusbar_stuck = boolean
num_cards = boolean
helpbar = boolean
num_recent_games = integer(10, 100)
last_gameid = integer
game_holded = integer
wm_maximized = boolean
splashscreen = boolean
mouse_type = string
mouse_undo = boolean
negative_bottom = boolean
randomize_place = boolean
save_cardsets = boolean
dragcursor = boolean
save_games_geometry = boolean
game_geometry = int_list(min=2, max=2)
sound = boolean
sound_mode = integer(0, 1)
sound_sample_volume = integer(0, 128)
sound_sample_buffer_size = integer(1, 4)
tabletile_name = string
recent_gameid = int_list
favorite_gameid = int_list
visible_buttons = string_list
translate_game_names = boolean
solver_presets = string_list
solver_show_progress = boolean
solver_max_iterations = integer
solver_iterations_output_step = integer
solver_preset = string
display_win_message = boolean

[sound_samples]
move = boolean
autodrop = boolean
drop = boolean
nomove = boolean
gameperfect = boolean
deal = boolean
gamelost = boolean
autopilotwon = boolean
flip = boolean
undo = boolean
gamefinished = boolean
areyousure = boolean
startdrag = boolean
autoflip = boolean
autopilotlost = boolean
turnwaste = boolean
gamewon = boolean
droppair = boolean
redo = boolean
dealwaste = boolean

[fonts]
sans = list
small = list
fixed = list
canvas_default = list
canvas_small = list
canvas_fixed = list
canvas_large = list

[colors]
piles = string
text = string
table = string
hintarrow = string
cards_1 = string
cards_2 = string
samerank_1 = string
samerank_2 = string
not_matching = string

[timeouts]
highlight_samerank = float(0.2, 9.9)
raise_card = float(0.2, 9.9)
demo = float(0.2, 9.9)
highlight_cards = float(0.2, 9.9)
hint = float(0.2, 9.9)
highlight_piles = float(0.2, 9.9)

[cardsets]
0 = string_list(min=2, max=2)
1 = string_list(min=2, max=2)
2 = string_list(min=2, max=2)
3 = string_list(min=2, max=2)
4 = string_list(min=2, max=2)
5 = string_list(min=2, max=2)
6 = string_list(min=2, max=2)
7 = string_list(min=2, max=2)
8 = string_list(min=2, max=2)
9 = string_list(min=2, max=2)
scale_cards = boolean
scale_x = float
scale_y = float
auto_scale = boolean
preserve_aspect_ratio = boolean
'''.splitlines()


class Options:
    GENERAL_OPTIONS = [
        ('player', 'str'),
        ('confirm', 'bool'),
        ('update_player_stats', 'bool'),
        ('autofaceup', 'bool'),
        ('autodrop', 'bool'),
        ('autodeal', 'bool'),
        ('quickplay', 'bool'),
        ('shuffle', 'bool'),
        ('undo', 'bool'),
        ('bookmarks', 'bool'),
        ('hint', 'bool'),
        ('highlight_piles', 'bool'),
        ('highlight_cards', 'bool'),
        ('highlight_samerank', 'bool'),
        ('highlight_not_matching', 'bool'),
        ('mahjongg_show_removed', 'bool'),
        ('mahjongg_create_solvable', 'int'),
        ('shisen_show_hint', 'bool'),
        ('shisen_show_matching', 'bool'),
        ('animations', 'int'),
        ('redeal_animation', 'bool'),
        ('win_animation', 'bool'),
        ('flip_animation', 'bool'),
        ('compact_stacks', 'bool'),
        ('shadow', 'bool'),
        ('shade', 'bool'),
        ('shrink_face_down', 'bool'),
        ('shade_filled_stacks', 'bool'),
        ('demo_logo', 'bool'),
        ('tile_theme', 'str'),
        ('default_tile_theme', 'str'),
        ('toolbar', 'int'),
        ('toolbar_style', 'str'),
        ('toolbar_relief', 'str'),
        ('toolbar_compound', 'str'),
        ('toolbar_size', 'int'),
        ('statusbar', 'bool'),
        ('statusbar_game_number', 'bool'),
        ('statusbar_stuck', 'bool'),
        ('num_cards', 'bool'),
        ('helpbar', 'bool'),
        ('num_recent_games', 'int'),
        ('last_gameid', 'int'),
        ('game_holded', 'int'),
        ('wm_maximized', 'bool'),
        ('splashscreen', 'bool'),
        ('mouse_type', 'str'),
        ('mouse_undo', 'bool'),
        ('negative_bottom', 'bool'),
        ('randomize_place', 'bool'),
        ('save_cardsets', 'bool'),
        ('dragcursor', 'bool'),
        ('save_games_geometry', 'bool'),
        ('sound', 'bool'),
        ('sound_mode', 'int'),
        ('sound_sample_volume', 'int'),
        ('sound_music_volume', 'int'),
        ('sound_sample_buffer_size', 'int'),
        ('tabletile_name', 'str'),
        ('translate_game_names', 'bool'),
        ('solver_presets', 'list'),
        ('solver_show_progress', 'bool'),
        ('solver_max_iterations', 'int'),
        ('solver_iterations_output_step', 'int'),
        ('solver_preset', 'string'),
        # ('toolbar_vars', 'list'),
        # ('recent_gameid', 'list'),
        # ('favorite_gameid', 'list'),
        ('display_win_message', 'bool'),
        ]

    def __init__(self):
        self._config = None             # configobj.ConfigObj instance
        self._config_encoding = 'utf-8'

        self.version_tuple = pysollib.settings.VERSION_TUPLE  # XXX
        self.saved = 0                  # XXX
        # options menu:
        self.player = _("Unknown")
        self.confirm = True
        self.update_player_stats = True
        self.autofaceup = True
        self.autodrop = False
        self.autodeal = True
        self.quickplay = True
        self.shuffle = True
        self.undo = True
        self.bookmarks = True
        self.hint = True
        self.highlight_piles = True
        self.highlight_cards = True
        self.highlight_samerank = True
        self.highlight_not_matching = True
        self.mahjongg_show_removed = False
        self.mahjongg_create_solvable = 2  # 0 - none, 1 - easy, 2 - hard
        if TOOLKIT == 'kivy':
            self.mahjongg_create_solvable = 1  # 0 - none, 1 - easy, 2 - hard
        self.shisen_show_hint = True
        self.shisen_show_matching = False
        self.animations = 3             # default to Medium
        self.redeal_animation = True
        self.win_animation = True
        if TOOLKIT == 'kivy':
            self.redeal_animation = False
            self.win_animation = False
        self.flip_animation = True
        self.compact_stacks = True
        self.shadow = True
        self.shade = True
        self.shrink_face_down = True
        self.shade_filled_stacks = True
        self.demo_logo = True
        self.tile_theme = 'default'
        self.default_tile_theme = 'default'
        self.toolbar = 1       # 0 == hide, 1,2,3,4 == top, bottom, lef, right
        # self.toolbar_style = 'default'
        if TOOLKIT == 'kivy':
            self.toolbar = 4  # 0 == hide, 1,2,3,4 == top, bottom, lef, right
        self.toolbar_style = 'bluecurve'
        self.toolbar_relief = 'flat'
        self.toolbar_compound = 'none'  # icons only
        self.toolbar_size = 0
        self.toolbar_vars = {}
        for w in TOOLBAR_BUTTONS:
            self.toolbar_vars[w] = True  # show all buttons
        self.statusbar = True
        self.statusbar_game_number = False  # show game number in statusbar
        self.statusbar_stuck = False        # show stuck indicator
        self.num_cards = False
        self.helpbar = False
        self.splashscreen = True
        self.mouse_type = 'drag-n-drop'  # or 'sticky-mouse' or 'point-n-click'
        self.mouse_undo = False         # use mouse for undo/redo
        self.negative_bottom = True
        self.translate_game_names = True
        self.display_win_message = True
        # sound
        self.sound = True
        self.sound_mode = 1
        self.sound_sample_volume = 75
        self.sound_music_volume = 100
        self.sound_sample_buffer_size = 1  # 1 - 4 (1024 - 4096 bytes)
        self.sound_samples = {
            'areyousure': True,
            'autodrop': True,
            'autoflip': True,
            'autopilotlost': True,
            'autopilotwon': True,
            'deal': True,
            'dealwaste': True,
            'droppair': True,
            'drop': True,
            # 'extra': True,
            'flip': True,
            'move': True,
            'nomove': True,
            'redo': True,
            'startdrag': True,
            'turnwaste': True,
            'undo': True,
            'gamefinished': False,
            'gamelost': False,
            'gameperfect': False,
            'gamewon': False,
            }
        # fonts
        self.fonts = {
            "default": None,
            # "default": ("helvetica", 12),
            "sans": ("times",     12),  # for html
            "fixed": ("courier",   12),  # for html & log
            "small": ("helvetica", 12),
            "canvas_default": ("helvetica", 12),
            # "canvas_card": ("helvetica", 12),
            "canvas_fixed": ("courier",   12),
            "canvas_large": ("helvetica", 16),
            "canvas_small": ("helvetica", 10),
            }
        # colors
        self.colors = {
            'table':        '#008200',
            'text':         '#ffffff',
            'piles':        '#ffc000',
            'cards_1':      '#ffc000',
            'cards_2':      '#0000ff',
            'samerank_1':   '#ffc000',
            'samerank_2':   '#0000ff',
            'hintarrow':    '#303030',
            'not_matching': '#ff0000',
            }
        # delays
        self.timeouts = {
            'hint':               1.0,
            'demo':               1.0,
            'raise_card':         1.0,
            'highlight_piles':    1.0,
            'highlight_cards':    1.0,
            'highlight_samerank': 1.0,
            }
        # additional startup information
        self.num_recent_games = 15
        self.recent_gameid = []
        self.favorite_gameid = []
        if TOOLKIT == 'kivy':
            self.favorite_gameid = [2, 7, 8, 19, 140, 116, 152, 176, 181,
                                    194, 207, 706, 721, 756, 903, 5034,
                                    11004, 14405, 14410, 15411, 22225]
        self.last_gameid = 0            # last game played
        self.game_holded = 0            # gameid or 0
        self.wm_maximized = 0
        self.save_games_geometry = False
        # saved games geometry (gameid: (width, height))
        self.games_geometry = {}
        self.game_geometry = (0, 0)  # game geometry before exit
        self.offsets = {}           # cards offsets
        #
        self.randomize_place = False
        self.save_cardsets = True
        self.dragcursor = True
        #
        self.scale_cards = False
        self.scale_x = 1.0
        self.scale_y = 1.0
        self.auto_scale = False
        self.preserve_aspect_ratio = True
        # solver
        self.solver_presets = [
            'none',
            'abra-kadabra',
            'blue-yonder',
            'conspiracy-theory',
            'cookie-monster',
            'cool-jives',
            'crooked-nose',
            'fools-gold',
            'good-intentions',
            'hello-world',
            'john-galt-line',
            'looking-glass',
            'one-big-family',
            'rin-tin-tin',
            'slick-rock',
            'the-last-mohican',
            'video-editing',
            'yellow-brick-road',
            ]
        self.solver_show_progress = True
        self.solver_max_iterations = 100000
        self.solver_iterations_output_step = 100
        self.solver_preset = 'video-editing'

    def setDefaults(self, top=None):
        WIN_SYSTEM = pysollib.settings.WIN_SYSTEM
        # toolbar
        # if WIN_SYSTEM == 'win32':
        #    self.toolbar_style = 'crystal'
        # fonts
        if WIN_SYSTEM == 'win32':
            self.fonts["sans"] = ("times new roman", 12)
            self.fonts["fixed"] = ("courier new", 10)
        elif WIN_SYSTEM == 'x11':
            self.fonts["sans"] = ("helvetica", -12)
        # tile theme
        if WIN_SYSTEM == 'win32':
            self.tile_theme = self.default_tile_theme = 'winnative'
            if sys.getwindowsversion() >= (5, 1):  # xp
                self.tile_theme = 'xpnative'
        elif WIN_SYSTEM == 'x11':
            self.tile_theme = 'clam'
            self.default_tile_theme = 'default'
        elif WIN_SYSTEM == 'aqua':
            self.tile_theme = self.default_tile_theme = 'aqua'
        #
        sw, sh, sd = 0, 0, 8
        if top:
            sw, sh, sd = (top.winfo_screenwidth(),
                          top.winfo_screenheight(),
                          top.winfo_screendepth())
        # bg
        if sd > 8:
            self.tabletile_name = "Nostalgy.gif"  # basename
        else:
            self.tabletile_name = None
        # cardsets
        c = "Standard"
        if sw < 800 or sh < 600:
            c = "2000"
        if TOOLKIT == 'kivy':
            c = "Standard"

        # if sw > 1024 and sh > 768:
        #    c = 'Dondorf'
        self.cardset = {
            # game_type:        (cardset_name, back_file)
            0:                  (c, ""),
            CSI.TYPE_FRENCH:    (c, ""),
            CSI.TYPE_HANAFUDA:  ("Kintengu", ""),
            CSI.TYPE_MAHJONGG:  ("Crystal Mahjongg", ""),
            CSI.TYPE_TAROCK:    ("Vienna 2K", ""),
            CSI.TYPE_HEXADECK:  ("Hex A Deck", ""),
            CSI.TYPE_MUGHAL_GANJIFA: ("Mughal Ganjifa", ""),
            # CSI.TYPE_NAVAGRAHA_GANJIFA: ("Navagraha Ganjifa", ""),
            CSI.TYPE_NAVAGRAHA_GANJIFA: ("Dashavatara Ganjifa", ""),
            CSI.TYPE_DASHAVATARA_GANJIFA: ("Dashavatara Ganjifa", ""),
            CSI.TYPE_TRUMP_ONLY: ("Matrix", ""),
        }

    # not changeable options
    def setConstants(self):
        if 'shuffle' not in self.toolbar_vars:
            # new in v.1.1
            self.toolbar_vars['shuffle'] = True
        if isinstance(self.mahjongg_create_solvable, bool):
            # changed in v.1.1
            self.mahjongg_create_solvable = 2
        pass

    def copy(self):
        opt = Options()
        opt.__dict__.update(self.__dict__)
        opt.setConstants()
        return opt

    def save(self, filename):
        config = self._config

        # general
        for key, t in self.GENERAL_OPTIONS:
            val = getattr(self, key)
            if isinstance(val, str):
                if sys.version_info < (3,):
                    val = six.text_type(val, 'utf-8')
            config['general'][key] = val

        config['general']['recent_gameid'] = self.recent_gameid
        config['general']['favorite_gameid'] = self.favorite_gameid
        visible_buttons = [b for b in self.toolbar_vars
                           if self.toolbar_vars[b]]
        config['general']['visible_buttons'] = visible_buttons
        if 'none' in config['general']['solver_presets']:
            config['general']['solver_presets'].remove('none')

        # sound_samples
        config['sound_samples'] = self.sound_samples

        # fonts
        for key, val in self.fonts.items():
            if key == 'default':
                continue
            if val is None:
                continue
            config['fonts'][key] = val

        # colors
        config['colors'] = self.colors

        # timeouts
        config['timeouts'] = self.timeouts

        # cardsets
        for key, val in self.cardset.items():
            config['cardsets'][str(key)] = val
        for key in ('scale_cards', 'scale_x', 'scale_y',
                    'auto_scale', 'preserve_aspect_ratio'):
            config['cardsets'][key] = getattr(self, key)

        # games_geometry
        config['games_geometry'].clear()
        for key, val in self.games_geometry.items():
            config['games_geometry'][str(key)] = val
        config['general']['game_geometry'] = self.game_geometry

        # offsets
        for key, val in self.offsets.items():
            config['offsets'][key] = val

        config.write()
        # config.write(sys.stdout); print

    def _getOption(self, section, key, t):
        config = self._config
        try:
            if config[section][key] is None:
                # invalid value
                return None
            if t == 'bool':
                val = config[section].as_bool(key)
            elif t == 'int':
                val = config[section].as_int(key)
            elif t == 'float':
                val = config[section].as_float(key)
            elif t == 'list':
                val = config[section][key]
                assert isinstance(val, (list, tuple))
            else:  # str
                val = config[section][key]
        except KeyError:
            val = None
        except Exception:
            print_err('load option error: %s: %s' % (section, key))
            traceback.print_exc()
            val = None
        return val

    def load(self, filename):

        # create ConfigObj instance
        try:
            config = configobj.ConfigObj(filename,
                                         configspec=configspec,
                                         encoding=self._config_encoding)
        except configobj.ParseError:
            traceback.print_exc()
            config = configobj.ConfigObj(configspec=configspec,
                                         encoding=self._config_encoding)
        self._config = config

        # create sections
        for section in (
            'general',
            'sound_samples',
            'fonts',
            'colors',
            'timeouts',
            'cardsets',
            'games_geometry',
            'offsets',
                ):
            if section not in config:
                config[section] = {}

        # add initial comment
        if not os.path.exists(filename):
            config.initial_comment = ['-*- coding: %s -*-' %
                                      self._config_encoding]
            return

        # validation
        vdt = validate.Validator()
        res = config.validate(vdt)
        # from pprint import pprint; pprint(res)
        if res is not True:
            for section, data in res.items():
                if data is True:
                    continue
                for key, value in data.items():
                    if value is False:
                        print_err('config file: validation error: '
                                  'section: "%s", key: "%s"' % (section, key))
                        config[section][key] = None

        # general
        for key, t in self.GENERAL_OPTIONS:
            val = self._getOption('general', key, t)
            if val == 'None':
                setattr(self, key, None)
            elif val is not None:
                setattr(self, key, val)

        pysollib.settings.TRANSLATE_GAME_NAMES = self.translate_game_names

        recent_gameid = self._getOption('general', 'recent_gameid', 'list')
        if recent_gameid is not None:
            try:
                self.recent_gameid = [int(i) for i in recent_gameid]
            except Exception:
                traceback.print_exc()

        favorite_gameid = self._getOption('general', 'favorite_gameid', 'list')
        if favorite_gameid is not None:
            try:
                self.favorite_gameid = [int(i) for i in favorite_gameid]
            except Exception:
                traceback.print_exc()

        visible_buttons = self._getOption('general', 'visible_buttons', 'list')
        if visible_buttons is not None:
            for key in TOOLBAR_BUTTONS:
                self.toolbar_vars[key] = (key in visible_buttons)

        # solver
        solver_presets = self._getOption('general', 'solver_presets', 'list')
        if solver_presets is not None:
            if 'none' not in solver_presets:
                solver_presets.insert(0, 'none')
            self.solver_presets = solver_presets

        # sound_samples
        for key in self.sound_samples:
            val = self._getOption('sound_samples', key, 'bool')
            if val is not None:
                self.sound_samples[key] = val

        # fonts
        for key in self.fonts:
            if key == 'default':
                continue
            val = self._getOption('fonts', key, 'str')
            if val is not None:
                try:
                    val[1] = int(val[1])
                except Exception:
                    traceback.print_exc()
                else:
                    val = tuple(val)
                    self.fonts[key] = val

        # colors
        for key in self.colors:
            val = self._getOption('colors', key, 'str')
            if val is not None:
                self.colors[key] = val

        # timeouts
        for key in self.timeouts:
            val = self._getOption('timeouts', key, 'float')
            if val is not None:
                self.timeouts[key] = val

        # cardsets
        for key in self.cardset:
            val = self._getOption('cardsets', str(key), 'list')
            if val is not None:
                try:
                    self.cardset[int(key)] = val
                except Exception:
                    traceback.print_exc()
        for key, t in (('scale_cards', 'bool'),
                       ('scale_x', 'float'),
                       ('scale_y', 'float'),
                       ('auto_scale', 'bool'),
                       ('preserve_aspect_ratio', 'bool')):
            val = self._getOption('cardsets', key, t)
            if val is not None:
                setattr(self, key, val)

        # games_geometry
        for key, val in config['games_geometry'].items():
            try:
                val = [int(i) for i in val]
                assert len(val) == 2
                self.games_geometry[int(key)] = val
            except Exception:
                traceback.print_exc()
        game_geometry = self._getOption('general', 'game_geometry', 'list')
        if game_geometry is not None:
            try:
                self.game_geometry = tuple(int(i) for i in game_geometry)
            except Exception:
                traceback.print_exc()

        # cards offsets
        for key, val in config['offsets'].items():
            try:
                val = [int(i) for i in val]
                assert len(val) == 2
                self.offsets[key] = val
            except Exception:
                traceback.print_exc()