# chessrecord.py
# Copyright 2008 Roger Marsh
# Licence: See LICENCE (BSD licence)

"""Record definitions for chess game database.

The ...Game... classes differ in the PGN parser used as a superclass of the
...valueGame... class.  These generate different combinations of the available
data structures from the game score for the various display and update uses.
The ...Update classes allow editing of a, possibly incomplete, game score.

"""
from ast import literal_eval
import re

from basesup.api.record import KeyData, Value, ValueText, Record
from basesup.api.constants import DB_SEGMENT_SIZE

from pgn.core.constants import (
    SEVEN_TAG_ROSTER,
    START_RAV,
    END_RAV,
    NON_MOVE,
    TAG_OPENING,
    TAG_DATE,
    SPECIAL_TAG_DATE,
    TAG_WHITE,
    TAG_BLACK,
    IFG_TAG_SYMBOL,
    IFG_TAG_STRING_VALUE,
    )
from pgn.core.parser import (
    PGNDisplay,
    PGNDisplayMoves,
    PGNRepertoireDisplay,
    PGNRepertoireTags,
    PGNRepertoireUpdate,
    PGNTags,
    PGNUpdate,
    PGNAnalysis,
    )

from .partialposition import PartialPosition
from .filespec import (
    POSITIONS_FIELD_DEF,
    SOURCE_FIELD_DEF,
    PIECESQUAREMOVE_FIELD_DEF,
    GAMES_FILE_DEF,
    REPERTOIRE_FILE_DEF,
    OPENING_ERROR_FIELD_DEF,
    PGN_DATE_FIELD_DEF,
    VARIATION_FIELD_DEF,
    ENGINE_FIELD_DEF,
    PARTIALPOSITION_NAME_FIELD_DEF,
    RULE_FIELD_DEF,
    COMMAND_FIELD_DEF,
    )
from .analysis import Analysis
from .selectionrule import SelectionRule
from .engine import Engine

# Normalize player names for index consistency.
# Format defined in the PGN specification is 'surname, forename I.J.' assuming
# no spaces between initials when more than one are adjacent, Real PGN files do
# not always adhere.
# Names like 'surname, I. forename J.' may occur.
# Index values are created by replacing all sequences containing just commas,
# periods, and whitespace, by a single space.
PLAYER_NAME_TAGS = frozenset((TAG_WHITE, TAG_BLACK))
re_normalize_player_name = re.compile('([^,\.\s]+)(?:[,\.\s]*)')


class ChessRecordError(Exception):
    pass


class ChessDBkeyGame(KeyData):
    
    """Primary key of chess game.
    """

    def __eq__(self, other):
        """Return (self == other).  Attributes are compared explicitly."""
        try:
            return self.recno == other.recno
        except:
            return False
    
    def __ne__(self, other):
        """Return (self != other).  Attributes are compared explicitly."""
        try:
            return self.recno != other.recno
        except:
            return True
        

class ChessDBvaluePGN(Value):
    
    """Methods common to all chess PGN data classes.
    """
    
    @staticmethod
    def encode_move_number(key):
        """Return base 256 string for integer with left-end most significant.
        """
        return key.to_bytes(2, byteorder='big')

    def load(self, value):
        """Get game from value."""
        self.get_first_game(literal_eval(value))

    def pack_value(self):
        """Return PGN text for game."""
        return repr(
            ''.join((''.join([''.join(('[',
                                       t.group(IFG_TAG_SYMBOL),
                                       '"',
                                       t.group(IFG_TAG_STRING_VALUE),
                                       '"]'))
                              for t in self.collected_game[0]]),
                     ''.join([t.group() for t in self.collected_game[2]]),
                     ''.join([t for t in self.collected_game[3]]),
                     )))
        

