import sys
import json
import random
import logger

this = sys.modules[__name__]

this.encoded = None
this.pattern = None
this.values = {
    "stack": 0,
    "stack2": 0,
    "stack3": 0,
    "stack4": 0,
    "stack5": 0,
    "stack6": 0,
    "stackr": 0,
    "stackg": 0,
    "stackb": 0,
    "r": 0,
    "g": 0,
    "b": 0,
    "tick": 0,
    "index": 0,
    "fadeval": 0,
    "fadeinc": True
}


def constant(target, arg, index):
    return (arg, index)


def add(target, arg, index):
    return (target + arg, index)


def sub(target, arg, index):
    return (target - arg, index)


def mult(target, arg, index):
    return (target * arg, index)


def div(target, arg, index):
    return (target / arg, index)


def mod(target, arg, index):
    if arg <= 0:
        return target
    return (target % arg, index)


def fade(target, arg, index):
    value = this.values['fadeval']
    if this.values['fadeinc']:
        value += 1
        if value >= arg:
            this.values['fadeinc'] = False
    else:
        value -= 1
        if value <= 0:
            this.values['fadeinc'] = True
    this.values['fadeval'] = value
    return (value, index)


def rand(target, arg, index):
    return (random.randrange(0, 255), index)


def jmp(target, arg, index):
    return (target, target)


def jnz(target, arg, index):
    if target != 0:
        return (target, arg)
    else:
        return (target, index)


def jez(target, arg, index):
    if target == 0:
        return (target, arg)
    else:
        return (target, index)


def _apply(index, target, arg, func):
    if type(arg) is int:
        logger.debug("ran: {} {} {} {}".format(index, func.__name__, target, arg))
        return func(target, arg, index)
    elif type(arg) is str and arg in this.values:
        logger.debug("ran: {} {} {} {}".format(
            index, func.__name__, target, this.values[arg]))
        return func(target, this.values[arg], index)


def apply(index, targets, args, func):
    j = index
    for target in range(len(targets)):
        if this.values[targets[target]['channel']] != None:
            logger.debug("target: {}".format(targets[target]['channel']))
            if target < len(args):
                val, jump = _apply(
                    index, this.values[targets[target]['channel']], args[target], func)
                if val != this.values[targets[target]['channel']]:
                    this.values[targets[target]['channel']] = val
                j = jump
            else:
                val, jump = _apply(
                    index, this.values[targets[target]['channel']], 0, func)
                if val != this.values[targets[target]['channel']]:
                    this.values[targets[target]['channel']] = val
                j = jump
    return j


this.instructions = {
    "CONSTANT": constant,
    "ADD": add,
    "SUBTRACT": sub,
    "MULTIPLY": mult,
    "DIVIDE": div,
    "MODULO": mod,
    "FADE": fade,
    "RANDOM": rand,
    "JMP": jmp,
    "JNZ": jnz,
    "JEZ": jez
}


def default(index, tick):
    return (((index + tick) * 5) % 255, (tick * 42) % 255, (tick * 50) % 255)


def pat(index, tick, previous_values):
    this.values['tick'] = tick
    this.values['index'] = index
    if this.pattern != None:
        i = 0
        while i < len(this.pattern):
            if i < len(this.pattern):
                name = this.pattern[i]['instruction']['name']
                targets = this.pattern[i]['instruction']['targets']
                args = []
                if 'args' in this.pattern[i]['instruction']:
                    args = this.pattern[i]['instruction']['args']
                if this.instructions[name] != None:
                    jump = apply(i, targets, args, this.instructions[name])
                    logger.debug("{} {} {} {} {}".format(jump, i, len(
                        this.pattern), jump != i, jump <= len(this.pattern)))
                    if jump != i and jump <= len(this.pattern):
                        logger.debug("jumping to {}".format(jump - 1))
                        i = jump - 1
            i += 1
        r = this.values["r"]
        g = this.values["g"]
        b = this.values["b"]
        this.values["r"] = 0
        this.values["g"] = 0
        this.values["b"] = 0
        this.values["stack"] = 0
        if r > 255:
            r = 255
        elif r < 0:
            r = 0
        if g > 255:
            g = 255
        elif g < 0:
            g = 0
        if b > 255:
            b = 255
        elif b < 0:
            b = 0
        logger.debug("final color: {}".format((r, g, b)))
        return (r, g, b)
    else:
        return default(index, tick)


def parse(str):
    this.encoded = str
    logger.debug(this.encoded)
    this.pattern = json.loads(this.encoded)