""" Utilities to help load the vehicle/ordnance listings. """

import os
import json
import re
import copy
import logging

from vasl_templates.webapp import app, globvars
from vasl_templates.webapp.config.constants import DATA_DIR

_vo_comments = None

# ---------------------------------------------------------------------

_NOTE_ID_PREFIXES = {
    "US": "american",
    "Br": "british",
    "Ge": "german",
    "Ru": "russian",
    "Fr": "french",
    "Ch": "chinese",
    "AllM": "allied-minor",
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

_COMMENT_HANDLERS = {
    "russian": {
        "vehicles": {
            "N": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(a)": "American ESB",
                "(b)": "British ESB"
            } ),
            "LL": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(a)": "{? 01/1944- | Black TH# | Red TH# | Black TH#<sup>44+</sup> ?}"
            }, "Black TH#" ),
        }
    },
    "british": {
        "vehicles": {
            "A": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(a)": "American ESB+"
            } )
        }
    },
    "french": {
        "vehicles": {
            "F": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(a)": _french_veh_f,
                "(b)": _french_veh_f,
                "(f)": _french_veh_f,
            } ),
        }
    },
    "finnish": {
        "vehicles": {
            "D": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(b)": "British ESB",
                "(g)": [ "German ESB", "Black TH#" ],
                "(r)": "Russian ESB",
                "(s)": [ "Swedish ESB", "Black TH#" ]
            } )
        },
        "ordnance": {
            "B": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(b)": "Black TH#",
                "(f)": "Black TH#",
                "(g)": [ "Black TH#", "No Captured Use penalty for Germans" ],
                "(r)": "No Captured Use penalty for Russians",
                "(s)": "Black TH#",
                "(t)": "Black TH#",
            } )
        }
    },
    "chinese": {
        "vehicles": {
            "A": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(a)": [ "American ESB, +1 DRM" ],
                "(b)": [ "British ESB, +1 DRM" ],
                "(g)": [ "German ESB, +1 DRM" ],
                "(i)": [ "Italian ESB, +1 DRM" ],
                "(r)": [ "Russian ESB, +1 DRM" ],
            } ),
            "D": "2 TK DR"
        }
    },
    "allied-minor": {
        "vehicles": {
            "A": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(a)": "American ESB+",
                "(b)": "British ESB+",
                "(f)": "French ESB+",
                "(i)": "Italian ESB+",
            } )
        }
    },
    "axis-minor": {
        "vehicles": {
            "E": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(f)": "French ESB",
                "(g)": _axis_minor_veh_e,
                "(i)": "Italian ESB",
                "(r)": "Russian ESB",
                "(t)": _axis_minor_veh_e,
            } ),
        },
        "ordnance": {
            "E": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(g)": _axis_minor_ord_e,
                "(t)": _axis_minor_ord_e,
            } ),
        }
    },
    "kfw-un": {
        "vehicles": {
            "UU": lambda vo_entry, note_id: _check_name( vo_entry, note_id, {
                "(a)": "American ESB+",
            } ),
        }
    }
}

def _check_comment_handlers( vo_entry, nat, vo_type, note_id, comments, orig_nat ):
    """Add any multi-applicable note-specific comments to the vehicle/ordnance."""
    val = _COMMENT_HANDLERS.get( nat, {} ).get( vo_type, {} ).get( note_id )
    if not val:
        return
    if isinstance( val, str ):
        comments.append( val )
    elif isinstance( val, list ):
        comments.extend( val )
    else:
        assert callable( val )
        val = val( vo_entry, orig_nat )
        if val:
            assert isinstance( val, list )
            comments.extend( val )

def _french_veh_f( vo_entry, orig_nat ): #pylint: disable=unused-argument
    """Handle French Vehicle Note F."""
    # NOTE: French Vehicle Note F says things like:
    #   "(a)" also indicates that this vehicle is treated as captured if crewed by other than Free French or U.S.
    # so we would like to be smart here and check the owning player's nationality and add a "Captured Use" comment
    # only if it applies. Unfortunately, while this technique works for the Allied/Axis Minor common vehicles/ordnance,
    # it won't here :-(
    # Consider a scenario where the British have an Ac de 40 CA(a). This piece won't appear in the list of
    # British vehicles, so the user has to set up a 2nd scenario, with a Free French player, to get access to
    # this piece. The code will detect the owning player is the Free French, and so conclude that it doesn't need
    # to add a "Captured Use" comment. There's no way of fixing this (other than adding the Free French
    # vehicles/ordnance to every nationality that could possibly use them), so we add the comment verbatim
    # and let the user figure it out.
    if "(a)" in vo_entry["name"]:
        comments = [ "Black TH#", "American ESB+" ]
        if vo_entry["id"] == "fr/v:020": # nb: AM Dodge(a)
            comments.append( "Captured Use (unless Vichy French)" )
        else:
            comments.append( "Captured Use (unless Free French or US)" )
    elif "(b)" in vo_entry["name"]:
        comments = [ "Black TH#", "British ESB+" ]
        comments.append( "Captured Use (unless Vichy French or British)" )
    elif "(f)" in vo_entry["name"]:
        comments = [ "Red TH#", "French ESB+" ]
        comments.append( "Captured Use (unless Free/Vichy French)" )
    else:
        comments = []
    return comments