class ChessDBvalueGame(PGNDisplay, ChessDBvaluePGN):
    
    """Chess game data.

    Data is indexed by PGN Seven Tag Roster tags.

    """

    def __init__(self):
        """Extend with game source and move number encoder placeholders."""
        super(ChessDBvalueGame, self).__init__()

    def pack(self):
        """Return PGN text and indexes for game."""
        v = super(ChessDBvalueGame, self).pack()
        index = v[1]
        tags = self.collected_game[1]
        for field in tags:
            if field in PLAYER_NAME_TAGS:

                # PGN specification states colon is used to separate player
                # names in consultation games.
                index[field] = [' '.join(re_normalize_player_name.findall(tf))
                                for tf in tags[field].split(':')]

            elif field in SEVEN_TAG_ROSTER:
                index[field] = [tags[field]]
        if TAG_DATE in tags:
            index[PGN_DATE_FIELD_DEF] = [
                tags[TAG_DATE].replace(*SPECIAL_TAG_DATE)]
        return v


class ChessDBrecordGame(Record):
    
    """Chess game record customised for displaying the game score and tags.
    """

    def __init__(self):
        """Extend with move number encode and decode methods"""
        super(ChessDBrecordGame, self).__init__(
            ChessDBkeyGame,
            ChessDBvalueGame)

    def clone(self):
        """Return copy of ChessDBrecordGame instance.

        The bound method attributes are dealt with explicitly and the rest
        are handled by super(...).clone().  (Hope that DPT CR LF restrictions
        will be removed at which point the bound method attributes will not
        be needed.  Then ChessDBrecordGame.clone() can be deleted.)
        
        """
        # are conditions for deleting this method in place?
        clone = super(ChessDBrecordGame, self).clone()
        return clone

    @staticmethod
    def decode_move_number(skey):
        """Return integer from base 256 string with left-end most significant.
        """
        return int.from_bytes(skey, byteorder='big')
        
    def delete_record(self, database, dbset):
        """Delete record not allowed using ChessDBrecordGame class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError

    def edit_record(self, database, dbset, dbname, newrecord):
        """Edit record not allowed using ChessDBrecordGame class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError
        
    def get_keys(self, datasource=None, partial=None):
        """Return list of (key, value) tuples.
        
        The keys for the secondary databases in a ChessDatabase instance are
        embedded in, or derived from, the PGN string for the game.  All
        except the positions are held in self.value.collected_game[1].
        Multiple position keys can arise becuse all repetitions of a
        position are of interest.  The partial argument is used to select
        the relevant keys.  The position is in partial and the keys will
        differ in the move number.
        
        """
        dbname = datasource.dbname
        if dbname != POSITIONS_FIELD_DEF:
            if dbname == GAMES_FILE_DEF:
                return [(self.key.recno, self.srvalue)]
            elif dbname in self.value.collected_game[1]:
                return [(self.value.collected_game[1][dbname], self.key.pack())]
            else:
                return []
        if partial == None:
            return []
        
        moves = self.value.moves
        gamekey = datasource.dbhome.encode_record_number(self.key.pack())
        rav = 0
        ref = 0
        keys = []
        convert_format = datasource.dbhome.db_compatibility_hack

        p = tuple(partial)
        for mt in moves:
            if mt == START_RAV:
                rav += 1
            elif mt == END_RAV:
                rav -= 1
            elif mt == NON_MOVE:
                pass
            else:
                if mt[-1] == p:
                    record = (partial, None)
                    keys.append(convert_format(record, gamekey))
            ref += 1
        return keys
        
    def put_record(self, database, dbset):
        """Put record not allowed using ChessDBrecordGame class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError


class ChessDBrecordGameText(Record):
    
    """Chess game record customised for processing the game score as text.
    
    Used to export games or repertoires from a database in the 'Import Format',
    see PGN specification 3.1, used to store the games.

    """

    def __init__(self):
        """Extend with move number encode and decode methods"""
        super(ChessDBrecordGameText, self).__init__(
            ChessDBkeyGame,
            ValueText)

    def clone(self):
        """Return copy of ChessDBrecordGameText instance.

        The bound method attributes are dealt with explicitly and the rest
        are handled by super(...).clone().  (Hope that DPT CR LF restrictions
        will be removed at which point the bound method attributes will not
        be needed.  Then ChessDBrecordGameText.clone() can be deleted.)
        
        """
        # are conditions for deleting this method in place?
        clone = super(ChessDBrecordGameText, self).clone()
        return clone

    @staticmethod
    def decode_move_number(skey):
        """Return integer from base 256 string with left-end most significant.
        """
        return int.from_bytes(skey, byteorder='big')
        
    def delete_record(self, database, dbset):
        """Delete record not allowed using ChessDBrecordGamePosition class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError

    def edit_record(self, database, dbset, dbname, newrecord):
        """Edit record not allowed using ChessDBrecordGamePosition class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError
        
    def put_record(self, database, dbset):
        """Put record not allowed using ChessDBrecordGamePosition class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError


class ChessDBvalueGameTags(PGNTags, ChessDBvalueGame):
    
    """Chess game data excluding PGN movetext but including PGN Tags.
    """

    def __init__(self):
        """Extend with game source and move number encoder placeholders."""
        super(ChessDBvalueGameTags, self).__init__()

    def get_field_value(self, fieldname, occurrence=0):
        """Return value of a field occurrence, the first by default.

        Added to support Find and Where classes.

        """
        return self.collected_game[1].get(fieldname, None)

    def get_field_values(self, fieldname):
        """Return tuple of field values for fieldname.

        Added to support Find and Where classes.

        """
        return self.get_field_value(fieldname),


class ChessDBrecordGameTags(Record):
    
    """Chess game record customised for displaying tag information for a game.
    """

    def __init__(self):
        """Extend with move number encode and decode methods"""
        super(ChessDBrecordGameTags, self).__init__(
            ChessDBkeyGame,
            ChessDBvalueGameTags)


class ChessDBrecordGamePosition(Record):
    
    """Chess game record customised for displaying the game score only.
    
    Much of the game structure to be represented in the row display is held
    in the Tkinter.Text object created for display.  Thus the processing of
    the record data is delegated to a PositionScore instance created when
    filling the grid.
    
    """

    def __init__(self):
        """Extend with move number encode and decode methods"""
        super(ChessDBrecordGamePosition, self).__init__(
            ChessDBkeyGame,
            ChessDBvalueGameTags)

    def clone(self):
        """Return copy of ChessDBrecordGamePosition instance.

        The bound method attributes are dealt with explicitly and the rest
        are handled by super(...).clone().  (Hope that DPT CR LF restrictions
        will be removed at which point the bound method attributes will not
        be needed.  Then ChessDBrecordGamePosition.clone() can be deleted.)
        
        """
        # are conditions for deleting this method in place?
        clone = super(ChessDBrecordGamePosition, self).clone()
        return clone

    @staticmethod
    def decode_move_number(skey):
        """Return integer from base 256 string with left-end most significant.
        """
        return int.from_bytes(skey, byteorder='big')
        
    def delete_record(self, database, dbset):
        """Delete record not allowed using ChessDBrecordGamePosition class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError

    def edit_record(self, database, dbset, dbname, newrecord):
        """Edit record not allowed using ChessDBrecordGamePosition class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError
        
    def put_record(self, database, dbset):
        """Put record not allowed using ChessDBrecordGamePosition class.

        Process the game using a ChessDBrecordGameUpdate instance

        """
        raise ChessRecordError


