## BEGIN_IMPORT
from . moduledata import ModuleData
from . withtraits import PieceSlot, DummyWithTraits
from . trait import Trait
from . traits import BasicTrait, StackTrait, TrailTrait, NoStackTrait
from . element import DummyElement
from . mapelements import AtStart
from . vmod import VMod
from .. common import VerboseGuard
from . withtraits import WithTraits
## END_IMPORT

# ====================================================================
class SaveIO:
    '''Wrapper around a save file 
    
    Save file is
    
        "!VCSK" KEY content
    
    Key is two bytes drawn as a random number in 0-255.  Content is
    two bytes per character.  Content characters are encoded with the
    random key.
    
    Save file (.vsav) content is
    
        "begin_save" ESC
        "\" ESC
        [commands]* ESC
        "PLAYER" name password side ESC
        [map+"BoardPicker" name x y ESC]+
        "SETUP_STACK\t" ESC
        "TURN"+name state ESC
        "end_save"
    
    Commands are
    
        "+/" id "/" body "\"
    
    where body are
    
        "stack" "/" mapName ; x ; y ; ids "\"
        piece_type "/" piece_state   (x and y set here too) "\"
    
    x and y are pixel coordinates (sigh!).  This means we have to know
    
    - the pixel location of a hex
    - the hex coordinates of that hex
    - whether rows and columns are descending
    - if even hexes are higher or not
    
    The two first items _must_ be user supplied (I think).  When we
    add stacks or pieces, we must then get the numerical hex
    coordinates - not what the user specified in the VASSAL editor or
    the like.  Of course, this means opening the module before writing
    the patch.py script.
    
    It seems like every piece is added in a stack.
    
    The id is a numerical value.  Rather big (e.g., 1268518000806). It
    is the current number of miliseconds since epoch, with offset to
    disambiguate.
    
    The ID is the current time, taken from a milisecond clock,
    possibly adjusted up if there is a clash.  This is all managed by
    the GameState class.

    The SETUP_STACK command (which _must_ be followed by a tab),
    cleansup at-start stacks so that we do not have duplicates of the
    pieces on the board.  Note, that this means we must place _all_
    pieces in our save.

    '''
    VCS_HEADER = b'!VCSK'
    VK_ESC     = chr(27)
    DEC_MAP    = {
        # 0-9
        0x30: 0x30,
        0x31: 0x30,
        0x32: 0x30,
        0x33: 0x30,
        0x34: 0x30,
        0x35: 0x30,
        0x36: 0x30,
        0x37: 0x30,
        0x38: 0x30,
        0x39: 0x30,
        # A-F
        0x41: 0x37,
        0x42: 0x37,
        0x43: 0x37,
        0x44: 0x37,
        0x45: 0x37,
        0x46: 0x37,
        # a-f
        0x61: 0x57,
        0x62: 0x57,
        0x63: 0x57,
        0x64: 0x57,
        0x65: 0x57,
        0x66: 0x57
    }
    ENC_MAP = [b'0',b'1',b'2',b'3',b'4',b'5',b'6',b'7',b'8',b'9',
               b'a',b'b',b'c',b'd',b'e',b'f']

    # ----------------------------------------------------------------
    @classmethod
    def decHex(cls,b):
        '''Decode a single char into a number

        If the encoded number is b, then the decoded number is
        
            b - off
    
        where off is an offset that depends on b
    
           off = 0x30   if 0x30 <= b <= 0x39
                 0x37   if 0x41 <= b <= 0x46
                 0x57   if 0x61 <= b <= 0x66
        '''
        return b - cls.DEC_MAP[b]
    
    # --------------------------------------------------------------------
    @classmethod
    def decode(cls,key,first,second):
        return ((cls.decHex(first) << 4 | cls.decHex(second)) ^ key) & 0xFF
                
    # --------------------------------------------------------------------
    @classmethod
    def readByte(cls,inp,key):
        '''Read a single byte of information from input stream
    
        Two characters (c1 and c2) are read from input stream, and the
        decoded byte is then
    
            ((dechex(c1) << 4 | dechex(c2)) ^ key) & 0xFF
        
        Parameters
        ----------
        inp : stream
            Input to read from
        key : int
            Key to decode the input
    
        Returns
        -------
        b : int
            The read byte
        '''
        try:
            pair = inp.read(2)
        except Exception as e:
            from sys import stderr
            print(e,file=stderr)
            return None
    
        if len(pair) < 2:
            return None

        return cls.deode(key, pair[0], pair[1])
        #return ((cls.decHex(pair[0]) << 4 | cls.decHex(pair[1])) ^ key) & 0xFF
    # --------------------------------------------------------------------
    @classmethod
    def readSave(cls,file,alsometa=False):
        '''Read data from save file.  The data is read into lines
        returned as a list.

        '''
        from zipfile import ZipFile
