#!/usr/bin/env python

#    mse-export-fixer: a command-line app for fixing the .xml files
#    generated by the Magic Set Editor "Cockatrice Exporter Re-Updated"
#    export template.
#    Copyright (C) 2020 Brandon Lewis.
#
#    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 <https://www.gnu.org/licenses/>.

import sys
import argparse
from argparse import ArgumentParser
import re
from datetime import date
from string import Template
from itertools import islice

version = ('1.0.2')

# get the current date and put it in the correct string format
today = date.today()
date = today.strftime("%Y-%m-%d")

### command line argument parsing 
## define the argument parser
parser = ArgumentParser(description = "Fix a Cockatrice .xml file exported from MSE")
# first arg: file to be fixed
parser.add_argument('File', help = "path to the .xml file to fix")
# second arg: file datestamping flag
parser.add_argument('--date', '-d', dest='doDate', action='store_true', 
                    help = "set the set release date to today\'s date (default off)")
# third arg: flag for version number (exits the script if used)
parser.add_argument('--version', '-v', action='version',
                    version="fixer v" + version)
# fourth arg: what to name the output file
parser.add_argument('--outputname ', '-o', dest='outputName', default='set_fixed.xml',
                    help = "what to name the output (fixed) file. Defaults to set_fixed.xml")
# fifth arg: flag for verbosity option
#parser.add_argument('--verbose', dest='Verbose', action='store_true',
                    #help = "verbose extraction and construction")
# sixth arg: flag for prompting user for colors of DFC backs
parser.add_argument('--colorprompt', '-cp', dest='colorPrompt', action='store_true',
                    help = "manually enter color for DFC backs (default copies front color)")


def outputInit():
    """Initialize the output file.

Writes the standard .xml header, the Cockatrice card database tag,
and the set info block derived from the input file.
Returns the location of the </sets> tag in the input file,
as well as the set code."""
    with open(outputFilename, "wt") as Out: #open the output file
        Out.write('<?xml version="1.0" encoding="UTF-8"?>\n') #write the .xml general header
        Out.write('<cockatrice_carddatabase version="4">\n') #write the Cockatrice database header
    [setEnd,setBlock] = blockExtract('sets', 0) #extract the set info block
    tags = ['name','longname','settype','releasedate'] #the list of info tags that the set should have
    setInfo = infoExtract(setBlock, tags) #get the set info from the block
    fixInfo = setDiagnose(setInfo) #check for missing info
    newBlock = blockBuild(fixInfo, 'set') #build the new set info block
    with open(outputFilename, "at") as Out: # write set block and <cards> tag to output file
        Out.write('    <sets>\n')
        Out.write(newBlock)
        Out.write('    </sets>\n')
        Out.write('    <cards>\n')
    return [setEnd, fixInfo.name]


def blockExtract(tag, loc):
    """Extract an entire set or card info block from the input file."""
    start = '<' + tag + '>'
    end = '</' + tag + '>' #build matchable strings for the start and end tags
    ## open the input file and search for the first string enclosed by <tag></tag>
    block = ''
    with open(inputFilename, "rt") as In:
        i = loc
        sliced = islice(In, loc, None)
        for line in sliced:
            if re.search(start, line):
                block = line
                break
            else: i=i+1
    with open(inputFilename, "rt") as In:
        sliced2 = islice(In, i, None)
        for line in sliced2:
            block = block + line
            i = i+1
            if re.search(end, line):
                break
    endLoc = i
    return [endLoc, block]


def infoExtract(block, tags): # block should be the info block string, tags should be a list of info tags
    """Extract the info from a card or set info block."""
    info = SimpleNamespace() #define the block's info as a namespace object
    d = vars(info)
    for tag in tags:
        if (tag + '>') in block: #check if the tag is present (activates for <tag> or </tag>)
            try:
                regex = regex_temp.substitute({'tag' : tag}) #form the regex string
                found = re.search(regex, block, re.S) #search for the info in the block
                d[tag] = found.group(1) #assign the extracted info to the tag in the namespace
            except AttributeError:
                print("Missing <" + tag + "> or </" + tag + "> tag")
                # will only activate if one half of the tag wrapper is present but not the other
    return info