class ChessDBvalueGameUpdate(PGNUpdate, ChessDBvaluePGN):
    
    """Chess game data with position, piece location, and PGN Tag, indexes.

    Currently behaves exactly like ChessDBvalueGameImport but is used in the
    ChessDBrecordGameUpdate class designed to edit or insert a single record
    by typing the game into a widget.

    """

    def __init__(self):
        """Extend with game source and move number encoder placeholders."""
        super(ChessDBvalueGameUpdate, self).__init__()
        self.gamesource = None

    def pack(self):
        """Return PGN text and indexes for game."""
        v = super(ChessDBvalueGameUpdate, self).pack()
        index = v[1]
        cg = self.collected_game
        if self.is_pgn_valid():
            tags = cg[1]
            for field in SEVEN_TAG_ROSTER:
                if field in PLAYER_NAME_TAGS:

                    # PGN specification states colon is used to separate player
                    # names in consultation games.
                    index[field
                          ] = [' '.join(re_normalize_player_name.findall(tf))
                               for tf in tags[field].split(':')]

                else:
                    index[field] = [tags[field]]
            index[POSITIONS_FIELD_DEF] = cg[4]
            index[PIECESQUAREMOVE_FIELD_DEF] = cg[5]
            index[PGN_DATE_FIELD_DEF] = [
                tags[TAG_DATE].replace(*SPECIAL_TAG_DATE)]
        else:
            index[SOURCE_FIELD_DEF] = [self.gamesource]
        return v

    def set_game_source(self, source):
        """Set game source (the PGN file name or '')"""
        self.gamesource = source
    

class ChessDBrecordGameUpdate(Record):
    
    """Chess game record customized for editing database records.

    Used to edit or insert a single record by typing in a widget.

    """

    def __init__(self):
        """Extend with move number encode and decode methods"""
        super(ChessDBrecordGameUpdate, self).__init__(
            ChessDBkeyGame,
            ChessDBvalueGameUpdate)

    def clone(self):
        """Return copy of ChessDBrecordGameUpdate instance.

        The bound method attributes are dealt with explicitly and the rest
        are handled by super(...).clone().  (Hope that DPT CR LF restrictions
        will be removed at which point the bound method attributes will not
        be needed.  Then ChessDBrecordGameUpdate.clone() can be deleted.)
        
        """
        # are conditions for deleting this method in place?
        clone = super(ChessDBrecordGameUpdate, self).clone()
        return clone

    @staticmethod
    def decode_move_number(skey):
        """Return integer from base 256 string with left-end most significant.
        """
        return int.from_bytes(skey, byteorder='big')
        
    def get_keys(self, datasource=None, partial=None):
        """Return list of (key, value) tuples.
        
        The keys for the secondary databases in a ChessDatabase instance are
        embedded in, or derived from, the PGN string for the game.  All
        except the positions are held in self.value.collected_game[1].
        Multiple position keys can arise becuse all repetitions of a
        position are of interest.  The partial argument is used to select
        the relevant keys.  The position is in partial and the keys will
        differ in the move number.
        
        """
        dbname = datasource.dbname
        if dbname != POSITIONS_FIELD_DEF:
            if dbname == GAMES_FILE_DEF:
                return [(self.key.recno, self.srvalue)]
            elif dbname in self.value.collected_game[1]:
                return [(self.value.collected_game[1][dbname], self.key.pack())]
            else:
                return []
        if partial == None:
            return []
        
        moves = self.value.moves
        gamekey = datasource.dbhome.encode_record_number(self.key.pack())
        rav = 0
        ref = 0
        keys = []
        convert_format = datasource.dbhome.db_compatibility_hack

        p = tuple(partial)
        for mt in moves:
            if mt == START_RAV:
                rav += 1
            elif mt == END_RAV:
                rav -= 1
            elif mt == NON_MOVE:
                pass
            else:
                if mt[-1] == p:
                    record = (partial, None)
                    keys.append(convert_format(record, gamekey))
            ref += 1
        return keys
        
    def load_instance(self, database, dbset, dbname, record):
        """Extend to set source for game if necessary."""
        super(ChessDBrecordGameUpdate, self).load_instance(
            database, dbset, dbname, record)
    
        if self.value.callbacktried:
            pass
        elif self.value.callbacktried == None:
            pass
        elif not self.value.callbacktried:
            self.value.set_game_source(record[0])