def _axis_minor_veh_e( vo_entry, orig_nat ):
    """Handle Axis Minor Vehicle Note E (cases (g) and (t) only)."""
    assert "(g)" in vo_entry["name"] or "(t)" in vo_entry["name"]
    comments = [ "German ESB" if "(g)" in vo_entry["name"] else "Czech ESB" ]
    if orig_nat in ( "romanian", "hungarian", "slovakian" ):
        comments.append( "Black TH#" )
    return comments

def _axis_minor_ord_e( vo_entry, orig_nat ):
    """Handle Axis Minor Ordnance Note E."""
    assert "(g)" in vo_entry["name"] or "(t)" in vo_entry["name"]
    if orig_nat in ( "romanian", "hungarian", "slovakian" ):
        return [ "Black TH#" ]
    return None

def _check_name( vo_entry, nat, cases, defaultVal=None ):
    """Check the vehicle/ordnance's name."""
    for key,val in cases.items():
        if key in vo_entry.get("name",""):
            if isinstance( val, str ):
                return [val]
            elif isinstance( val, list ):
                return val
            else:
                assert callable( val )
                return val( vo_entry, nat )
    if defaultVal:
        return [defaultVal]
    return None

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

def add_vo_comments( listings, vo_type, msg_store ):
    """Add comments to the vehicle/ordnance entries."""
    # Melbourne, Australia (JUN/20)

    # initialize
    global _vo_comments
    if not _vo_comments:
        fname = os.path.join( app.config.get("DATA_DIR",DATA_DIR), "vo-comments.json" )
        with open( fname, "r", encoding="utf-8" ) as fp:
            _vo_comments = json.load( fp )

    # process each vehicle/ordnance
    for nat,vo_entries in listings.items():
        for vo_entry in vo_entries:
            if "copy_from" in vo_entry:
                continue # nb: we do these later, when the entry is actually copied
            _do_add_vo_comments( vo_entry, nat, vo_type, msg_store )

def _do_add_vo_comments( vo_entry, nat, vo_type, msg_store ): #pylint: disable=too-many-locals,too-many-branches
    """Add comments to a vehicle/ordnance entry."""

    # figure out which comments have been disabled
    disable_comments_for_note_ids = set() # disable all omments associated with these note ID's
    disabled_comments = set() # disable these specific comments
    prefixes = "|".join( _NOTE_ID_PREFIXES.keys() )
    regex = re.compile( "^(({}) )?[A-Za-z]{{,2}}$".format( prefixes ) )
    vals = vo_entry.get( "disabled_comments", [] )
    for val in vals if isinstance(vals,list) else [vals]:
        if regex.search( val ):
            disable_comments_for_note_ids.add( val )
        else:
            disabled_comments.add( val )

    # get the vehicle/ordnance's manually-defined comments
    comments = vo_entry.get( "comments", [] )
    if isinstance( comments, str ):
        comments = [ comments ]

    # add any generated comments
    comments.extend(
        _make_comments( vo_entry, nat, vo_type, disable_comments_for_note_ids )
    )

    # dedupe the comments
    # NOTE: This needs to be done in the front-end as well, since some comments will be generated
    # based on the scenario date, and we have no way of knowing what that is here.
    comments2, comment_index = [], set()
    for cmt in comments:
        if cmt in comment_index:
            continue
        comments2.append( cmt )
        comment_index.add( cmt )

    # remove comments that have been disabled
    # NOTE: This needs to be done in the front-end as well, since some comments will be generated
    # based on the scenario date, and we have no way of knowing what that is here.
    comments3 = []
    def parse_cmd( cmt ):
        """Parse a disabled comment command."""
        if cmt.startswith( "?:" ):
            return cmt[2:].strip(), False # nb: this is an optional comment (i.e. don't warn if it's not there)
        else:
            return cmt, True
    disabled_comments = dict( parse_cmd(c) for c in disabled_comments )
    for cmt in comments2:
        if cmt in disabled_comments:
            del disabled_comments[ cmt ]
        else:
            comments3.append( cmt )
    disabled_comments = { k: v for k,v in disabled_comments.items() if v }
    if disabled_comments:
        if msg_store:
            msg_store.warning(
                "Can't find disabled comments for {}: <ul> {} </ul>".format(
                    vo_entry["id"],
                    " ".join( "<li> {}".format(c) for c in disabled_comments )
                )
            )

    # install the comments into the vehicle/ordnance entry
    if comments3:
        vo_entry["comments"] = comments3
    else:
        vo_entry.pop( "comments", None )