## BEGIN_IMPORT
        from . vsav import VSav
## END_IMPORT

        with VerboseGuard(f'Reading save '
                          f'{file if isinstance(file,str) else file.name}') as vg:
        
            # We open the save file as a zip file 
            with ZipFile(file,'r') as z:
                # open the save file in the archive
                sinf = z.getinfo('savedGame')
                save = z.open('savedGame','r')
                stot = sinf.file_size

                vg(f'Reading {stot} bytes')
                
                # First, we check the header
                head = save.read(len(cls.VCS_HEADER))
                assert head == cls.VCS_HEADER, \
                    f'Read header {head} is not {cls.VCS_HEADER}'
                srd  = len(head)
            
                # Then, read the key
                pair =  save.read(2)
                srd  += 2
                key  =  (cls.decHex(pair[0]) << 4 | cls.decHex(pair[1]))
            
                # Now read content, one byte at a time 
                content = ''
                #old     = int(srd / stot * 100)
                #vg(f'{old:3d}%',flush=True,end='')
                
                raw     = save.read()
                srd     += len(raw)
                vg(f'Read in {len(raw)} bytes of raw content')
                
                vg(f'Decoding data, please be patient...')
                content = ''.join([chr(cls.decode(key,first,second))
                                   for first, second in zip(raw[::2],raw[1::2])])
                
                # while True:
                #     byte =  cls.readByte(save,key)
                #     if byte is None:
                #         break
                #     srd  += 2
                # 
                #     # Convert byte to character 
                #     content += chr(byte)
                # 
                #     now   = int(srd / stot * 100)
                #     if now != old:
                #         old = now
                #         vg(f'\r{vg.i}{now:3d}%',flush=True,end='')
                        
                vg(f'Read {srd} bytes')
            
                lines = content.split(cls.VK_ESC)
            
                if alsometa:
                    savedata = z.read(VSav.SAVE_DATA)
                    moduledata = z.read(VMod.MODULE_DATA)
            
            if not alsometa:
                return key, lines
            
            return key,lines,savedata,moduledata

    # --------------------------------------------------------------------
    @classmethod
    def encode(cls,key,byte):
        '''Encode a single byte with key'''
        b = ord(byte) ^ key
        return cls.ENC_MAP[(b & 0xF0) >> 4], cls.ENC_MAP[b & 0x0F]
        
    # --------------------------------------------------------------------
    @classmethod
    def writeByte(cls,out,byte,key):
        '''Write a single byte

        Parameters
        ----------
        out : IOStream
            Stream to write to
        byte : char
            Single byte to write
        key : int
            Key to encode with (defaults to 0xAA - alternating 0's and 1's)
        '''
        # b    = ord(byte) ^ key
        # pair = cls.ENC_MAP[(b & 0xF0) >> 4], cls.ENC_MAP[b & 0x0F]
        pair = cls.encode(key, byte)
        out.write(pair[0])
        out.write(pair[1])

    # --------------------------------------------------------------------
    @classmethod
    def writeInZip(cls,z,key,lines,filename='savedGame'):
        '''Write a save file in a zip file (VMod)'''
        # Maximum size of chunks to write in one go.  This _must_ be
        # even.  This is to avoid memory problems.
        MAX_CHUNK = 2**24
        
        with VerboseGuard(f'Writing {len(lines)} lines with key {key:x} '
                          f'into {filename}') as vg:
            # open the save file in the archive
            with z.open(filename,'w') as save:
                # Write header
                save.write(cls.VCS_HEADER)
    
                # Split key
                pair = (cls.ENC_MAP[(key & 0xF0) >> 4], \
                        cls.ENC_MAP[(key & 0x0F)])
                save.write(pair[0])
                save.write(pair[1])
    
                # Form content
                content = cls.VK_ESC.join(lines)
                vwr     = len(cls.VCS_HEADER) + 2
                vtot    = 2*len(content) + vwr
                vg(f'Will write {vtot} bytes')

                #old = int(vwr / vtot * 100)
                # vg(f'{vg.i}{old:3d}%',end='',flush=True)

                vg(f'Encoding data, please be patient...')
                # raw = b''.join([c
                #                 for b in content
                #                 for c in cls.encode(key, b)])
                # 
                # vg(f'Writing {len(raw)} raw bytes')

                nc     = len(content)
                chunks = list(range(0,nc,MAX_CHUNK))+[nc+1]
                for s, e in zip(chunks[0:],chunks[1:]):
                    chunk = b''.join([c
                                      for b in content[s:e]
                                      for c in cls.encode(key,b)])
                    vg(f'Write {s} to {e} of {nc}')
                    vwr += save.write(chunk)
                    
                # # Write each character as two
                # for c in content:
                #     cls.writeByte(save, c, key)
                # 
                #     vwr += 2
                #     new =  int(vwr / vtot * 100)
                #     if new != old:
                #         vg(f'\r{vg.i}{new:3d}%',end='',flush=True)
                #         old = new

                vg(f'Wrote {vwr} bytes')
        
    # --------------------------------------------------------------------
    @classmethod
    def writeSave(cls,file,key,lines,savedata=None,moduledata=None):
        '''Write a save file'''
        from zipfile import ZipFile, ZIP_DEFLATED