class ChessDBvalueGameImport(PGNUpdate, ChessDBvaluePGN):
    
    """Chess game data with position, piece location, and PGN Tag, indexes.

    Currently behaves exactly like ChessDBvalueGameUpdate but is used in the
    ChessDBrecordGameImport class designed to import records from PGN files.

    """
    # Implication of original is encode_move_number not supported and load in
    # ChessDBvaluePGN superclass is used.

    def __init__(self):
        """Extend with game source and move number encoder placeholders."""
        super(ChessDBvalueGameImport, self).__init__()
        self.gamesource = None
    
    def pack(self):
        """Return PGN text and indexes for game."""
        v = super(ChessDBvalueGameImport, self).pack()
        index = v[1]
        cg = self.collected_game
        if self.is_pgn_valid():
            tags = cg[1]
            for field in SEVEN_TAG_ROSTER:
                if field in PLAYER_NAME_TAGS:

                    # PGN specification states colon is used to separate player
                    # names in consultation games.
                    index[field
                          ] = [' '.join(re_normalize_player_name.findall(tf))
                               for tf in tags[field].split(':')]

                else:
                    index[field] = [tags[field]]
            index[POSITIONS_FIELD_DEF] = cg[4]
            index[PIECESQUAREMOVE_FIELD_DEF] = cg[5]
            index[PGN_DATE_FIELD_DEF] = [
                tags[TAG_DATE].replace(*SPECIAL_TAG_DATE)]
        else:
            index[SOURCE_FIELD_DEF] = [self.gamesource]
        return v

    def set_game_source(self, source):
        """Set game source (the PGN file name or '')"""
        self.gamesource = source
    

class ChessDBrecordGameImport(Record):
    
    """Chess game record customised for importing games from PGN files.

    Used to import multiple records from a PGN file.

    """

    def __init__(self):
        """Customise Record with chess database import key and value classes"""
        super(ChessDBrecordGameImport, self).__init__(
            ChessDBkeyGame,
            ChessDBvalueGameImport)

    def import_pgn(self, database, source, sourcename, reporter=None):
        """Update database with games read from source."""
        self.set_database(database)
        self.value.set_game_source(sourcename)
        if reporter is not None:
            reporter('Extracting games from ' + sourcename)
        ddup = database.deferred_update_points
        count = 0
        for d in self.value.read_games(
            source,
            housekeepinghook=database.deferred_update_housekeeping):
            self.key.recno = 0
            self.put_record(self.database, GAMES_FILE_DEF)
            if reporter is not None:
                if not count:
                    base = self.key.recno - self.key.recno % DB_SEGMENT_SIZE
                count += 1
                if self.key.recno % DB_SEGMENT_SIZE in ddup:
                    reporter(' '.join(('Game',
                                       str(count),
                                       'stored as record',
                                       str(self.key.recno),
                                       'offset',
                                       str(self.key.recno - base))))
        if reporter is not None:
            reporter('Extraction from ' + sourcename + ' done')
            reporter('', timestamp=False)


class ChessDBkeyPartial(KeyData):
    
    """Primary key of partial position record.
    """


class ChessDBvaluePartial(PartialPosition, Value):
    
    """Partial position data.
    """

    def __init__(self):
        super(ChessDBvaluePartial, self).__init__()

    def __eq__(self, other):
        """Return (self == other).  Attributes are compared explicitly."""
        try:
            if (self.get_name_position_text() !=
                other.get_name_position_text()):
                return False
            else:
                return True
        except:
            return False

    def __ne__(self, other):
        """Return (self != other).  Attributes are compared explicitly."""
        try:
            if (self.get_name_position_text() ==
                other.get_name_position_text()):
                return False
            else:
                return True
        except:
            return True
    
    def load(self, value):
        """Set partial position from value"""
        self.extract_position(literal_eval(value))
        self.process_partial_position()

    def pack_value(self):
        """Return partial position value"""
        return repr(self.get_name_position_text())

    def pack(self):
        """Extend, return partial position record and index data."""
        v = super().pack()
        index = v[1]
        index[PARTIALPOSITION_NAME_FIELD_DEF] = [self.get_name_text()]
        return v
        