def _make_comments( vo_entry, nat, vo_type, disabled_note_ids ): #pylint: disable=too-many-branches
    """Automatically generate comments for a vehicle/ordnance."""

    # initialize
    all_comments = []

    # process each multi-applicable note
    vo_notes = vo_entry.get( "notes", [] )
    for note_id in vo_notes:

        # clean up the next note ID
        pos = note_id.find( "\u2020" )
        if pos >= 0:
            note_id = note_id[:pos]
        note_id = re.sub( r"\<sup\>.*?\</sup\>", "", note_id )
        note_id = re.sub( r"\<s\>.*?\</s\>", "", note_id )
        if not note_id:
            continue
        assert re.search( "^[A-Za-z0-9 ]+$", note_id )

        # translate nationality-specific note ID's
        orig_note_id = note_id
        force_auto_comment = False
        nat2 = nat
        if globvars.template_pack:
            nat_type = globvars.template_pack[ "nationalities" ].get( nat, {} ).get( "type" ) #pylint: disable=unsubscriptable-object
        else:
            nat_type = None
        if nat in ( "kfw-uro", "kfw-bcfk", "kfw-un-common" ):
            nat2 = "kfw-un"
        elif nat in ( "kfw-kpa", "kfw-cpva" ):
            nat2 = "kfw-comm"
        elif nat_type == "allied-minor" or nat == "allied-minor-common":
            nat2 = "allied-minor"
        elif nat_type == "axis-minor" or nat == "axis-minor-common":
            nat2 = "axis-minor"
        words = note_id.split()
        if len(words) > 1:
            nat2 = _NOTE_ID_PREFIXES.get( words[0] )
            if nat2:
                note_id = " ".join( words[1:] )
            force_auto_comment = True

        # check if all comments for this note have been disabled
        if orig_note_id in disabled_note_ids:
            continue

        # generate any comments associated with this multi-applicable note
        comments = []
        if not vo_entry.get( "extn_id" ):
            # NOTE: We don't do this for extensions because if a vehicle/ordnance has Note X,
            # that references the extension's Note X, not the nationality's normal Note X.
            # However, vehicles/ordnance can reference things like "Ru M" or "AllM F", but these will
            # set force_auto_comment and cause those comments to be added below.
            if nat2 != nat:
                _check_comment_handlers( vo_entry, nat2, vo_type, note_id, comments, nat )
            else:
                _check_comment_handlers( vo_entry, nat, vo_type, note_id, comments, None )
        if not vo_entry.get( "extn_id" ) or force_auto_comment:
            auto_comments = _vo_comments.get( nat2, {} ).get( vo_type, {} ).get( note_id )
            if auto_comments:
                _append_to( comments, auto_comments )

        # update the vehicle/ordnance entry
        _append_to( all_comments, comments )

    return all_comments

def _append_to( dest, val ):
    """Append value(s) to a list."""
    assert isinstance( dest, list )
    if isinstance( val, str ):
        dest.append( val )
    elif isinstance( val, list ):
        dest.extend( val )
    else:
        assert False

# ---------------------------------------------------------------------