def blockBuild(info, blocktype):
    """Build a new set or card info block using an info namespace."""
    d = vars(info)
    if blocktype=='set':
        block = setBlock_temp.substitute(d)
    if blocktype=='card':
        #block = '        <card>\n            whatever\n        </card>\n'
        block = '        <card>\n'
        block += '            ' + singleInfo.substitute({'tag' : 'name', 'info' : d['name']})
        block += '            ' + singleInfo.substitute({'tag' : 'text', 'info' : d['text']})
        block += '            <prop>\n'
        for tag in generictags:
            if hasattr(info, tag):
                infoline = singleInfo.substitute({'tag' : tag, 'info' : d[tag]})
                block += '                ' + infoline
        block += '            </prop>\n'
        block += '            <set>' + setCode + '</set>\n'
        for tag in specialtags:
            if hasattr(info, tag):
                infoline = singleInfo.substitute({'tag' : tag, 'info' : d[tag]})
                block += '            ' + infoline
        if hasattr(info, 'related'):
            if info.attach:
                infoline = '<related attach="attach">' + info.related + '</related>\n'
            else:
                infoline = singleInfo.substitute({'tag' : 'related', 'info' : info.related})
            block += '            ' + infoline
        if hasattr(info, 'reverse-related'):
            infoline = singleInfo.substitute({'tag' : 'reverse-related', 'info' : info.reverse-related})
            block += '            ' + infoline
        block += '        </card>\n'
    return block


def outputFin():
    """Finalize the output file.

Writes the Cockatrice card database end tag to the output file.
Displays a success message, using the set code if it exists."""
    with open(outputFilename, "at") as Out:
        Out.write('\n    </cards>\n')
        Out.write('</cockatrice_carddatabase>')
    if setCode == ' ': #if the set had no name, meaning diagnostics set the name to a whitespace string
        endMessage = "Successfully wrote " + outputFilename
    else:
        endMessage = "Successfully wrote " + outputFilename + " for set " + setCode
    print(endMessage)


def cardfix(cardsLoc):
    """Fix all card info blocks and write them to the output file."""
    nextCard = cardsLoc
    with open(inputFilename, "rt") as In:
        file = In.read()
        cardcount = file.count('<card>') #count the cards with the <card> tag
    cardnum = 1
    while cardnum <= cardcount:
        [nextCard,cardblock] = blockExtract('card', nextCard) #extract the next card block
        cardInfo = infoExtract(cardblock, cardtags) #extract the card info
        fixInfo = cardDiagnose(cardInfo, cardnum)
        info = secondaryInfo(fixInfo) #color identity and cmc don't depend on whether it's a DFC
        if DFC_check(fixInfo): #if it's a DFC:
            newBlock = DFC_process(fixInfo) #call the DFC processor
        else:
            newBlock = blockBuild(info, 'card') #build the normal block
        with open(outputFilename, "at") as Out:
            Out.write(newBlock) #append the new info block
        cardnum = cardnum + 1 #increment the card number

def setDiagnose(info):
    """Detect missing set info.

Sets noncritical info (date and set type) to defaults.
Sends messages for missing set name and set code."""
    d = vars(info)
    if not hasattr(info, 'settype'): d['settype'] = 'Custom' #default set type due to assumed usage
    if not doDate: #then check if there's already an assigned date
        if not hasattr(info, 'releasedate'): d['releasedate'] = date #assign current date as default
    else: d['releasedate'] = date #set date if datestamping was on
    if not hasattr(info, 'longname'):
        d['longname'] = ' '
        print("Set has no name")
    if not hasattr(info, 'name'):
        d['name'] = ' '
        print("Set has no set code")
    return info


def cardDiagnose(info, cardNumber):
    """Detect missing critical card info."""
    return info


def DFC_check(info):
    """Check if a card is double-faced."""
    if '//' in info.type:
        isDFC = True
    else:
        isDFC = False
    return isDFC


def secondaryInfo(info):
    """Process additional card info from extracted info."""
    return info


def cmc(manacost):
    """Determine a card's converted mana cost."""
    pass


def color(manacost, cardtext):
    """Determine a card's color."""
    pass


def coloridentity(manacost, cardtext):
    """Determine a card's color identity."""
    pass