class ChessDBrecordPartial(Record):
    
    """Partial position record.
    """

    def __init__(self):
        """Extend as a partial position record."""
        super(ChessDBrecordPartial, self).__init__(
            ChessDBkeyPartial,
            ChessDBvaluePartial)
        
    def get_keys(self, datasource=None, partial=None):
        """Return list of (key, value) tuples.
        
        The partial position name is held in an attribute which is not named
        for the field where it exists in the database.
        
        """
        if datasource.dbname == PARTIALPOSITION_NAME_FIELD_DEF:
            return [(self.value.get_name_text(), self.key.pack())]
        else:
            return super().get_keys(datasource=datasource, partial=partial)
        

# Not quite sure what customization needed yet
class ChessDBvalueRepertoire(PGNRepertoireDisplay, Value):
    
    """Repertoire data using custom non-standard tags in PGN format.
    """

    def __init__(self):
        """Extend with game source and move number encoder placeholders."""
        super(ChessDBvalueRepertoire, self).__init__()

    def load(self, value):
        """Get game from value."""
        self.get_first_game(literal_eval(value))


# Not quite sure what customization needed yet
class ChessDBvalueRepertoireTags(PGNRepertoireTags, ChessDBvalueRepertoire):
    
    """Repertoire data using custom non-standard tags in PGN format.
    """

    def __init__(self):
        """Extend with game source and move number encoder placeholders."""
        super(ChessDBvalueRepertoireTags, self).__init__()

    def load(self, value):
        """Get game from value."""
        self.get_first_game(literal_eval(value))


# Not quite sure what customization needed yet
class ChessDBvalueRepertoireUpdate(PGNRepertoireUpdate, ChessDBvaluePGN):
    
    """Repertoire data using custom non-standard tags in PGN format.
    """

    def __init__(self):
        """Extend with game source and move number encoder placeholders."""
        super(ChessDBvalueRepertoireUpdate, self).__init__()
        self.gamesource = None

    def pack(self):
        """Return PGN text and indexes for game."""
        v = super(ChessDBvalueRepertoireUpdate, self).pack()
        index = v[1]
        tags = self.collected_game[1]
        if self.is_pgn_valid():
            index[TAG_OPENING] = [tags[TAG_OPENING]]
        else:
            index[OPENING_ERROR_FIELD_DEF] = [self.gamesource]
        return v

    def set_game_source(self, source):
        """Set game source (the PGN file name or '')"""
        self.gamesource = source


# Not quite sure what customization needed yet
class ChessDBrecordRepertoire(ChessDBrecordGame):
    
    """Repertoire record customised for exporting repertoire information.
    """

    def __init__(self):
        """Extend with move number encode and decode methods"""
        # Skip the immediate superclass __init__ to fix key and value classes
        super(ChessDBrecordGame, self).__init__(
            ChessDBkeyGame,
            ChessDBvalueRepertoire)


# Not quite sure what customization needed yet
class ChessDBrecordRepertoireTags(ChessDBrecordGameTags):
    
    """Repertoire record customised for displaying repertoire tag information.
    """

    def __init__(self):
        """Extend with move number encode and decode methods"""
        # Skip the immediate superclass __init__ to fix key and value classes
        super(ChessDBrecordGameTags, self).__init__(
            ChessDBkeyGame,
            ChessDBvalueRepertoireTags)
    

# Not quite sure what customization needed yet
class ChessDBrecordRepertoireUpdate(ChessDBrecordGameUpdate):
    
    """Repertoire record customized for editing repertoire records.
    """

    def __init__(self):
        """Extend with move number encode and decode methods"""
        # Skip the immediate superclass __init__ to fix key and value classes
        super(ChessDBrecordGameUpdate, self).__init__(
            ChessDBkeyGame,
            ChessDBvalueRepertoireUpdate)
        
    def get_keys(self, datasource=None, partial=None):
        """Return list of (key, value) tuples.
        
        The keys for the secondary databases in a ChessDatabase instance are
        embedded in, or derived from, the PGN string for the game.  All
        except the positions are held in self.value.collected_game[1].
        Multiple position keys can arise becuse all repetitions of a
        position are of interest.  The partial argument is used to select
        the relevant keys.  The position is in partial and the keys will
        differ in the move number.
        
        """
        dbname = datasource.dbname
        if dbname == REPERTOIRE_FILE_DEF:
            return [(self.key.recno, self.srvalue)]
        elif dbname in self.value.collected_game[1]:
            return [(self.value.collected_game[1][dbname], self.key.pack())]
        else:
            return []
        