def copy_vo_entry( placeholder_vo_entry, src_vo_entry, nat, vo_type, msg_store ): #pylint: disable=too-many-branches
    """Create a new vehicle/ordnance entry by copying an existing one."""
    # Anjuna, India (FEB/19)

    # create the new vehicle/ordnance entry
    new_vo_entry = copy.deepcopy( src_vo_entry )
    new_vo_entry["id"] = placeholder_vo_entry["id"]
    if "name" in placeholder_vo_entry:
        new_vo_entry["name"] = placeholder_vo_entry["name"]
    if "gpid" in placeholder_vo_entry:
        new_vo_entry["gpid"] = placeholder_vo_entry["gpid"]
    elif "extra_gpids" in placeholder_vo_entry:
        if not isinstance( new_vo_entry["gpid"], list ):
            new_vo_entry["gpid"] = [ new_vo_entry["gpid"] ]
        new_vo_entry["gpid"].extend( placeholder_vo_entry["extra_gpids"] )

    def add_prefix( notes, prefix ):
        """Add a prefix to a list of note ID's."""
        for i,note in enumerate(notes):
            notes[i] = "{} {}".format( prefix, note )

    # fixup any note numbers and multi-applicable notes
    vo_id = placeholder_vo_entry[ "copy_from" ]
    if vo_id.startswith( "br/" ):
        prefix = "Br"
    elif vo_id.startswith( "am/" ):
        prefix = "US"
    elif vo_id.startswith( "fr/" ):
        prefix = "Fr"
    else:
        logging.warning( "Unexpected vehicle/ordnance reference nationality: %s", vo_id )
        prefix = ""
    if "note_number" in placeholder_vo_entry:
        # replace the note# with the explicitly-defined one
        new_vo_entry["note_number"] = placeholder_vo_entry["note_number"]
    else:
        # fixup the note# from the original vehicle/ordnance
        new_vo_entry["note_number"] = "{} {}".format(  prefix, new_vo_entry["note_number"] )
    if "notes" in placeholder_vo_entry:
        # replace the multi-applicable notes with the explicitly-defined ones
        new_vo_entry["notes"] = placeholder_vo_entry["notes"]
    elif "notes" in new_vo_entry:
        # fixup the multi-applicable notes from the original vehicle/ordnance
        add_prefix( new_vo_entry["notes"], prefix )
        if "extra_notes" in placeholder_vo_entry:
            new_vo_entry["notes"].extend( placeholder_vo_entry["extra_notes"] )

    # fixup the comments
    if "comments" in placeholder_vo_entry:
        if "comments" in new_vo_entry:
            new_vo_entry["comments"].extend( placeholder_vo_entry["comments"] )
        else:
            new_vo_entry["comments"] = placeholder_vo_entry["comments"]
    if "disabled_comments" in new_vo_entry:
        add_prefix( new_vo_entry["disabled_comments"], prefix )
    if "disabled_comments" in placeholder_vo_entry:
        if "disabled_comments" in new_vo_entry:
            new_vo_entry["disabled_comments"].extend( placeholder_vo_entry["disabled_comments"] )
        else:
            new_vo_entry["disabled_comments"] = placeholder_vo_entry["disabled_comments"]
    # NOTE: Dynamically adding comments complicates things a lot, since they sometimes depend on
    # the vehicle/ordnance's name (e.g. if it contains "(a)"), or the owning nationality.
    # We re-generate the comments here, which means that comments from the source entry will be
    # re-added, but they will get deduped.
    _do_add_vo_comments( new_vo_entry, nat, vo_type, msg_store )

    return new_vo_entry

# ---------------------------------------------------------------------

def apply_extn_info( listings, extn_fname, extn_info, vo_index, vo_type ):
    """Update the vehicle/ordnance listings for the specified VASL extension."""

    # initialize
    logger = logging.getLogger( "vasl_mod" )
    logger.info( "Updating %s for VASL extension '%s': %s",
        vo_type, extn_info["extensionId"], os.path.split(extn_fname)[1]
    )

    # process each entry
    for nat in extn_info:
        if not isinstance( extn_info[nat], dict ):
            continue
        for entry in extn_info[nat].get( vo_type, [] ):
            vo_entry = vo_index.get( entry["id"] )
            if vo_entry:
                # update an existing vehicle/ordnance
                logger.debug( "- Updating GPID's for %s: %s", entry["id"], entry["gpid"] )
                if vo_entry["gpid"]:
                    prev_gpids = vo_entry["gpid"]
                    if not isinstance( vo_entry["gpid"], list ):
                        vo_entry["gpid"] = [ vo_entry["gpid"] ]
                    vo_entry["gpid"].extend( entry["gpid"] )
                else:
                    prev_gpids = "(none)"
                    vo_entry["gpid"] = entry["gpid"]
                # NOTE: We can't really set the extension ID here because the counter is also in the core VASL module.
                logger.debug( "  - %s => %s", prev_gpids, vo_entry["gpid"] )
            else:
                # add a new vehicle/ordnance
                if nat not in listings:
                    listings[ nat ] = []
                entry[ "extn_id" ] = extn_info[ "extensionId" ]
                listings[ nat ].append( entry )

# ---------------------------------------------------------------------

def make_vo_index( vo_entries ):
    """Generate an index of each vehicle/ordnance entry."""
    vo_index = {}
    for nat in vo_entries:
        for vo_entry in vo_entries[nat]:
            vo_index[ vo_entry["id"] ] = vo_entry
    return vo_index