## BEGIN_IMPORT
        from . vsav import VSav
## END_IMPORT

        # We open the save file as a zip file 
        with ZipFile(file,'w',ZIP_DEFLATED) as z:
            cls.writeInZip(z,key,lines,filename='savedGame')
            
            if savedata is not None:
                z.writestr(VSav.SAVE_DATA,savedata)
                z.writestr(VMod.MODULE_DATA,moduledata)

    # --------------------------------------------------------------------
    @classmethod
    def zeroSave(cls,
                 input,
                 output     = None,
                 player_map = {},
                 passwd_map = {},
                 side_map   = {},
                 newkey     = None,
                 verbose    = False):
        '''Zero or reset player information in a save file.

        Parameters
        ----------
        input : str
            Input file name
        output : str
            Output file name
        player_map : dict
            Mapping from old user name to new user name
        password_map : dict
            Mapping from new user name (whether changed or not) to new
            password.
        side_map : dict
            Mapping from new user name (whether changed or not) to new
            player side.
        newkey : int
            New encoding key
        verbose : bool
            Be verbose
        '''
        from pathlib import Path
        
        key, lines, savemeta, modulemeta = cls.readSave(input,True)
    
        if verbose:
            print(f'Read {len(lines)} lines with the key {key:02x}')

        newlines = []
        for line in lines:
            if not line.startswith('PLAYER'):
                newlines.append(line)
                continue

            tag, passwd, user, side = line.split()
            olduser                 = user
            user                    = player_map.get(user,user)
            passwd                  = passwd_map.get(user,passwd)
            side                    = side_map  .get(user,side)
            # Update the player line
            if user in ['-', 'delete']:
                if verbose:
                    print(f'Removing user {olduser} from save: {line}')
                continue
            
            newlines.append('\t'.join([tag,passwd,user,side]))

        if verbose:
            print('\n'.join([f'{lineno:6d}: {line}'
                             for lineno,line in enumerate(newlines)]))

        outname = output
        key     = key if newkey is None else (newkey & 0xFF)
        if outname is None:
            ipath   = Path(input if isinstance(input,str) else input.name)
            outname = str(ipath.with_stem(ipath.stem+'-new'))

        cls.writeSave(outname, key, newlines, savemeta, modulemeta)
        if verbose:
            print(f'Wrote modified save to {outname}')

    # --------------------------------------------------------------------
    @classmethod
    def patchSave(cls,
                  input,
                  patch_name = 'patch.py',
                  verbose    = False):
        '''Run a patching script on a save or log file

        The patch to run must have the format
        ```
        def patch(lines,
                  key,
                  save_meta,
                  module_meta,
                  verbose)
        ```
        and return the new lines to write. 
              
        Parameters
        ----------
        input : str
            Input file name
        output : str
            Output file name
        patch_name : str
            Name of patch script to execute 
        verbose : bool
            Be verbose
        '''
        from pathlib import Path
        
        key, lines, savemeta, modulemeta = cls.readSave(input,True)
    
        if verbose:
            print(f'Read {len(lines)} lines with the key {key:02x}')

        from importlib.util import spec_from_file_location, module_from_spec
        from pathlib import Path
        from sys import modules

        p = Path(patch_name)

        spec   = spec_from_file_location(p.stem, p.absolute())
        module = module_from_spec(spec)
        spec.loader.exec_module(module)
    
        modules[p.stem] = module

        newlines = module.patch(lines,
                                key,
                                savemeta,
                                modulemeta,
                                verbose)
        
        if newlines is None:
            return

        outname = input
        cls.writeSave(outname, key, newlines, savemeta, modulemeta)
        if verbose:
            print(f'Wrote patched save to {outname}')
            
    # --------------------------------------------------------------------
    @classmethod
    def dumpSave(cls,
                 input,
                 alsometa    = False,
                 linenumbers = False,
                 details     = False,
                 verbose     = False):

        ret  = cls.readSave(input,alsometa)

        key, lines = ret[0], ret[1]
        if details:
            for lineno,line in enumerate(lines):
                if not line.startswith('+'):
                    print(f'{lineno:9d}: {line}')
                    continue

                iden, traits = WithTraits.decodeAdd2(line,verbose)
                print(f'{lineno:9d}: {iden}')
                for trait in traits:
                    trait.print()
            
        else:
            if linenumbers:
                print('\n'.join([f'{lineno:9d}: {line}'
                                 for lineno, line in enumerate(lines)]))
            else:
                print('\n'.join(lines))
        
        if alsometa:
            savemeta, modulemeta = ret[2], ret[3]

            print(savemeta)
            print(modulemeta)

    # --------------------------------------------------------------------
    @classmethod
    def writeScenario(cls,
                      setup,
                      vmod,
                      name,
                      filename,
                      build=None,
                      game=None,
                      menu=None):
        '''Write a scenario

        Setup dictonary has the format
        
            {
                [mapName]: {
                    [boardName]: {
                        [zoneName]: {
                            [places]
                        }
                    }
                }
            }

        where places can be either

            {
                 [location]: [list of piece names]
            }

        or

            {
                [pieceName]: [location]
            }
        
        Parameters
        ----------
        setup : dict
            Dictionary of setup
        vmod : VMod
            Module file representation
        name : str
            Name of the save
        filename : str
            File name to write save to
        build : BuildFile or None
            Build file representation.  If not given, then retrieve it
            from the vmod object.
        game : Game
            Game representation. If not given then retrieved from build
        menu : PredefinedSetup
            If given, then add scenario to the setup menu
        '''