class ChessDBvalueAnalysis(Analysis, Value):
    
    """Chess engine analysis data for a position.
    """

    def __init__(self):
        """"""
        super().__init__()

    def pack(self):
        """Extend, return analysis record and index data."""
        v = super().pack()
        index = v[1]
        index[VARIATION_FIELD_DEF] = [self.position]
        index[ENGINE_FIELD_DEF] = [k for k in self.scale]
        return v


class ChessDBrecordAnalysis(Record):
    
    """Chess game record customised for chess engine analysis data.

    No index values are derived from PGN move text, so there is no advantage in
    separate classes for display and update.  The PGN FEN tag provides the only
    PGN related index value used.
    
    """

    def __init__(self):
        """Delegate using ChessDBkeyGame and ChessDBvalueAnalysis classes."""
        super().__init__(KeyData, ChessDBvalueAnalysis)


class ChessDBkeySelection(KeyData):
    
    """Primary key of game selection rule record.
    """


class ChessDBvalueSelection(SelectionRule, Value):
    
    """Game selection rule data.
    """

    def __init__(self):
        super(ChessDBvalueSelection, self).__init__()

    def __eq__(self, other):
        """Return (self == other).  Attributes are compared explicitly."""
        try:
            if (self.get_name_selection_rule_text() !=
                other.get_name_selection_rule_text()):
                return False
            else:
                return True
        except:
            return False

    def __ne__(self, other):
        """Return (self != other).  Attributes are compared explicitly."""
        try:
            if (self.get_name_selection_rule_text() ==
                other.get_name_selection_rule_text()):
                return False
            else:
                return True
        except:
            return True
    
    def load(self, value):
        """Set game selection rule from value"""
        self.extract_selection_rule(literal_eval(value))
        self.process_selection_rule()

    def pack_value(self):
        """Return gameselection rule value"""
        return repr(self.get_name_selection_rule_text())

    def pack(self):
        """Extend, return game selection rule record and index data."""
        v = super().pack()
        index = v[1]
        index[RULE_FIELD_DEF] = [self.get_name_text()]
        return v
        

class ChessDBrecordSelection(Record):
    
    """Game selection rule record.
    """

    def __init__(self):
        """Extend as a game selection rule record."""
        super(ChessDBrecordSelection, self).__init__(
            ChessDBkeySelection,
            ChessDBvalueSelection)
        
    def get_keys(self, datasource=None, partial=None):
        """Return list of (key, value) tuples.
        
        The game selection rule name is held in an attribute which is not named
        for the field where it exists in the database.
        
        """
        if datasource.dbname == RULE_FIELD_DEF:
            return [(self.value.get_name_text(), self.key.pack())]
        else:
            return super().get_keys(datasource=datasource, partial=partial)


class ChessDBkeyEngine(KeyData):
    
    """Primary key of chess engine record.
    """


class ChessDBvalueEngine(Engine, Value):
    
    """Game selection rule data.
    """

    def __init__(self):
        super(ChessDBvalueEngine, self).__init__()

    def pack(self):
        """Extend, return game selection rule record and index data."""
        v = super().pack()
        index = v[1]
        index[COMMAND_FIELD_DEF] = [self.get_name_text()]
        return v
        

class ChessDBrecordEngine(Record):
    
    """Chess engine record.
    """

    def __init__(self):
        """Extend as a game selection rule record."""
        super(ChessDBrecordEngine, self).__init__(
            ChessDBkeyEngine,
            ChessDBvalueEngine)
