"""
Batch processors

These commands implements the 'batch-command' and 'batch-code'
processors, using the functionality in evennia.utils.batchprocessors.
They allow for offline world-building.

Batch-command is the simpler system. This reads a file (*.ev)
containing a list of in-game commands and executes them in sequence as
if they had been entered in the game (including permission checks
etc).

Batch-code is a full-fledged python code interpreter that reads blocks
of python code (*.py) and executes them in sequence. This allows for
much more power than Batch-command, but requires knowing Python and
the Evennia API.  It is also a severe security risk and should
therefore always be limited to superusers only.

"""
from builtins import range

from django.conf import settings
from evennia.utils.batchprocessors import BATCHCMD, BATCHCODE
from evennia.commands.cmdset import CmdSet
from evennia.utils import logger, utils

COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
#from evennia.commands.default.muxcommand import COMMAND_DEFAULT_CLASS

# limit symbols for API inclusion
__all__ = ("CmdBatchCommands", "CmdBatchCode")

_HEADER_WIDTH = 70
_UTF8_ERROR = \
"""
 {rDecode error in '%s'.{n

 This file contains non-ascii character(s). This is common if you
 wrote some input in a language that has more letters and special
 symbols than English; such as accents or umlauts.  This is usually
 fine and fully supported! But for Evennia to know how to decode such
 characters in a universal way, the batchfile must be saved with the
 international 'UTF-8' encoding. This file is not.

 Please re-save the batchfile with the UTF-8 encoding (refer to the
 documentation of your text editor on how to do this, or switch to a
 better featured one) and try again.

 Error reported was: '%s'
"""

_PROCPOOL_BATCHCMD_SOURCE = """
from evennia.commands.default.batchprocess import batch_cmd_exec, step_pointer, BatchSafeCmdSet
caller.ndb.batch_stack = commands
caller.ndb.batch_stackptr = 0
caller.ndb.batch_batchmode = "batch_commands"
caller.cmdset.add(BatchSafeCmdSet)
for inum in range(len(commands)):
    print "command:", inum
    caller.cmdset.add(BatchSafeCmdSet)
    if not batch_cmd_exec(caller):
        break
    step_pointer(caller, 1)
print "leaving run ..."
"""
_PROCPOOL_BATCHCODE_SOURCE = """
from evennia.commands.default.batchprocess import batch_code_exec, step_pointer, BatchSafeCmdSet
caller.ndb.batch_stack = codes
caller.ndb.batch_stackptr = 0
caller.ndb.batch_batchmode = "batch_code"
caller.cmdset.add(BatchSafeCmdSet)
for inum in range(len(codes)):
    print "code:", inum
    caller.cmdset.add(BatchSafeCmdSet)
    if not batch_code_exec(caller):
        break
    step_pointer(caller, 1)
print "leaving run ..."
"""


#------------------------------------------------------------
# Helper functions
#------------------------------------------------------------

def format_header(caller, entry):
    """
    Formats a header
    """
    width = _HEADER_WIDTH - 10
    entry = entry.strip()
    header = utils.crop(entry, width=width)
    ptr = caller.ndb.batch_stackptr + 1
    stacklen = len(caller.ndb.batch_stack)
    header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header)
    # add extra space to the side for padding.
    header = "%s%s" % (header, " " * (width - len(header)))
    header = header.replace('\n', '\\n')

    return header


def format_code(entry):
    """
    Formats the viewing of code and errors
    """
    code = ""
    for line in entry.split('\n'):
        code += "\n{G>>>{n %s" % line
    return code.strip()


def batch_cmd_exec(caller):
    """
    Helper function for executing a single batch-command entry
    """
    ptr = caller.ndb.batch_stackptr
    stack = caller.ndb.batch_stack
    command = stack[ptr]
    caller.msg(format_header(caller, command))
    try:
        caller.execute_cmd(command)
    except Exception:
        logger.log_trace()
        return False
    return True


def batch_code_exec(caller):
    """
    Helper function for executing a single batch-code entry
    """
    ptr = caller.ndb.batch_stackptr
    stack = caller.ndb.batch_stack
    debug = caller.ndb.batch_debug
    code = stack[ptr]

    caller.msg(format_header(caller, code))
    err = BATCHCODE.code_exec(code,
                              extra_environ={"caller": caller}, debug=debug)
    if err:
        caller.msg(format_code(err))
        return False
    return True


def step_pointer(caller, step=1):
    """
    Step in stack, returning the item located.

    stackptr - current position in stack
    stack - the stack of units
    step - how many steps to move from stackptr
    """
    ptr = caller.ndb.batch_stackptr
    stack = caller.ndb.batch_stack
    nstack = len(stack)
    if ptr + step <= 0:
        caller.msg("{RBeginning of batch file.")
    if ptr + step >= nstack:
        caller.msg("{REnd of batch file.")
    caller.ndb.batch_stackptr = max(0, min(nstack - 1, ptr + step))


def show_curr(caller, showall=False):
    """
    Show the current position in stack
    """
    stackptr = caller.ndb.batch_stackptr
    stack = caller.ndb.batch_stack

    if stackptr >= len(stack):
        caller.ndb.batch_stackptr = len(stack) - 1
        show_curr(caller, showall)
        return

    entry = stack[stackptr]

    string = format_header(caller, entry)
    codeall = entry.strip()
    string += "{G(hh for help)"
    if showall:
        for line in codeall.split('\n'):
            string += "\n{G|{n %s" % line
    caller.msg(string)


def purge_processor(caller):
    """
    This purges all effects running
    on the caller.
    """
    try:
        del caller.ndb.batch_stack
        del caller.ndb.batch_stackptr
        del caller.ndb.batch_pythonpath
        del caller.ndb.batch_batchmode
    except:
        pass
    # clear everything but the default cmdset.
    caller.cmdset.delete(BatchSafeCmdSet)
    caller.cmdset.clear()
    caller.scripts.validate()  # this will purge interactive mode

#------------------------------------------------------------
# main access commands
#------------------------------------------------------------


class CmdBatchCommands(COMMAND_DEFAULT_CLASS):
    """
    build from batch-command file

    Usage:
     @batchcommands[/interactive] <python.path.to.file>

    Switch:
       interactive - this mode will offer more control when