def token(cardtext):
    """Determine tokens to associate."""
    pass


def dual_check(info):
    """Check if a card is a dual card."""
    if '//' in info.name:
        isDual = True
    else:
        isDual = False
    return isDual


def DFC_process(info):
    """Create the combined info block for a DFC."""
    [frontinfo, backinfo] = backProcess(info) #call the info processor
    frontBlock = blockBuild(frontinfo, 'card')
    backBlock = blockBuild(backinfo, 'card')
    block = frontBlock + backBlock
    return block


def backProcess(info):
    """Derive the front and back info from a combined info namespace."""
    backInfo = SimpleNamespace(side='back') #create back info namespace
    frontInfo = SimpleNamespace(side='front') #create front info namespace
    frontInfo.name = info.name
    backInfo.name = input("Enter the name for the back of " + info.name + ": ")
    if hasattr(info, 'color'):
        frontInfo.color = info.color
        if doBackColor == True:
            backInfo.color = input("Enter the color of the back of " + info.name + ": ")
        else:
            backInfo.color = info.color
    found = re.search('(.+?) // (.+)', info.type)
    if found:
        frontInfo.type = found.group(1) #first part goes to the front
        backInfo.type = found.group(2) #second part goes to the back
    else:
        print("Error processing type for " + frontInfo.name + ' // ' + backInfo.name)
    found = re.search('(.+?)\n---\n(.+)', info.text, re.S) #split the rules text
    if found:
        frontInfo.text = found.group(1) # same as above
        backInfo.text = found.group(2) 
    else:
        print("Error processing rules text for " + frontInfo.name + ' // ' + backInfo.name)
    if hasattr(info, 'pt'): #split the power/toughness
        found = re.search('(\d?[XYZ*\d]/\d?[XYZ*\d]) // (\d?[XYZ*\d]/\d?[XYZ*\d])', info.pt)
        if found:
            frontInfo.pt = found.group(1) # same as above
            backInfo.pt = found.group(2)
        else:
            print("Error processing power/toughness for " + frontInfo.name + ' // ' + backInfo.name)
    d = vars(info)
    df = vars(frontInfo)
    db = vars(backInfo)
    for tag in carrytags:
        if hasattr(info, tag):
            df[tag] = d[tag]
            db[tag] = d[tag]
    frontInfo.related = backInfo.name
    backInfo.related = frontInfo.name
    frontInfo.attach = True
    backInfo.attach = True
    return [frontInfo, backInfo]


### String templates

singleInfo = Template('<$tag>$info</$tag>\n') #output template for a single pair of [tag, info]

setBlock_temp = Template('        <set>\n' +
                         '            <name>$name</name>\n' +
                         '            <longname>$longname</longname>\n' +
                         '            <settype>$settype</settype>\n' +
                         '            <releasedate>$releasedate</releasedate>\n' +
                         '        </set>\n') #output template for a set info block

# template for the regex string for finding info for a given tag
regex_temp = Template('<$tag>(.+?)</$tag>')


### Global lists

colors = ['R','W','G','U','B'] #list of mana colors for determining card color or color identity

# list of possible card info tags
cardtags = ['name','text','layout','side','type','maintype','manacost','cmc','colors','coloridentity',
            'pt','loyalty','related','reverse-related','token','tablerow','cipt','upsidedown']
# list of generic card info tags, i.e. those that go in the <prop> tag block
generictags = ['layout','side','type','maintype','manacost','cmc','colors',
               'coloridentity','pt','loyalty']
# tags that go between </prop> and </card>
specialtags = ['token','tablerow','cipt','upsidedown']
# tags whose values are common to both sides of DFCs
carrytags = ['coloridentity','tablerow','maintype','layout','cmc']


### Initialize the application

## parse the supplied arguments and extract their surplus value, like a capitalist
argspace = parser.parse_args()
inputFilename = argspace.File
outputFilename = argspace.outputName
doDate = argspace.doDate
doBackColor = argspace.colorPrompt

[cardsLoc, setCode] = outputInit() #conduct output initialization,
# retrieving card info start location and set code into global variables

cardfix(cardsLoc) #activate main processing starting at start of card info

outputFin() #finalize the output file