## BEGIN_IMPORT
        from . vsav import VSav
        from . buildfile import BuildFile
## END_IMPORT

        if build is None:
            build = BuildFile(vmod.getBuildFile())

        if game is None:
            game = build.getGame()

        vsav = VSav(build,vmod)
        save = vsav.addSaveFile()
        game = build.getGame()
        maps = game.getMaps()
        for mapName,mapData in setup.items():
            map    = maps.get(mapName,None)
            if not map:
                print(f'No such map "{mapName}" in module')
                continue
                
            picker = map.getBoardPicker()[0]
            if not picker:
                print(f'Map "{mapName}" has no board picker')
                continue

            boards = map.getBoards()
            
            for boardName, boardData in mapData.items():
                board = boards.get(boardName,None)
                if not board:
                    print(f'No such board "{boardName}" in map {mapName}')
                    continue
                                
                zones = board.getZones()
                picker.selectBoard(boardName)

                for zoneName, zoneData in boardData.items():
                    zone = zones.get(zoneName,None)
                    if not zone:
                        print(f'No such zone "{zoneName}" in '
                              f'{mapName}/{boardName}')
                        continue
                    
                    grid = zone.getGrids()[0]

                    save.add(grid, mapName, **zoneData)

        runArgs = { 'filename':    filename,
                    'description': name,
                    'savename':    None }
        if menu:
            menu.addPredefinedSetup(name        = name,
                                    useFile     = True,
                                    file        = filename,
                                    description = name)
            runArgs['filename'] = None
            runArgs['savename'] = filename

        vsav.run(**runArgs)

        
                    
    # --------------------------------------------------------------------
    @classmethod
    def scaleSave(cls,
                  input,
                  output      = None,
                  factor      = 1,
                  alsometa    = False):
## BEGIN_IMPORT
        from . scaler import Scaler
## END_IMPORT

        from pathlib import Path

        content              = SaveIO.readSave(input,alsometa)
        key, lines           = content[0], content[1]
        savemeta, modulemeta = None, None

        if alsometa:
            savemeta, modulemeta = content[2], content[3]

        scaler = Scaler(factor = factor)

        with VerboseGuard(f'Scaling lines by {factor}') as vg:
            for lineno,line in enumerate(lines):
                if not line.startswith('+'):
                    #vg(f'{lineno:7d}: {line}')
                    continue

                iden, traits = WithTraits.decodeAdd2(line,vg)
                #vg(f'{lineno:7d}: Decoded traits {iden}')

                scaler.scaleTraits(traits)

                newl          = WithTraits.encodeAdd(*traits,
                                                     iden=iden,
                                                     verbose=vg)
                lines[lineno] = newl

            
            outname = output
            if outname is None:
                ipath   = Path(input if isinstance(input,str) else
                               input.name)
                outname = str(ipath.with_stem(ipath.stem+'-new'))

            SaveIO.writeSave(outname, key, lines, savemeta, modulemeta)

            vg(f'Write to {outname}')
            
        
        
# ====================================================================
#
# VSave file
#
class SaveFile:
    def __init__(self,game,firstid=None):

        '''Creates a save file to add positions to'''
        from time import time
        self._game     = game
        self._counters = {}
        self._stacks   = {}
        self._pieces   = self._game.getPieces(asdict=True)
        self._baseId   = (int(time()*1000) # - 360000
                          if firstid is None else firstid)
        self._nextId   = 1
        
    def add(self,grid,mapname,**kwargs):
        '''Add pieces to the save.

        Parameters
        ----------
        grid : BaseGrid
            Grid to add pieces to 
        kwargs : dict
            Either a map from piece name to hex position,
            Or a map from hex position to list of pieces
        '''
        for k,v in kwargs.items():
            # print('Add to save',k,v)
            self._add(grid,mapname,k,v)

    def addNoGrid(self,mapName,mapping):
        for k,v in mapping.items():
            # print('Add to save',k,v)
            self._add(None,mapName,k,v)
        

    def _add(self,grid,mapName,k,v):

        '''Add to the save'''
        with VerboseGuard(f'Adding piece(s) to save: {len(k)}') as vg:
            from time import time
            # print(f'Adding {k} -> {v}')
            loc       = None
            piece     = self._pieces.get(k,None)
            pieces    = []
            boardName = (grid.getMap()['mapName']
                         if mapName is None else mapName)
            # print(f'Map name: {mapName}')
            vg(f'Adding to {boardName}')
            if piece is not None:
                vg(f'Key {k} is a piece')
                #print(f'Key is piece: {k}->{piece}')
                pieces.append(piece)
                loc = v
            else:
                vg(f'Key {k} is a location')
                # Key is not a piece name, so a location
                loc = k
                # Convert value to iterable 
                try:
                    iter(v)
                except:
                    v = list(v)
                
                for vv in v:
                    if isinstance(vv,PieceSlot):
                        pieces.append(vv)
                        continue
                    if isinstance(vv,str):
                        piece = self._pieces.get(vv,None)
                        if piece is None:
                            continue
                        pieces.append(piece)
            
            vg(f'Loc: {loc} -> {pieces}')
            if len(pieces) < 1:
                return

            if (mapName,loc) not in self._stacks:
                vg(f'Adding stack {mapName},{loc}')
                coord = grid.getLocation(loc) if grid is not None else loc
                if coord is None:
                    print(f'did not get coordinates from {loc}')
                    return
                self._stacks[(mapName,loc)] = {
                    'x': coord[0],
                    'y': coord[1],
                    'pids': [],
                    'idens': [],
                    'layer': -1}
                    
            place = self._stacks[(mapName,loc)]
            for piece in pieces:
                gpid = piece['gpid']
                self._nextId = max(int(gpid)+1,self._nextId)
                
            for piece in pieces:
                name    = piece['entryName']
                gpid    = piece['gpid']
                counter = self._counters.get((name,gpid),None)
                vg(f'Got counter {counter} for {name},{gpid}')

                if counter is None:
                    if gpid == 0:
                        print(f'making new counter with pid={self._nextId}: '
                              f'{gpid}')
                        gpid = self._nextId
                        self._nextId += 1

                    iden = self._baseId+int(gpid)
                    vg(f'Save adding counter with pid={gpid} {iden}')
                    counter = {'pid':   gpid,
                               'piece': piece,
                               'board': mapName,
                               'x':     place['x'],
                               'y':     place['y'],
                               'iden':  iden,
                               'loc':   loc
                               }
                    self._counters[(name,iden)] = counter

                traits  = piece.getTraits()
                nostack = Trait.findTrait(traits, NoStackTrait.ID)
                vg(f'{piece} {nostack}')
                if nostack is None:
                    vg(f'Adding to stack {mapName},{loc}: {counter}')
                    place['pids'].append(counter['pid'])
                    place['idens'].append(counter['iden'])

    def getLines(self,update=None):
        '''Get the final lines of code'''
        key   = 0xAA # fixed key
        
        lines = ['begin_save',
                 '',
                 '\\']

        self._pieceLines(lines,update=update)
        self._otherLines(lines)
        
        lines.append('end_save')
        return lines

    def _pieceLines(self,lines,update=lambda t:t):
        '''Add piece lines to save file

        Parameters
        ----------
        lines : list
            The lines to add
        '''
        with VerboseGuard(f'Adding piece lines to save') as vg:
            # print(self._counters)
            for i,((name,gpid),counter) in enumerate(self._counters.items()):
                iden   = counter['iden']
                # iden   = counter['pid']
                piece  = counter['piece']
                loc    = counter['loc']
                traits = piece.getTraits()
                traits = Trait.flatten(traits,self._game)
                # Get last - trait (basic piece), and modify coords
                basic        = traits[-1]
                basic['map'] = counter['board']
                basic['x']   = counter['x']
                basic['y']   = counter['y']
                # Set old location if possible
                parent = piece.getParent(DummyElement,checkTag=False)
                if parent is not None and parent._node.nodeName == AtStart.TAG:
                    #vg(f'Piece {iden} parent is AtStart {parent._node.nodeName}')
                    oldLoc   = parent['location']
                    oldBoard = parent['owningBoard']
                    oldMap   = self._game.getBoards()[oldBoard].getMap()['mapName']
                    oldX     = parent['x']
                    oldY     = parent['y']
                    oldZone  = None
                    zones    = self._game.getBoards()[oldBoard].getZones()
                    for zone in zones.values():
                        grids = zone.getGrids()
                        if grids is None: continue
                        
                        grid = grids[0]
                        if grid is None: continue
                        
                        coord = grid.getLocation(oldLoc)
                        if coord is None: continue
            
                        oldZone = zone['name']
                        oldX    = coord[0]
                        oldY    = coord[1]
                        break
            
                    # vg(f'Old state {oldMap}/{oldBoard}/{oldZone} {oldX},{oldY}')
                    newloc = (oldMap != basic['map'] or
                              oldX   != basic['x'] or
                              oldY   != basic['y'])
                    if newloc:
                        place = self._stacks[(basic['map'],loc)]
                        place['layer'] = 1
                        
                        if oldZone is not None:
                            basic['properties'] = \
                                f'8;'+\
                                f'UniqueID;{iden};'+\
                                f'OldZone;{oldZone};'+\
                                f'OldLocationName;{oldLoc};'+\
                                f'OldDeckName;;'+\
                                f'OldX;{oldX};'+\
                                f'OldY;{oldY};'+\
                                f'OldBoard;{oldBoard};'+\
                                f'OldMap;{oldMap}'
                        else:
                            basic['properties'] = \
                                f'7;'+\
                                f'UniqueID;{iden};'+\
                                f'OldLocationName;{oldLoc};'+\
                                f'OldDeckName;;'+\
                                f'OldX;{oldX};'+\
                                f'OldY;{oldY};'+\
                                f'OldBoard;{oldBoard};'+\
                                f'OldMap;{oldMap}'
                    else:
                            basic['properties'] = \
                                f'1;'+\
                                f'UniqueID;{iden}'
            
                    for trait in traits:
                        if trait.ID == TrailTrait.ID:
                            trait['map']    = oldMap
                            trait['points'] = f'1;{oldX},{oldY}'
                            trait['init']   = True
            
                # Let user code update the flattened traits
                if update is not None:
                    update(name,traits)
                # Wrapper 
                wrap   = DummyWithTraits(self._game,traits=[])
                wrap.setTraits(*traits,iden=str(iden))
                lines.append(wrap._node.childNodes[0].nodeValue+
                             ('\\' if i != len(self._counters)-1 or
                              len(self._stacks) != 0 else ''))
            
            for i,(key,dat) in enumerate(self._stacks.items()):
                idens = dat.get('idens',None)
                x     = dat['x']
                y     = dat['y']
                layer = dat['layer']
                if idens is None or len(idens) < 1:
                    print(f'No pieces at {key[0]},{key[1]}')
                    continue
                
                iden         =  self._nextId + self._baseId
                self._nextId += 1
                stack        =  StackTrait(board=key[0],x=x,y=y,
                                           pieceIds=idens,layer=layer)
                wrap         =  DummyWithTraits(self._game,traits=[])
                wrap.setTraits(stack,iden=iden)

                lines.append(wrap._node.childNodes[0].nodeValue+'\\')
                # ('\\' if i != len(self._stacks)-1 else '')
            
    def _otherLines(self,lines):
        '''Add other lines to save'''
        lines.append('UNMASK\tnull')
        if self._game.getGlobalProperties() and \
           self._game.getGlobalProperties()[0].getProperties():
            for g in self._game.getGlobalProperties()[0].getProperties().values():
                gv = g.encode()
                lines.extend(gv)
        if self._game.getPlayerRoster():
            for r in self._game.getPlayerRoster():
                lines.extend(r.encode())
        # if self._game.getTurnTracks(asdict=False):
        #     for t in self._game.getTurnTracks(asdict=False):
        #         lines.extend(t.encode())
        if self._game.getNotes(single=False):
            for n in self._game.getNotes(single=False):
                lines.extend(n.encode())
        setupStack = False
        for m in self._game.getMaps(asdict=False):
            for bp in m.getBoardPicker(single=False):
                lines.extend(bp.encode())
            if not setupStack:
                atstart = m.getAtStarts(single=False)
                if atstart and len(atstart) > 0:
                    # The terminating tab is absolutely needed!
                    lines.append('SETUP_STACK\t')
                    setupStack = True

            
# --------------------------------------------------------------------
class SaveData(ModuleData):
    def __init__(self,root=None):
        '''Convinience wrapper'''
        super(SaveData,self).__init__(root=root)
