from enum import Enum
import unittest
from tags import TagAttribs
from tags import TagObjName
from tags import InvalidTagObjName
from words import WordAttribs
from words import WordObjName
from words import WordKind
from words import InvalidWordObjName
from bank_utils import VocabError
from bank_utils import BankUtils
import readline

class InvalidCommandError(VocabError):
    def __init__(self, message = ""):
        self.message = message

    def __str__(self):
        return self.message

class CommandKind(Enum):
    REVERSE_WORD_LOOKUP = 'reverse-word-lookup'

    WORD_DELETE = 'word-obj-delete'
    WORD_LOOKUP = 'word-obj-lookup'
    WORD_ASSIGN = 'word-obj-assign'
    WORD_REMOVE = 'word-obj-remove'
    WORD_APPEND = 'word-obj-append'
    WORD_OPPOSITE_SEX = 'word-opposite-sex'

    TAG_CLEAR = 'tag-obj-clear'
    TAG_DELETE = 'tag-obj-delete'
    TAG_LOOKUP = 'tag-obj-lookup'
    TAG_ASSIGN = 'tag-obj-assign'
    TAG_REMOVE = 'tag-obj-remove'
    TAG_APPEND = 'tag-obj-append'

    PHRASE_LOOKUP = 'phrase-lookup'
    REVERSE_PHRASE_LOOKUP = 'reverse-phrase-lookup'

    COURSE_LOOKUP = 'course-lookup'
    COURSE_DELETE = 'course-delete'
    COURSE_CREATE = 'course-create'
    COURSE_CHANGE = 'course-change'
    COURSE_CLEAR  = 'course-clear'
    COURSE_PUT    = 'course-put'
    COURSE_REMOVE = 'course-remove'

    SAVE = 'save'
    LOAD = 'load'
    EXIT = 'exit'
    CONTINUE = 'continue'
    LIST = 'list'

class CommandParser():
    def __init__(self, bank):
        self.bank = bank

    def complete(self, text, state):
        if state == 0:
            self.matches = []
            line = readline.get_line_buffer()
            sline = line.lstrip()
            if text and text.startswith('#'):
                if not line.startswith('cd '):
                    if not self.bank.tags.complete_tags(text, self.matches):
                        return None

            if sline.startswith('?'):
                if not self.bank.words.reverse_complete(sline, text, self.matches):
                    return None
            elif sline.startswith('more ') or sline.startswith('put '):
                if not self.bank.words.complete(sline, text, self.matches):
                    return None
            elif sline.startswith('cd '):
                if not self.bank.courses.complete(text, self.matches):
                    return None
        try:
            return self.matches[state]
        except IndexError:
            return None

    def list_command(self, original, cmd, options):
        return [CommandKind.LIST, options, cmd]

    def reverse_word_lookup(self, original, cmd, options):
        words = [a.strip() for a in cmd.split(',')]
        return [CommandKind.REVERSE_WORD_LOOKUP, options, words]

    def word_lookup(self, original, cmd, options):
        words = [a.strip() for a in cmd.split(',')]
        r = []
        for word in words:
            if not WordObjName.is_valid(word):
                raise InvalidWordObjName("Not a valid word name: " + word + " in '" + original + "'")
            r.append(WordObjName.parse_object_name(word))

        return [CommandKind.WORD_LOOKUP, options,  r]

    def word_delete(self, original, cmd, options):
        words = [a.strip() for a in cmd.split(',')]
        r = []
        for word in words:
            if not WordObjName.is_valid(word):
                raise InvalidWordObjName("Not a valid word name: " + word + " in '" + original + "'")
            r.append(WordObjName.parse_object_name(word))

        return [CommandKind.WORD_DELETE, options, r]

    def tag_lookup(self, original, cmd, options):
        tags = [a.strip() for a in cmd.split(',')]
        r = []
        for tag in tags:
            if not TagObjName.is_valid(tag):
                raise InvalidTagObjName("Not a valid tag name: " + tag + " in '" + original + "'")
            r.append(TagObjName.parse_object_name(tag))

        return [CommandKind.TAG_LOOKUP, options, r]

    def course_put(self, original, cmd, options):
        if not self.bank.course:
            print("WARNING: must first change to a course")
            return [CommandKind.CONTINUE]
        words = [a.strip() for a in cmd.split(',')]
        t = []
        w = []
        p = []
        for word in words:
            if word.startswith('#'):
                if not TagObjName.is_valid(word):
                    raise InvalidTagObjName("Not a valid tag name: " + word + " in '" + original + "'")
                t.append(word)
            else:
                if not WordObjName.is_valid(word):
                    raise InvalidWordObjName("Not a valid word name: " + word + " in '" + original + "'")
                w.append(word)

        return [CommandKind.COURSE_PUT, options, w, t, p]

    def course_remove(self, original, cmd, options):
        if not self.bank.course:
            print("WARNING: must first change to a course")
            return [CommandKind.CONTINUE]
        words = [a.strip() for a in cmd.split(',')]
        t = []
        w = []
        p = []
        for word in words:
            if word.startswith('#'):
                if not TagObjName.is_valid(word):
                    raise InvalidTagObjName("Not a valid tag name: " + word + " in '" + original + "'")
                t.append(word)
            else:
                if not WordObjName.is_valid(word):
                    raise InvalidWordObjName("Not a valid word name: " + word + " in '" + original + "'")
                w.append(word)

        return [CommandKind.COURSE_REMOVE, options, w, t, p]

    def tag_delete(self, original, cmd, options):
        if cmd.find(',') != -1:
            tags = [a.strip() for a in cmd.split(',')]
        else:
            tags = [a.strip() for a in cmd.split(' ')]
        r = []
        for tag in tags:
            if not TagObjName.is_valid(tag):
                raise InvalidTagObjName("Not a valid tag name: " + tag + " in '" + original + "'")
            r.append(TagObjName.parse_object_name(tag))

        return [CommandKind.TAG_DELETE, options, r]

    def tag_clear(self, original, cmd, options):
        if cmd.find(',') != -1:
            tags = [a.strip() for a in cmd.split(',')]
        else:
            tags = [a.strip() for a in cmd.split(' ')]
        r = []
        for tag in tags:
            if not TagObjName.is_valid(tag):
                raise InvalidTagObjName("Not a valid tag name: " + tag + " in '" + original + "'")
            s = TagObjName.parse_object_name(tag)
            if s[2] != 0 or s[4] != 0:
                raise InvalidCommandError("Can not clear an indexed item here: " + original)
            r.append(s)

        return [CommandKind.TAG_CLEAR, options, r]

    def reverse_phrase_lookup(self, original, cmd, options):
        return [CommandKind.REVERSE_PHRASE_LOOKUP, options, cmd]

    def phrase_lookup(self, original, cmd, options):
        return [CommandKind.PHRASE_LOOKUP, options, cmd]

    def phrase_delete(self, original, cmd, options):
        return [CommandKind.PHRASE_DELETE, options, cmd]

    def course_lookup(self, original, cmd, options):
        return [CommandKind.COURSE_LOOKUP, options, cmd]

    def course_delete(self, original, cmd, options):
        return [CommandKind.COURSE_DELETE, options, cmd]

    def course_clear(self, original, cmd, options):
        return [CommandKind.COURSE_CLEAR, options, cmd]

    def course_create(self, original, cmd, options):
        return [CommandKind.COURSE_CREATE, options, cmd]

    def course_change(self, original, cmd, options):
        return [CommandKind.COURSE_CHANGE, options, cmd]

    def word_opposite_sex(self, original, cmd, symbol, cmd_kind, kind = WordKind.Unknown):
        idx = cmd.find(symbol)
        symbol_len = len(symbol)

        if idx == 0 or idx == (len(cmd) - symbol_len):
            raise InvalidCommandError("Invalid command: " + original)

        value = cmd[idx+symbol_len:].strip()
        word = cmd[:idx].strip()

        if not WordObjName.is_valid(word):
            raise InvalidWordObjName("Not a valid word name: " + word + " in '" + original + "'")

        name, mkind, meaning_idx, attribute, attribute_idx = tuple(WordObjName.parse_object_name(word))
        if attribute:
            raise InvalidCommandError("Does not work with attributes: " + original)

        name2, mkind2, meaning_idx2, attribute, attribute_idx = tuple(WordObjName.parse_object_name(value))
        if attribute:
            raise InvalidCommandError("Does not work with attributes: " + original)

        return [cmd_kind, name, mkind, meaning_idx, name2, mkind2, meaning_idx2]

    def word_assignment(self, original, cmd, symbol, cmd_kind, kind = WordKind.Unknown):
        idx = cmd.find(symbol)
        symbol_len = len(symbol)

        if idx == 0 or idx == (len(cmd) - symbol_len):
            raise InvalidCommandError("Invalid command: " + original)

        value = cmd[idx+symbol_len:].strip()
        word = cmd[:idx].strip()

        if not WordObjName.is_valid(word):
            raise InvalidWordObjName("Not a valid word name: " + word + " in '" + original + "'")

        name, mkind, meaning_idx, attribute, attribute_idx = tuple(WordObjName.parse_object_name(word))

        if mkind != WordKind.Unknown:
            if kind == WordKind.Unknown:
                kind = mkind
            elif kind != mkind:
                raise VocabError("Mismatched kind for : " + word + " in " + original)

        if attribute:
            if WordAttribs.is_list(attribute):
                values = BankUtils.dequote_and_split(value)
            else:
                values = [value]
            if attribute_idx != 0 and cmd_kind != CommandKind.WORD_ASSIGN:
                raise InvalidCommandError("Can not remove or append to an attribute value: " + original)
        else:
            values = [a.strip() for a in value.split(',')]

        return [cmd_kind, name, kind, meaning_idx, attribute, attribute_idx, values]

    def tag_assignment(self, original, cmd, symbol, cmd_kind):
        idx = cmd.find(symbol)
        if idx == -1:
            raise InvalidCommandError("Invalid tag command " + original)

        symbol_len = len(symbol)

        if idx == 0 or idx == (len(cmd) - symbol_len):
            raise InvalidCommandError("Invalid command: " + original)

        value = cmd[idx+symbol_len:].strip()
        tag = cmd[:idx].strip()

        if not TagObjName.is_valid(tag):
            raise InvalidTagObjName("Not a valid tag name: " + tag + " in '" + original + "'")

        base_name, sub_name, word_idx, attrib, attrib_idx = tuple(TagObjName.parse_object_name(tag))

        if attrib:
            if TagAttribs.is_list(attrib):
                values = BankUtils.dequote_and_split(value)
            else:
                values = [value]
        else:
            values = [a.strip() for a in value.split(',')]

        return [cmd_kind, base_name, sub_name, word_idx, attrib, attrib_idx, values]

    def parse_command(self, original):
        # remove any double space
        cmd = ' '.join(original.strip().split())
        if cmd.startswith('#'):
            if cmd.find('-=') != -1:
                r = self.tag_assignment(original, cmd, '-=', CommandKind.TAG_REMOVE)
                if r[3] != 0:
                    raise InvalidCommandError("Can not remove something from a tag member")
                return r
            elif cmd.find('+=') != -1:
                r = self.tag_assignment(original, cmd, '+=', CommandKind.TAG_APPEND)
                if r[3] != 0:
                    raise InvalidCommandError("Can not append something to a tag member")
                return r
            elif cmd.find('=') != -1:
                return self.tag_assignment(original, cmd, '=', CommandKind.TAG_ASSIGN)
            else:
                idx = cmd.find(' ')
                if idx == -1:
                    return self.tag_lookup(original, cmd, "")
                else:
                    cmd = cmd[:idx] + ' = ' + cmd[idx+1:]
                    return self.tag_assignment(original, cmd, '=', CommandKind.TAG_ASSIGN)

        elif cmd.startswith('('):
            idx = cmd.find(')')
            if idx == -1:
                raise InvalidCommandError("Not closed parentheses: " + original)
            kind = WordKind.parse(cmd[1:idx].strip())
            cmd = cmd[idx+1:]
            if cmd.find('-=') != -1:
                return self.word_assignment(original, cmd, '-=', CommandKind.WORD_REMOVE, kind)
            elif cmd.find('+=') != -1:
                return self.word_assignment(original, cmd, '+=', CommandKind.WORD_APPEND, kind)
            else:
                return self.word_assignment(original, cmd, '=', CommandKind.WORD_ASSIGN, kind)

        elif cmd.find("==") != -1:

            return self.word_opposite_sex(original, cmd, '==', CommandKind.WORD_OPPOSITE_SEX)

        elif cmd.find("=") != -1:
            if cmd.find('-=') != -1:
                return self.word_assignment(original, cmd, '-=', CommandKind.WORD_REMOVE)
            elif cmd.find('+=') != -1:
                return self.word_assignment(original, cmd, '+=', CommandKind.WORD_APPEND)
            else:
                return self.word_assignment(original, cmd, '=', CommandKind.WORD_ASSIGN)

        elif cmd.startswith('load '):

            return [CommandKind.LOAD, cmd[5:].strip()]

        elif cmd == 'save':

            return [CommandKind.SAVE]

        elif cmd == 'exit':

            return [CommandKind.EXIT]

        elif cmd.startswith('mkdir '):

            return self.course_create(original, cmd[6:], "")

        elif cmd.startswith('cd '):

            return self.course_change(original, cmd[3:], "")

        elif cmd == 'cd':

            return self.course_change(original, '', "")

        elif cmd == 'clear' and self.bank.course:

            return self.course_clear(original, self.bank.course.name, "")

        elif cmd.startswith('clear '):
            cmd = cmd[6:]
            options = ""
            if cmd.startswith('-'):
                idx = cmd.find(' ')
                if idx == -1:
                    raise InvalidCommandError("More requires an argument: " + original)
                options = cmd[1:idx]
                cmd = cmd[idx+1]

            if cmd.startswith('#'):
                return self.tag_clear(original, cmd, options)
            elif cmd.startswith('$'):
                return self.course_clear(original, cmd[1:].strip(), options)

        elif cmd.startswith('put '):
            cmd = cmd[4:]
            options = ""
            if cmd.startswith('-'):
                idx = cmd.find(' ')
                if idx == -1:
                    raise InvalidCommandError("put requires an argument: " + original)
                options = cmd[1:idx]
                cmd = cmd[idx+1]

            return self.course_put(original, cmd, options)

        elif cmd.startswith('remove '):
            cmd = cmd[7:]
            options = ""
            if cmd.startswith('-'):
                idx = cmd.find(' ')
                if idx == -1:
                    raise InvalidCommandError("put requires an argument: " + original)
                options = cmd[1:idx]
                cmd = cmd[idx+1]

            return self.course_remove(original, cmd, options)

        elif cmd.startswith('del '):
            cmd = cmd[5:]
            options = ""
            if cmd.startswith('-'):
                idx = cmd.find(' ')
                if idx == -1:
                    raise InvalidCommandError("rm requires an argument: " + original)
                options = cmd[1:idx]
                cmd = cmd[idx+1]

            if cmd.startswith('#'):
                return self.tag_delete(original, cmd, options)
            elif cmd.startswith('$'):
                return self.course_delete(original, cmd[1:].strip(), options)
            elif cmd.startswith('>'):
                return self.phrase_delete(original, cmd[1:].strip(), options)
            else:
                return self.word_delete(original, cmd, options)

        elif cmd.startswith('ls'):
            cmd = cmd[2:]
            if cmd.startswith(' '):
                cmd = cmd[1:]
            options = ""
            if cmd.startswith('-'):
                idx = cmd.find(' ')
                if idx != -1:
                    options = cmd[1:idx]
                    cmd = cmd[idx+1:]
                else:
                    options = cmd[1:]
                    cmd = ""

            return self.list_command(original, cmd, options)
        elif cmd.startswith('more '):
            cmd = cmd[5:]
            options = ""
            if cmd.startswith('-'):
                idx = cmd.find(' ')
                if idx == -1:
                    raise InvalidCommandError("More requires an argument: " + original)
                options = cmd[1:idx]
                cmd = cmd[idx+1:]

            if cmd.startswith('#'):
                return self.tag_lookup(original, cmd, options)
            elif cmd.startswith('$'):
                return self.course_lookup(original, cmd[1:].strip(), options)
            elif cmd.startswith('>'):
                return self.phrase_lookup(original, cmd[1:].strip(), options)
            elif cmd.startswith('<'):
                return self.reverse_phrase_lookup(original, cmd[1:].strip(), options)
            elif cmd.startswith('?'):
                return self.reverse_word_lookup(original, cmd[1:].strip(), options)
            else:
                return self.word_lookup(original, cmd, options)

        elif cmd.startswith('?'):

            return self.reverse_word_lookup(original, cmd[1:].strip(), "")

        elif cmd.startswith('<'):

            return self.reverse_phrase_lookup(original, cmd[1:].strip(), "")

        elif cmd.startswith('>'):

            return self.phrase_lookup(original, cmd[1:].strip(), "")

        elif len(cmd) > 0:

            space_count = cmd.count(' ')

            if space_count > 3:
                return self.phrase_lookup(original, cmd, "")
            elif space_count == 3:
                if cmd.startswith('el ') or cmd.startswith('los ') or cmd.startswith('la ') or cmd.startswith('las '):
                    return self.word_lookup(original, cmd, "")
                else:
                    return self.phrase_lookup(original, cmd, "")
            else:
                return self.word_lookup(original, cmd, "")
        else:
            return [CommandKind.CONTINUE]

class Testing(unittest.TestCase):
    def check(self, commands):
        parser = CommandParser(None)
        for key in commands:
            r = parser.parse_command(key)
            try:
                assert r == commands[key]
            except:
                print(r)
                print(commands[key])
                assert False

    def test_lookups(self):
        commands = {
            "?mother": [CommandKind.REVERSE_WORD_LOOKUP, "", ["mother"]],
            "el padre": [CommandKind.WORD_LOOKUP, "", [["padre", WordKind.Male, 0, None, 0]]],
            "mi madre es inteligente": [CommandKind.PHRASE_LOOKUP, "", 'mi madre es inteligente'],
        }
        self.check(commands)

    def test_word_assignment(self):
        commands = {
            "el padre = father": [CommandKind.WORD_ASSIGN, "padre", WordKind.Male, 0, None, 0, ["father"]],
            "el padre=father": [CommandKind.WORD_ASSIGN, "padre", WordKind.Male, 0, None, 0, ["father"]],
            "(adj) inteligente = intelligent": [CommandKind.WORD_ASSIGN, "inteligente", WordKind.Adjective, 0, None, 0, ["intelligent"]],
            "el padre[1]=father": [CommandKind.WORD_ASSIGN, "padre", WordKind.Male, 1, None, 0, ["father"]],
            "el padre +=father": [CommandKind.WORD_APPEND, "padre", WordKind.Male, 0, None, 0, ["father"]],
            "el padre -=father": [CommandKind.WORD_REMOVE, "padre", WordKind.Male, 0, None, 0, ["father"]],
            "el padre-=father": [CommandKind.WORD_REMOVE, "padre", WordKind.Male, 0, None, 0, ["father"]],
            "el padre[1]:IMAGES = '/path/to/image.file'": [CommandKind.WORD_ASSIGN, "padre", WordKind.Male, 1, WordAttribs.IMAGES, 0, ['/path/to/image.file']],
            "el padre[1]:IMAGES -= '/path/to/image.file'": [CommandKind.WORD_REMOVE, "padre", WordKind.Male, 1, WordAttribs.IMAGES, 0, ['/path/to/image.file']],
            "el padre[1]:IMAGES += '/path/to/image.file'": [CommandKind.WORD_APPEND, "padre", WordKind.Male, 1, WordAttribs.IMAGES, 0, ['/path/to/image.file']],
        }
        self.check(commands)

        parser = CommandParser(None)
        try:
            r = parser.parse_command("el padre[1]:IMAGES[1] -= '/path/to/image.file'")
            assert r == [CommandKind.WORD_REMOVE, "padre", WordKind.Male, 1, WordAttribs.IMAGES, 0, ['/path/to/image.file']]
        except InvalidCommandError as e:
            assert e.message == "Can not remove or append to an attribute value: el padre[1]:IMAGES[1] -= '/path/to/image.file'"

        try:
            r = parser.parse_command("el padre&[1]:IMAGES[1] -= '/path/to/image.file'")
            assert r == [CommandKind.WORD_REMOVE, "padre&", 1, WordKind.Male, WordAttribs.IMAGES, 0, ['/path/to/image.file']]
        except InvalidWordObjName as e:
            assert e.message == "Not a valid word name: el padre&[1]:IMAGES[1] in 'el padre&[1]:IMAGES[1] -= '/path/to/image.file''"

        try:
            r = parser.parse_command("= '/path/to/image.file'")
            assert r == [CommandKind.WORD_REMOVE, "", WordKind.Unknown, 0, None, 0, ['/path/to/image.file']]
        except InvalidCommandError as e:
            assert e.message == "Invalid command: = '/path/to/image.file'"

        try:
            r = parser.parse_command("el padre =")
            assert r == [CommandKind.WORD_REMOVE, "padre", WordKind.Male, 0, None, 0, ['']]
        except InvalidCommandError as e:
            assert e.message == "Invalid command: el padre ="

    def test_tag_assignment(self):
        commands = {
            "#family el padre": [CommandKind.TAG_ASSIGN, "family", '*', 0, None, 0, ["el padre"]],
            "#family-beginner el padre": [CommandKind.TAG_ASSIGN, "family", 'beginner', 0, None, 0, ["el padre"]],
            "#family-beginner[1] el padre": [CommandKind.TAG_ASSIGN, "family", 'beginner', 1, None, 0, ["el padre"]],
            "#family-beginner = el padre, la madre": [CommandKind.TAG_ASSIGN, "family", 'beginner', 0, None, 0, ["el padre", "la madre"]],
            "#family-beginner += la madre": [CommandKind.TAG_APPEND, "family", 'beginner', 0, None, 0, ["la madre"]],
            "#family-beginner": [CommandKind.TAG_LOOKUP, "", [["family", 'beginner', 0, None, 0]]],
        }
        self.check(commands)

        parser = CommandParser(None)
        try:
            r = parser.parse_command("#family-beginner[1]:IMAGES el padre")
            assert r == [CommandKind.TAG_ASSIGN, "family", 'beginner', 1, TagAttribs.IMAGES, 0, ["el padre"]]
        except InvalidTagObjName as e:
            assert e.message == "Not a valid tag name: #family-beginner[1]:IMAGES in '#family-beginner[1]:IMAGES el padre'"

        try:
            r = parser.parse_command("#family-beginner[1]:IMAGES el padre")
            assert r == [CommandKind.TAG_ASSIGN, "family", 'beginner', 1, TagAttribs.IMAGES, 0, ["el padre"]]
        except InvalidTagObjName as e:
            assert e.message == "Not a valid tag name: #family-beginner[1]:IMAGES in '#family-beginner[1]:IMAGES el padre'"

    def test_tag_delete(self):
        commands = {
            "rm #family": [CommandKind.TAG_DELETE, '', [["family", '*', 0, None, 0]]],
            "rm #family-beginner": [CommandKind.TAG_DELETE, '', [["family", 'beginner', 0, None, 0]]],
            "rm #family-beginner:IMAGES": [CommandKind.TAG_DELETE, '', [["family", 'beginner', 0, TagAttribs.IMAGES, 0]]],
            "rm #family-beginner[2]": [CommandKind.TAG_DELETE, '', [["family", 'beginner', 2, None, 0]]],
            "rm #family-beginner[2], #family-advanced:IMAGES": [CommandKind.TAG_DELETE, '', [["family", 'beginner', 2, None, 0], ["family", 'advanced', 0, TagAttribs.IMAGES, 0]]],
            "rm #family-beginner[2] #family-advanced:IMAGES": [CommandKind.TAG_DELETE, '', [["family", 'beginner', 2, None, 0], ["family", 'advanced', 0, TagAttribs.IMAGES, 0]]],
        }
        self.check(commands)

    def test_word_delete(self):
        commands = {
            "rm el padre": [CommandKind.WORD_DELETE, '', [["padre", WordKind.Male, 0, None, 0]]],
            "rm el padre[1]": [CommandKind.WORD_DELETE, '', [["padre", WordKind.Male, 1, None, 0]]],
            "rm el padre[1]:IMAGES": [CommandKind.WORD_DELETE, '', [["padre", WordKind.Male, 1, WordAttribs.IMAGES, 0]]],
            "rm el padre[1]:IMAGES, la madre": [CommandKind.WORD_DELETE, '', [["padre", WordKind.Male, 1, WordAttribs.IMAGES, 0], ["madre", WordKind.Female, 0, None, 0]]],
        }
        self.check(commands)

if __name__ == 'main':
    test_suite = unittest.TestSuite()
    test_suite.addTest(Testing("test_word_assignment"))
    test_suite.addTest(Testing("test_tag_assignment"))
    test_suite.addTest(Testing("test_lookups"))
    test_suite.addTest(Testing("test_tag_delete"))
    test_suite.addTest(Testing("test_word_delete"))
    runner = unittest.TextTestRunner()
    runner.run(test_suite)


import itertools
from collections import defaultdict

from noise_robust_cobras.noise_robust import find_cycles
from noise_robust_cobras.noise_robust.datastructures.cycle import Cycle

class CycleIndex:
    """
        Cycle index is a class that keeps track of a set of cycles
        Cycles are added through add_cycle_to_index and removed with remove_cycle

        attributes:
        - cycle-index a dictionary that maps a constraint to all cycles that involve this constraint
        - all consistent cycles: all cycles in this cycle index that are consistent (#CL's != 1)
        - all inconsistent cycles: all cycles in this cycle index that are inconsistent (#CL == 1)

        Specific subclasses are provided to keep track of specific classes of cycles
    """

    def __init__(self, constraint_index):
        self.constraint_index = constraint_index
        self.cycle_index = defaultdict(CycleIndex.set_tuple)
        self.all_consistent_cycles = set()
        self.all_inconsistent_cycles = set()

    def replace_constraint(self, old_constraint, new_constraint):
        all_cycles_with_constraint = self.get_all_cycles_for_constraint(old_constraint)
        new_cycles_with_constraint = [
            cycle.replace_constraint(old_constraint, new_constraint)
            for cycle in all_cycles_with_constraint
        ]
        for cycle_to_remove in all_cycles_with_constraint:
            self.remove_cycle(cycle_to_remove)
        for cycle in new_cycles_with_constraint:
            self.add_cycle_to_index(cycle)

    @staticmethod
    def set_tuple():
        return (set(), set())

    def is_inconsistent(self):
        return len(self.all_inconsistent_cycles) > 0

    def __contains__(self, item):
        return (
            item in self.all_consistent_cycles or item in self.all_inconsistent_cycles
        )

    def all_cycles(self):
        return self.all_inconsistent_cycles.union(self.all_consistent_cycles)

    def get_all_cycles_for_constraint(self, constraint):
        con_cycles, incon_cycles = self.cycle_index[constraint]
        return con_cycles.union(incon_cycles)

    def get_inconsistent_cycles_for_constraint(self, constraint):
        _, incon_cycles = self.cycle_index[constraint]
        return incon_cycles

    def get_consistent_cycles_for_constraint(self, constraint):
        con_cycles, _ = self.cycle_index[constraint]
        return con_cycles

    def add_cycle_to_index_entry(self, cycle, constraint):
        consistent_cycles, inconsistent_cycles = self.cycle_index[constraint]
        if cycle.is_inconsistent():
            inconsistent_cycles.add(cycle)
        else:
            consistent_cycles.add(cycle)

    def add_cycle_to_index(self, cycle):
        """
        - inconsistent cycles are added to all_inconsistent_cycles and the inconsistent_cycle_index
        - consistent cycles are added to all_cycles and the cycle_index
        """

        assert cycle
        # add cycle to cycle_index
        for constraint in cycle.constraints:
            self.add_cycle_to_index_entry(cycle, constraint)

        # add cycle to all_inconsistent_cycles or all_consistent_cycles
        if cycle.is_inconsistent():
            self.all_inconsistent_cycles.add(cycle)
        else:
            # the cycle is consistent
            self.all_consistent_cycles.add(cycle)

    def remove_cycle(self, cycle_to_remove):
        self.all_consistent_cycles.discard(cycle_to_remove)
        self.all_inconsistent_cycles.discard(cycle_to_remove)

        for con in cycle_to_remove:
            consistent, inconsistent = self.cycle_index[con]
            consistent.discard(cycle_to_remove)
            inconsistent.discard(cycle_to_remove)

    def remove_cycles_with_constraint(self, constraint_to_remove):
        con_cycles, incon_cycles = self.cycle_index[constraint_to_remove]
        self.all_consistent_cycles.difference_update(con_cycles)
        self.all_inconsistent_cycles.difference_update(incon_cycles)
        self.cycle_index.pop(constraint_to_remove)

class MinimalCycleIndex(CycleIndex):
    """
        Through add constraint keeps track of all the minimal cycles in the graph
        (for each constraint only the cycles are kept with the minimal length)

        note: old cycles that are not minimal are not removed from this datastructure

        constraints should be added through add_constraint  and removed through remove_cycles_with_constraint to ensure consistency of the data structure
    """

    def __init__(self, constraint_index):
        super().__init__(constraint_index)
        # minimal cycles dict is a dictionary from a constraint to a set of cycles
        # it keeps the cycles that need to be retained for this constraint
        self.minimal_cycles_dict = defaultdict(set)

    def add_constraint(self, constraint):
        all_cycles = find_cycles.find_all_cycles_with_minimal_length(
            self.constraint_index, constraint
        )
        if all_cycles is not None:
            self.add_minimal_cycles_to_index(all_cycles)

    def add_minimal_cycles_to_index(self, cycles):
        minimal_length = len(cycles[0])
        assert all(len(cycle) == minimal_length for cycle in cycles)
        # add the cycles to the index
        for cycle in cycles:
            self.add_cycle_to_index(cycle)

        minimal_cycle: Cycle = cycles[0]
        # remove longer cycles and add smaller minimal cycles
        # this does nothing!
        constraints_that_occur_in_short_cycle = minimal_cycle.constraints

        cycles_to_check = {
            cycle
            for con in constraints_that_occur_in_short_cycle
            for cycle in self.get_all_cycles_for_constraint(con)
            if len(cycle) > minimal_length
        }
        for old_cycle in cycles_to_check:
            composition = minimal_cycle.compose_with(old_cycle)
            if composition is not None and len(composition) < len(old_cycle):
                if composition not in self:
                    self.add_cycle_to_index(composition)

    def add_cycle_to_index(self, cycle):
        super(MinimalCycleIndex, self).add_cycle_to_index(cycle)
        self.add_cycle_to_minimal_cycle_dict(cycle)

    def check_cycles_for_removal(self, cycles):
        for cycle in cycles:
            if not self.is_minimal_cycle(cycle):
                self.remove_cycle(cycle)

    def add_cycle_to_minimal_cycle_dict(self, cycle):
        for constraint in cycle.constraints:
            existing_entry = self.minimal_cycles_dict[constraint]
            if len(existing_entry) == 0:
                self.minimal_cycles_dict[constraint].add(cycle)
            else:
                # you should keep the old cycle to ensure you have an inconsistent cycle
                some_cycle = list(existing_entry)[0]
                old_length = len(some_cycle)
                new_length = len(cycle)
                if new_length < old_length:
                    old_cycles = self.minimal_cycles_dict[constraint]
                    self.minimal_cycles_dict[constraint] = {cycle}
                    self.check_cycles_for_removal(old_cycles)
                elif new_length == old_length:
                    self.minimal_cycles_dict[constraint].add(cycle)
                else:
                    # new_length > old_length
                    pass

    def is_minimal_cycle(self, cycle):
        for constraint in cycle.constraints:
            if cycle in self.minimal_cycles_dict[constraint]:
                return True
        return False

    def remove_cycles_with_constraint(self, constraint_to_remove):
        involved_cycles = self.get_all_cycles_for_constraint(constraint_to_remove)
        new_cycles = []
        for cycle1, cycle2 in itertools.combinations(involved_cycles, 2):
            new_cycle = cycle1.compose_with(cycle2)
            if new_cycle is None:
                continue
            new_cycles.append(new_cycle)

        for cycle in involved_cycles:
            self.remove_cycle(cycle)

        for new_cycle in new_cycles:
            self.add_cycle_to_index(new_cycle)

        self.cycle_index.pop(constraint_to_remove)

    def remove_cycle(self, cycle_to_remove):
        super(MinimalCycleIndex, self).remove_cycle(cycle_to_remove)
        self.remove_cycle_from_minimal_cycle_dict(cycle_to_remove)

    def remove_cycle_from_minimal_cycle_dict(self, cycle_to_remove):
        for con in cycle_to_remove:
            entry = self.minimal_cycles_dict[con]
            entry.discard(cycle_to_remove)


from mpl_toolkits.basemap import Basemap, cm, shiftgrid,interp
from netCDF4 import Dataset as NetCDFFile
import numpy as N
import math
import matplotlib.pyplot as plt
import glob
import numpy.ma as ma
from scipy.interpolate import griddata
import scipy.stats
from matplotlib.dates import DateFormatter
import datetime
from statsmodels.stats.weightstats import DescrStatsW
import matplotlib.colors as colors

nclu=NetCDFFile('/scratch2/scratchdirs/tslin2/plot/globalcrop/data/m3yield_isam.nc','r')
ncvar_maize = nclu.variables['maizey'][0,:,:]
lonab = nclu.variables["lon"][:]

region1=NetCDFFile('/scratch2/scratchdirs/tslin2/plot/globalcrop/data/clm/RCP45_crop_150901.nc','r')
maitrop = region1.variables['maize_trop'][4,:,:]
maitemp = region1.variables['maize_temp'][4,:,:]
maitropi = region1.variables['maize_trop_irrig'][4,:,:]
maitempi = region1.variables['maize_temp_irrig'][4,:,:]
maitrop= ma.masked_where(maitrop<=0,maitrop)
maitropi= ma.masked_where(maitropi<=0,maitropi)
maitemp= ma.masked_where(maitemp<=0,maitemp)
maitempi= ma.masked_where(maitempi<=0,maitempi)
maitrop=ma.filled(maitrop, fill_value=0.)
maitropi=ma.filled(maitropi,fill_value=0.)
maitemp=ma.filled(maitemp, fill_value=0.)
maitempi=ma.filled(maitempi, fill_value=0.)
maizeto = maitrop+maitemp
maizetoi = maitropi+maitempi
maitoatemp=maitemp+maitempi
maitoatrop=maitrop+maitropi
maizetotal = maizeto+maizetoi

isam=NetCDFFile('/scratch2/scratchdirs/tslin2/isam/cheyenne/plot/finalyield/clm/clm45his_maiscaleifyield.nc','r')
isamyield = N.average(isam.variables['yield'][95:105,:,:],axis=0)
lona1 = isam.variables["lon"][:]
lata1 = isam.variables["lat"][:]
lon2,lat2 = N.meshgrid(lona1,lata1)

isamyield[N.isnan(isamyield)] = -9999
isamyield = ma.masked_where(isamyield<=0,isamyield)
isamyield = ma.masked_where(maizetotal<=0,isamyield)
#isamyield=ma.filled(isamyield, fill_value=0.)

yieldfa= N.zeros((10, 360, 720))
yieldf2a= N.zeros((10, 360, 720))
yieldf= N.zeros((10, 360, 720))
yieldf2= N.zeros((10, 360, 720))
years2 = range(2090,2100)

clmtropfin= N.zeros((10, 360, 720))
clmtempfin= N.zeros((10, 360, 720))

for j in range(0,10):

        clm2n=NetCDFFile('/scratch2/scratchdirs/tslin2/plot/globalcrop/data/clm/clm45rcp45/maizetrop_rcp45_co2_rf_fert_0.5x0.5.nc','r')
        cc = N.flipud(clm2n.variables['yield'][84+j,:,:])
        clmtropfin[j,:,:] = cc

        clm3n=NetCDFFile('/scratch2/scratchdirs/tslin2/plot/globalcrop/data/clm/clm45rcp45/maizetemp_rcp45_co2_rf_fert_0.5x0.5.nc','r')
        dd = N.flipud(clm3n.variables['yield'][84+j,:,:])
        clmtempfin[j,:,:] = dd

yield_new2a=N.average(clmtropfin,axis=0)
yield_new2=N.average(clmtempfin,axis=0)

yield_new2= ma.masked_where(yield_new2<=0.,yield_new2)
yield_new2a= ma.masked_where(yield_new2a<=0.,yield_new2a)
yield_new2=ma.masked_where( maitoatemp<=0,yield_new2)
yield_new2=ma.filled(yield_new2, fill_value=0.)

yield_new2a=ma.masked_where( maitoatrop<=0,yield_new2a)
yield_new2a=ma.filled(yield_new2a, fill_value=0.)

yield_new2=yield_new2+yield_new2a

yield_new2=ma.masked_where(isamyield<=0.,yield_new2)
yieldisam=yield_new2-isamyield
yieldisam= ma.masked_where(yieldisam==0.,yieldisam)

ncvar_maize,lona11 = shiftgrid(180.5,ncvar_maize,lonab,start=False)
yieldisam= ma.masked_where(ncvar_maize<=0.,yieldisam)

fig = plt.figure(figsize=(12,6))

ax1 = fig.add_subplot(211)
ax1.set_title("isam mai yield 1996-2005 (t/ha)",fontsize=18)

map = Basemap(projection ='cyl', llcrnrlat=-65, urcrnrlat=90,llcrnrlon=-180, urcrnrlon=180, resolution='c')
#map = Basemap(llcrnrlon=-119,llcrnrlat=23,urcrnrlon=-63,urcrnrlat=51,projection='lcc',lat_1=33,lat_2=45,lon_0=-95)
map.drawcoastlines()
#map.drawstates()
#map.drawcountries(color='b')

x,y = map(lon2,lat2)

map.drawcoastlines()
map.drawcountries()
map.drawmapboundary()
cs = map.pcolormesh(x,y,isamyield,cmap=plt.cm.jet,norm=colors.PowerNorm(gamma=1./2.),vmin=0,vmax=16)
cbar = map.colorbar(cs,location='bottom',size="4%",pad="2%")
cbar.ax.tick_params(labelsize=12)
plt.axis('off')

ax2 = fig.add_subplot(212)
ax2.set_title("Maize RCP4.5",fontsize=18)
map = Basemap(projection ='cyl', llcrnrlat=-65, urcrnrlat=90,llcrnrlon=-180, urcrnrlon=180, resolution='c')
#map = Basemap(llcrnrlon=-119,llcrnrlat=23,urcrnrlon=-63,urcrnrlat=51,projection='lcc',lat_1=33,lat_2=45,lon_0=-95)
map.drawcoastlines(color='gray')
#map.drawstates()
#map.drawcountries(color='b')

#map.drawcoastlines()
#map.drawcountries()
#map.drawmapboundary(color='gray')
cs = map.pcolormesh(x,y,yieldisam/isamyield*100,cmap=plt.cm.bwr,vmin=-100.,vmax=100.)
cbar = map.colorbar(cs,location='bottom',size="4%",pad="2%")
cbar.ax.tick_params(labelsize=12)
plt.axis('off')
plt.tight_layout()

plt.savefig('mai2006_2015_rcp45_clmc.jpg',dpi=300,bbox_inches='tight')
plt.show()


from docker import Client
import pytest
from faker import Faker
from utils import retry
from factories import ContainerFactory, MasterFactory, MinionFactory

@pytest.fixture(scope="session")
def docker_client():
    client = Client(base_url='unix://var/run/docker.sock')
    return client

@pytest.fixture(scope="session")
def salt_root(tmpdir_factory):
    return tmpdir_factory.mktemp("salt")

@pytest.fixture(scope="session")
def pillar_root(salt_root):
    salt_root.mkdir('pillar')
    return '/etc/salt/pillar'

@pytest.fixture(scope="session")
def file_root(salt_root):
    salt_root.mkdir('topfiles')
    return '/etc/salt/topfiles'

@pytest.fixture(scope="module")
def salt_master_config(file_root, pillar_root):
    return {
        'base_config': {
            'hash_type': 'sha384',
            'pillar_roots': {
                'base': [pillar_root]
            },
            'file_roots': {
                'base': [file_root]
            }
        }
    }

@pytest.fixture(scope="module")
def salt_minion_config(master_container, salt_root, docker_client):
    return {
        'master': master_container['ip'],
        'hash_type': 'sha384',
    }

@pytest.fixture(scope="module")
def master_container_extras():
    return dict()

@pytest.fixture(scope="module")
def master_container(request, salt_root, master_container_extras, salt_master_config, docker_client):
    fake = Faker()
    obj = ContainerFactory(
        config__name='master_{0}_{1}'.format(fake.word(), fake.word()),
        config__salt_config__tmpdir=salt_root,
        docker_client=docker_client,
        config__salt_config__conf_type='master',
        config__salt_config__config=salt_master_config,
        config__salt_config__post__id='{0}_{1}'.format(fake.word(), fake.word()),
        **master_container_extras
    )
    request.addfinalizer(
        lambda: obj['docker_client'].remove_container(
            obj['config']['name'], force=True)
    )
    return obj

@pytest.fixture(scope="module")
def minion_container_extras():
    return dict()

@pytest.fixture(scope="module")
def minion_container(request, salt_root, minion_container_extras, salt_minion_config, docker_client):
    fake = Faker()
    obj = ContainerFactory(
        config__name='minion_{0}_{1}'.format(fake.word(), fake.word()),
        config__salt_config__tmpdir=salt_root,
        docker_client=docker_client,
        config__salt_config__conf_type='minion',
        config__salt_config__config={
            'base_config': salt_minion_config
        },
        **minion_container_extras
    )
    request.addfinalizer(
        lambda: obj['docker_client'].remove_container(
            obj['config']['name'], force=True))
    return obj

@pytest.fixture(scope="module")
def master(request, master_container):
    return MasterFactory(container=master_container)

@pytest.fixture(scope="module")
def minion(request, minion_container):
    out = MinionFactory(container=minion_container)
    return out

@pytest.fixture(scope='module')
def minion_key_cached(master, minion):

    def cache():
        return minion['id'] in master.salt_key(minion['id'])['minions_pre']

    assert retry(cache) is True

@pytest.fixture(scope='module')
def minion_key_accepted(master, minion, minion_key_cached):
    master.salt_key_accept(minion['id'])

    def accept():
        return minion['id'] in master.salt_key()['minions']

    assert retry(accept) is True


"""The local thread, based on the local Python interpreter thread.

The local thread is the thread that every rsyscall program has available from the
start. From this thread, we create all the others.

"""
from __future__ import annotations
from rsyscall.thread import Thread
from rsyscall.tasks.util import log_syscall, raise_if_error
from rsyscall._raw import ffi, lib # type: ignore
import trio
import rsyscall.far as far
from rsyscall.near.sysif import SyscallInterface, SyscallResponse
import rsyscall.near.types as near
import rsyscall.handle as handle
import rsyscall.loader as loader
import logging
import os
import typing as t
from dataclasses import dataclass
import rsyscall.memory.allocator as memory
from rsyscall.memory.ram import RAM
from rsyscall.memory.transport import MemoryTransport
from rsyscall.handle import Pointer, Task, MemoryMapping
from rsyscall.signal import SIG, Sigaction, Sighandler
from rsyscall.sys.socket import AF, SOCK
from rsyscall.network.connection import FDPassConnection
from rsyscall.environ import Environment
from rsyscall.loader import NativeLoader
from rsyscall.monitor import ChildProcessMonitor

from rsyscall.sys.epoll import EpollFlag
from rsyscall.epoller import Epoller

logger = logging.getLogger(__name__)

__all__ = [
    "thread",
]

async def _direct_syscall(number, arg1=0, arg2=0, arg3=0, arg4=0, arg5=0, arg6=0):
    "Make a syscall directly in the current thread."
    return lib.rsyscall_raw_syscall(arg1, arg2, arg3, arg4, arg5, arg6, number)

class LocalSyscallResponse(SyscallResponse):
    "Dummy SyscallResponse for local syscalls."
    def __init__(self, result_func: t.Callable[[], int]) -> None:
        self.result_func = result_func

    async def receive(self) -> int:
        return self.result_func()

class LocalSyscall(SyscallInterface):
    "Makes syscalls in the local, Python interpreter thread."
    def __init__(self) -> None:
        self.logger = logger

    def get_activity_fd(self) -> None:
        return None

    async def close_interface(self) -> None:
        pass

    async def submit_syscall(self, number, arg1=0, arg2=0, arg3=0, arg4=0, arg5=0, arg6=0) -> LocalSyscallResponse:
        """Make a syscall in the local thread; return SyscallResponse already containing the result.

        We can't actually implement the submit_syscall API for local syscalls, so we just
        immediately make the syscall and pack the response into a dummy SyscallResponse.

        Linux returns errors as just another kind of syscall return value. A certain range
        of possible syscall return values (-4095 to 0, not inclusive) indicates an error;
        we call raise_if_error to check and throw if we're in that range.

        """
        log_syscall(self.logger, number, arg1, arg2, arg3, arg4, arg5, arg6)
        result = await _direct_syscall(
            number,
            arg1=int(arg1), arg2=int(arg2), arg3=int(arg3),
            arg4=int(arg4), arg5=int(arg5), arg6=int(arg6))
        def f(result=result) -> int:
            raise_if_error(result)
            return result
        return LocalSyscallResponse(f)

class LocalMemoryTransport(MemoryTransport):
    "This is a memory transport that only works on local pointers."
    def __init__(self, local_task: Task) -> None:
        self.local_task = local_task

    def inherit(self, task: Task) -> LocalMemoryTransport:
        return self

    async def batch_write(self, ops: t.List[t.Tuple[Pointer, bytes]]) -> None:
        for dest, data in ops:
            if dest.mapping.task.address_space != self.local_task.address_space:
                raise Exception("trying to write to pointer", dest, "not in local address space")
            ffi.memmove(ffi.cast('void*', int(dest.near)), data, len(data))

    async def batch_read(self, ops: t.List[Pointer]) -> t.List[bytes]:
        ret: t.List[bytes] = []
        for src in ops:
            if src.mapping.task.address_space != self.local_task.address_space:
                raise Exception("trying to read from pointer", src, "not in local address space")
            buf = ffi.buffer(ffi.cast('void*', int(src.near)), src.size())
            ret.append(bytes(buf))
        return ret

async def _make_local_thread() -> Thread:
    """Create the local thread, allocating various resources locally.

    For the most part, the local thread is like any other thread; it just bootstraps
    differently, and uses syscall and memory interfaces which are specialized to the local
    thread.

    """
    process = near.Process(os.getpid())
    task = Task(
        LocalSyscall(), process, far.FDTable(process.id),
        far.AddressSpace(process.id),
        far.PidNamespace(process.id),
    )
    ram = RAM(task, LocalMemoryTransport(task), memory.AllocatorClient.make_allocator(task))
    epfd = await task.epoll_create()
    async def wait_readable():
        logger.debug("wait_readable(%s)", epfd.near.number)
        await trio.hazmat.wait_readable(epfd.near.number)
    epoller = Epoller.make_subsidiary(ram, epfd, wait_readable)
    thread = Thread(
        task, ram,
        await FDPassConnection.make(task, ram, epoller),
        NativeLoader.make_from_symbols(task, lib),
        epoller,
        await ChildProcessMonitor.make(ram, task, epoller),
        Environment(task, ram, {key.encode(): value.encode() for key, value in os.environ.items()}),
        stdin=task.make_fd_handle(near.FileDescriptor(0)),
        stdout=task.make_fd_handle(near.FileDescriptor(1)),
        stderr=task.make_fd_handle(near.FileDescriptor(2)),
    )
    return thread

async def _initialize() -> Thread:
    thr = await _make_local_thread()
    # Wipe out the SIGWINCH handler that the readline module installs.
    # We do this because otherwise this handler will be inherited down to our
    # children, where it will segfault on run due to the environment being
    # totally different (lacking TLS for one). I'm not sure if there's any
    # alternative; wiping out the signal handlers from within the children after
    # they've been created still leaves a window for the signal handler to run.
    import readline
    await thr.task.sigaction(SIG.WINCH, await thr.ram.ptr(Sigaction(Sighandler.DFL)), None)
    return thr

thread: Thread = trio.run(_initialize)
"The local thread, fully initialized at import time"


#!/usr/bin/env python

"""Pulls files from Box
"""
import sys
import os

from boxsdk import JWTAuth
from boxsdk import Client

BOX_CONFIG = os.getenv("BOX_CONFIG_FILE", "box.json")

# Make sure we have files to download
argc = len(sys.argv)
if argc < 2:
    print("No filenames specified on the command line to download")
    sys.exit(0)

def get_id_from_folder(boxclient, parent_id, item_name, item_type='folder'):
    """Retrieves the contents of a parent folder looking for a sub folder
    Args:
        boxclient(obj): the Box client instance
        parent_id(str): the ID of the parent folder
        item_name(str): the name of the item we're trying to find
        item_type(str): one of 'file' or 'folder'. Defaults to 'folder'
    Return:
        The ID of the found item. None is returned if there's a problem or the item
        name wasn't found in the parent folder
    """
    folder_list = boxclient.folder(folder_id=str(parent_id)).get()
    if not folder_list:
        return None
    if not 'item_collection' in folder_list:
        return None
    if not 'entries' in folder_list['item_collection']:
        return None

    for entry in folder_list['item_collection']['entries']:
        if 'name' in entry and 'type' in entry:
            if entry['name'] == item_name and entry['type'] == item_type:
                return entry['id']

    return None

# Authentication from settings file
box_auth = JWTAuth.from_settings_file(BOX_CONFIG)

# Get auth client
box_client = Client(box_auth)

# Loop through the arguments downloading the files
for idx in range(1, argc):
    # Get the filename to download and the path
    file_path = sys.argv[idx]
    filefolder, filename = os.path.split(file_path)
    if not filename:
        print("An invalid file download was requested: '" + file_path + "'")
        continue

    # Find the ID of the folder the file is to be in
    folder_id = "0"
    if filefolder:
        parts = filefolder.split('/')
        for one_folder in parts:
            folder_id = get_id_from_folder(box_client, folder_id, one_folder, item_type='folder')
            if not folder_id:
                break

    if not folder_id:
        print("Folder not found in Box: '" + file_path + "'")
        continue

    # Get the ID of the file
    file_id = get_id_from_folder(box_client, folder_id, filename, item_type='file')
    if not file_id:
        print("File name not found in folder: '" + file_path + "'")

    # Download the file
    # We use the box file name instead of our name so that the search for the correct
    # file can be changed if needed (to include regular expressions, for example)
    box_file = box_client.file(file_id=file_id).get()
    with open(box_file.name, 'wb') as output_file:
        box_file.download_to(output_file)


from teanaps import configure as con
from teanaps.visualization import GraphVisualizer

from gluonnlp.model import BERTModel, BERTEncoder
import gluonnlp as nlp
from mxnet.gluon import nn
import mxnet as mx
from mxnet import gluon

import numpy as np

class SentimentAnalysis():
    def __init__(self, model_path=con.SENTIMENT_MODEL_PATH, kobert_path=con.SENTIMENT_UTIL_PATH["kobert"]):
        self.ctx = mx.cpu()
        self.kobert_path = kobert_path
        bert_base, vocab = self.__get_kobert_model()
        self.model = BERTClassifier(bert_base, num_classes=2, dropout=0.1)
        self.model.load_parameters(model_path, ctx=self.ctx)
        tokenizer_path = con.SENTIMENT_UTIL_PATH["tokenizer"]
        self.tok = nlp.data.BERTSPTokenizer(tokenizer_path, vocab, lower=False)

    def tag(self, sentence):
        sentence_list =  [[sentence, '0']]
        bert_sentence_list = BERTDataset(sentence_list, 0, 1, self.tok,
                                         con.SENTIMENT_MODEL_CONFIG["max_len"], True, False)
        gluon_sentence_list = mx.gluon.data.DataLoader(bert_sentence_list, num_workers=5,
                                                       batch_size=int(con.SENTIMENT_MODEL_CONFIG["batch_size"]/2))
        for t, v, s, label in gluon_sentence_list:
            token_ids = t.as_in_context(self.ctx)
            valid_length = v.as_in_context(self.ctx)
            segment_ids = s.as_in_context(self.ctx)
            label = label.as_in_context(self.ctx)
            _, output = self.model(token_ids, segment_ids, valid_length.astype("float32"))
            for r in output:
                r = list(r)
                predict_value = r[0] if r[0] > r[1] else r[1]
                sentiment = r.index(predict_value)
                sentiment_label = "positive" if sentiment == 1 else "negative"
                return (r.index(predict_value), sentiment_label)

    def draw_weight(self, sentence):
        attn_data = self.__get_attention(self.model, sentence)
        gv = GraphVisualizer()
        x = attn_data["text"]
        y = x
        x_data = []
        y_data = []
        z_data = []
        for x_index in range(len(x)):
            if x[x_index].strip() == "":
                continue
            x_data.append("("+str(x_index)+")"+x[x_index])
            z_data.append(attn_data["attn"][x_index])
        data_meta_list = []
        data_meta = {
            "graph_type": "histogram",
            "data_name": "Y",
            "x_data": x_data,
            "y_data": z_data,
            "y_axis": "y2"
        }
        data_meta_list.append(data_meta)
        graph_meta = {
            "title": "BERT SENTIMENT WEIGHT",
            "x_tickangle": -45,
            "y1_tickangle": 0,
            "y2_tickangle": 0,
            "x_name": "TOKEN",
            "y1_name": "WEIGHT",
            "y2_name": "WEIGHT",
        }
        return gv.draw_histogram(data_meta_list, graph_meta)

    def get_weight(self, sentence):
        attn_data = self.__get_attention(self.model, sentence)
        gv = GraphVisualizer()
        token_list = attn_data["text"]
        weight_list = []
        for token_index in range(len(token_list)):
            if token_list[token_index].strip() == "":
                continue
            weight_list.append(attn_data["attn"][token_index])
        return {"token_list": token_list, "weight_list": weight_list}

    def __remove_char(self, tokens):
        return [t.replace('Ġ', ' ').replace('▁', ' ') for t in tokens]

    def __get_attention(self, model, sentence):
        sentence_list =  [[sentence, '0']]
        bert_sentence_list = BERTDataset(sentence_list, 0, 1, self.tok,
                                         con.SENTIMENT_MODEL_CONFIG["max_len"], True, False)
        gluon_sentence_list = mx.gluon.data.DataLoader(bert_sentence_list, num_workers=5,
                                                       batch_size=int(con.SENTIMENT_MODEL_CONFIG["batch_size"]/2))
        for t, v, s, label in gluon_sentence_list:
            token_ids = t.as_in_context(self.ctx)
            valid_length = v.as_in_context(self.ctx)
            segment_ids = s.as_in_context(self.ctx)
            label = label.as_in_context(self.ctx)
            attn, _ = self.model(token_ids, segment_ids, valid_length.astype("float32"))
            attn_list = list(attn[11][0][0][11][127][:v.asnumpy()[0]].asnumpy())
        token_list = self.tok(sentence)
        token_list = self.__remove_char(token_list)
        results = {
            'attn': attn_list,
            'text': token_list,
        }
        return results

    def __get_kobert_model(self):
        use_pooler=True
        use_decoder=False
        use_classifier=False
        vocab_path = con.SENTIMENT_UTIL_PATH["tokenizer"]
        vocab_b_obj = nlp.vocab.BERTVocab.from_sentencepiece(vocab_path, padding_token="[PAD]")
        predefined_args = con.SENTIMENT_BERT_CONFIG
        encoder = BERTEncoder(attention_cell=predefined_args["attention_cell"],
                              num_layers=predefined_args["num_layers"],
                              units=predefined_args["units"],
                              hidden_size=predefined_args["hidden_size"],
                              max_length=predefined_args["max_length"],
                              num_heads=predefined_args["num_heads"],
                              scaled=predefined_args["scaled"],
                              dropout=predefined_args["dropout"],
                              #output_attention=False, output_all_encodings=False,
                              output_attention=True, output_all_encodings=False,
                              use_residual=predefined_args["use_residual"])
        net = BERTModel(encoder, len(vocab_b_obj.idx_to_token),
                        token_type_vocab_size=predefined_args["token_type_vocab_size"],
                        units=predefined_args["units"],
                        embed_size=predefined_args["embed_size"],
                        embed_dropout=predefined_args["embed_dropout"],
                        word_embed=predefined_args["word_embed"],
                        use_pooler=use_pooler, use_decoder=use_decoder, use_classifier=use_classifier)
        net.initialize(ctx=self.ctx)
        net.load_parameters(self.kobert_path, self.ctx, ignore_extra=True)
        return (net, vocab_b_obj)

class BERTClassifier(nn.Block):
    def __init__(self, bert, num_classes=2, dropout=None, prefix=None, params=None):
        super(BERTClassifier, self).__init__(prefix=prefix, params=params)
        self.bert = bert
        with self.name_scope():
            self.classifier = nn.HybridSequential(prefix=prefix)
            if dropout:
                self.classifier.add(nn.Dropout(rate=dropout))
            self.classifier.add(nn.Dense(units=num_classes))

    def forward(self, inputs, token_types, valid_length=None):
        _, attn, pooler = self.bert(inputs, token_types, valid_length)
        return attn, self.classifier(pooler)

class BERTDataset(mx.gluon.data.Dataset):
    def __init__(self, dataset, sent_idx, label_idx, bert_tokenizer, max_len, pad, pair):
        transform = nlp.data.BERTSentenceTransform(bert_tokenizer, max_seq_length=max_len, pad=pad, pair=pair)
        sent_dataset = gluon.data.SimpleDataset([[i[sent_idx],] for i in dataset])
        self.sentences = sent_dataset.transform(transform)
        self.labels = gluon.data.SimpleDataset([np.array(np.int32(i[label_idx])) for i in dataset])

    def __getitem__(self, i):
        return (self.sentences[i] + (self.labels[i], ))

    def __len__(self):
        return (len(self.labels))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Sep  5 12:32:28 2020

@author: gac
"""

from multiprocessing import Process, Value, RawArray
from threading import Thread
import subprocess, time, os
# os.popen('sh /home/gac/nginx.sh')
import cv2
import numpy as np
import json
import paho.mqtt.client as mqtt

class playVideoOnDemand:
    def __init__(self, image_raw, cam_IP, flag):
        self.pushVideo_dict = dict()

        def on_connect(client, userdata, flags, rc):
            print("Connected with result code " + str(rc))

        def on_message(client, userdata, msg):
            self.pushVideo_dict = dict(json.loads(msg.payload.decode()))
            # print(msg.topic + " " + str(msg.payload))

        self.flag_dict = {}
        self.openVideo = []
        self.cam_IP = cam_IP
        self.client = mqtt.Client()
        self.client.on_connect = on_connect
        self.client.on_message = on_message
        self.client.connect("127.0.0.1", 1883, 60)
        self.client.subscribe('/Caeri/Push_Stream', qos=0)
        self.client.loop_start()
        while flag.value == 1:
            # print(self.pushVideo_dict)
            try:
                for cameraIp, para in list(self.pushVideo_dict.items()):
                    print(para.get('set'))
                    if para.get('set') == 1:
                        if cameraIp in self.openVideo or cameraIp != self.cam_IP:
                            time.sleep(0.5)
                            break
                        self.flag_dict[cameraIp] = 1
                        self.openVideo.append(cameraIp)
                        pushType = para.get('pushType')
                        pushUri = para.get('pushUri')
                        try:
                            pushTimeMin = int(para.get('pushTimeMin'))
                        except:
                            pushTimeMin = 10
                        if cameraIp and pushUri:
                            Thread(target=lambda: self.push_stream(cameraIp, image_raw, pushType, pushUri,
                                                                   pushTimeMin)).start()
                    else:
                        self.flag_dict[cameraIp] = 0
                        del self.pushVideo_dict[cameraIp]
            except:
                pass
            time.sleep(0.5)

    def push_stream(self, cameraIp, push_img_raw, pushType, pushUri, pushTimeMin):
        command = ['ffmpeg',
                   '-y',
                   '-f', 'rawvideo',
                   '-vcodec', 'rawvideo',
                   '-pix_fmt', 'bgr24',
                   '-s', "{}x{}".format(960, 540),
                   '-r', '25',
                   '-i', '-',
                   '-c:v', 'libx264',
                   '-pix_fmt', 'yuv420p',
                   '-preset', 'ultrafast',
                   '-f', 'flv',
                   pushUri]
        # 管道配置
        p = subprocess.Popen(command, stdin=subprocess.PIPE)
        del self.pushVideo_dict[cameraIp]
        t0 = time.time()
        while self.flag_dict[cameraIp]:
            t = time.time()
            if t - t0 < int(pushTimeMin) * 60:
                data = np.copy(np.ctypeslib.as_array(push_img_raw))
                height = data[0] + data[1] * 256
                if height == 0:
                    continue
                width = data[2] + data[3] * 256
                image = data[4:height * width * 3 + 4].reshape(height, width, 3)
                try:
                    image = cv2.resize(image, (960, 540))
                    p.stdin.write(image.tobytes())
                except:
                    continue
            else:
                self.openVideo.remove(cameraIp)
                break
            if time.time() - t < 1/25:
                time.sleep(1/25 - (time.time() - t))
        self.openVideo.remove(cameraIp)

def push_rtmp(filename, IP='127.0.0.1', shape=(640, 360)):
    rtmp = 'rtmp://116.63.160.171:31935/live/' + filename
    print(rtmp)
    command = ['ffmpeg',
               '-y',
               '-f', 'rawvideo',
               '-vcodec', 'rawvideo',
               '-pix_fmt', 'bgr24',
               '-s', "{}x{}".format(shape[0], shape[1]),
               # '-r', str(fps),
               '-i', '-',
               '-c:v', 'libx264',
               '-pix_fmt', 'yuv420p',
               '-preset', 'ultrafast',
               '-f', 'flv',
               rtmp]
    return subprocess.Popen(command, stdin=subprocess.PIPE)

def camera_push_screen(file_name, img_arr, flag, img_size=(640, 360)):
    tick = time.time()
    while flag:
        t1 = time.time()
        if 'push_img' not in vars():
            push_img = push_rtmp(filename=file_name)
        if subprocess.Popen.poll(push_img) != None:
            push_img.kill()
            del push_img
            continue
        if time.time() - tick > 10:
            print('push status:', subprocess.Popen.poll(push_img))
            tick = time.time()
        data = np.copy(np.ctypeslib.as_array(img_arr))
        height = data[0] + data[1] * 256
        width = data[2] + data[3] * 256
        img = data[4:height * width * 3 + 4].reshape(height, width, 3)
        if height == width == 0:
            # print('image error')
            continue
        else:
            show_image = cv2.resize(img, img_size)
        try:
            # cv2.imshow('a', show_image)
            # cv2.waitKey(1)
            push_img.stdin.write(show_image.tobytes())
            # print('push stream success')
        except:
            print('Push stream failed!')
        # print(time.time() - t1)
        while time.time() - t1 <= 1 / 25:
            continue

if __name__ == '__main__':
    img = cv2.imread("D:/Desktop/000001.jpg")
    flag = Value('i', 1)
    Image_Raw = RawArray("B", 1920 * 1080 * 3 + 4)  # np.uint8
    i_height, i_width = img.shape[0], img.shape[1]
    i_packet_head = [i_height % 256, i_height // 256, i_width % 256, i_width // 256]
    i_packet_body = img.ravel()
    i_packet = np.insert(i_packet_body, 0, i_packet_head)
    memoryview(Image_Raw).cast('B')[:img.size + 4] = i_packet
    # a = Process(target=camera_push_screen, args=('172_16_11_130', Image_Raw, flag))
    # a.start()
    cam_IP = '10.130.210.30'
    b = Process(target=playVideoOnDemand, args=(Image_Raw, cam_IP, flag))
    b.start()
    while True:
        t1 = time.time()
        # print(aa.value)
        while time.time() - t1 <= 1 / 2:
            continue


"""""
====================================================================
datasynth.py - nipype wrapper for camino diffusion model simulations
====================================================================

"""
from nipype.interfaces.base import (traits, TraitedSpec, File, StdOutCommandLine,
                                    StdOutCommandLineInputSpec, isdefined)
# from nipype.utils.filemanip import split_filename
import os

class AnalyticModelInputSpec(StdOutCommandLineInputSpec):

    n_compartments = traits.Int(argstr='compartment %d', units='NA', position=1,
                                desc="Number of compartments " )

    compartment1_name = traits.Enum('stick', 'CylinderGPD', 'Gammadistribradiicylinders',
                                    'Ball', 'Zeppelin', 'Tensor', 'Astrosticks',
                                    'Astrocylinders', 'SphereGPD', 'Dot', argstr='%s',
                                    position=2,desc = 'compartment1_name' )

    compartment1_vf = traits.Float(argstr='%1.3f',position=3,desc='compartment1_volume_fraction')

    compartment1_params = traits.List(traits.Float,position=4,minlen=0, maxlen=5,argstr='%s',
                                      desc='compartment1_parameters')

    compartment2_name = traits.Enum('stick', 'CylinderGPD', 'Gammadistribradiicylinders',
                                    'Ball', 'Zeppelin', 'Tensor', 'Astrosticks',
                                    'Astrocylinders', 'SphereGPD', 'Dot', argstr='%s',
                                    position=5,desc = 'compartment2_name' )

    compartment2_vf = traits.Float(argstr='%1.3f',position=6,desc='compartment2_volume_fraction')

    compartment2_params = traits.List(traits.Float,position=7,minlen=0, maxlen=5,argstr='%s',
                                      desc='compartment2_parameters')

    compartment3_name = traits.Enum('stick', 'CylinderGPD', 'Gammadistribradiicylinders',
                                    'Ball', 'Zeppelin', 'Tensor', 'Astrosticks',
                                    'Astrocylinders', 'SphereGPD', 'Dot', argstr='%s',
                                    position=8,desc = 'compartment3_name' )

    compartment3_params = traits.List(traits.Float,position=9,minlen=0, maxlen=5,argstr='%s',
                                      desc='compartment3_parameters')

    n_voxels = traits.Int(argstr='-voxels %d', units='NA', desc="Number of voxels to simulate")
    # Check: 'separateruns' option for running a separate simulation in each voxel

    schemefile = traits.File(exists=True, argstr='-schemefile %s',desc = 'scheme file')

    out_file = traits.File(desc = 'name of output file (.Bfloat format)')

class AnalyticModelOutputSpec(TraitedSpec):

    synth_data = File(exists=True, desc='simulated_data')

class AnalyticModel(StdOutCommandLine):
    """
    Example: 'ZeppelinCylinderDot model from camino analytic models tutorial

    -------
    >>> from nipype.interfaces.camino import datasynth
    >>> zcd = datasynth.AnalyticModel()
    >>> zcd.inputs.n_compartments = 3
    >>> zcd.inputs.compartment1_name = 'CylinderGPD'
    >>> zcd.inputs.compartment1_vf = 0.6
    >>> zcd.inputs.compartment1_params = [1.7E-9, 0.0,0.0,4E-6]
    >>> zcd.inputs.compartment2_name = 'Zeppelin'
    >>> zcd.inputs.compartment2_vf = 0.1
    >>> zcd.inputs.compartment2_params = [1.7E-9,0.0,0.0,2E-10]
    >>> zcd.inputs.compartment3_name = 'Dot'
    >>> zcd.inputs.schemefile = '59.scheme'
    >>> zcd.inputs.n_voxels = 1
    >>> zcd.inputs.out_file = 'sim_data.Bfloat'
    >>> zcd.run()                  # doctest: +SKIP
    """

    _cmd = 'datasynth -synthmodel'
    input_spec=AnalyticModelInputSpec
    output_spec=AnalyticModelOutputSpec

    def _list_outputs(self):
        outputs = self.output_spec().get()
        outputs['synth_data'] = os.path.abspath(self._gen_outfilename())
        return outputs

    def _gen_outfilename(self):
        outfilename = self.inputs.out_file
        return outfilename

class MonteCarloInputSpec(StdOutCommandLineInputSpec):

    walkers = traits.Int(argstr='-walkers %d', position=1,units='NA', desc="Number of " \
                         "spins to use in simulation. Simple simulations require " \
                         "around 10000 spins, but simulations on more complex " \
                         "substrates require more spins to capture the additional " \
                         "complexity, particularly at higher b-values." \
                         "For the simulations covered in this tutorial we recommend" \
                         "AT LEAST 100000 spins, but more may well be required.")

    tmax = traits.Int(argstr='-tmax %d', position=2,units='NA', desc="Number of timesteps into " \
                      "which the simulation is divided. This can be thought of as " \
                      "defining the temporal resolution of the simulation, since the total" \
                      "duration of a simulation's dynamics (i.e. the period of time that is " \
                      "simulated, and not how long it takes to run!) is defined by its schemefile." \
                      "This duration will be split into a number of updates of equal length" \
                      "dt. This is an important number, since together with the diffusivity" \
                      "it defines the length of steps in each spin's' random walk."\
                      "It also defines how long a simulation will take to run." \
                      "More timesteps equates to a longer execution. Investigations"  \
                      "have shown that simple substrates require about 1000 updates"\
                      "although very complex substrates and very high b-values "\
                      "may need more (5000 or more).")

    voxels = traits.Int(argstr='-voxels %d', units='NA', position=3,desc="Specifies the number of voxels of "\
                         "data to synthesise. It is important to specify this to be at least 1. By "\
                         "default, a simulation will run once and then fill remaining voxels with "\
                         "different noise-realisations of the synthesised data. To run a separate "\
                         "simulation in each voxel, use the -separateruns option.")

    barrier_permeability = traits.Float(argstr='-p %s', units='NA',position=4, desc="Specifies the probability"\
                                     "that a spin will step through a barrier unaffected instead of"\
                                     "interacting with it. This is usually set to 0, making barriers impermeable.")

    schemefile = traits.File(argstr='-schemefile %s', units='NA', position=5,desc="Specifies the scheme file "\
                            "to use for data synthesis. The scheme file describes the pulse sequence "\
                            "used to generate diffusion-weighted data. These specify gradients, "\
                            "pulse durations, orientations, separations, etc. and are no different "\
                            "to those used by other parts of Camino.Specifies the seed for the random number ")

    initial = traits.Enum('uniform', 'intra', 'extra', 'square', 'hex', argstr='-initial %s',position=6,desc="Specificies "\
                          "initial conditions of spins by a keyword. uniform distributes spins uniformly "\
                          "(but randomly) across the substrate. delta locates them all at the same "\
                          "point in the centre of the substrate, intra distributes them uniformly in "\
                          "the intracellular space and extra does the same but in the extracellular "\
                          "space. Note that some substrates do not have a reliable definition of "\
                          "intracellular and extracellular, and in this case the use of intra and "\
                          "extra will lead to unexpected results and is deprecated.")

    substrate = traits.Enum('empty', 'cylinder', 'crossing', 'inflammation', 'ply', argstr='-substrate %s',position=7,desc=" write somethign here")

    substratesize = traits.Float(argstr='-substratesize %s',desc="something to write here")

    ply_file = traits.File(argstr='-plyfile %s', desc='plyfile...')

    increments = traits.Int(argstr='-increments %s',desc="something to write here")

    diffusivity = traits.Float(argstr='-diffusivity %d', units='NA', desc="Specifies the free"\
                              "diffusivity for spins in the simulation in SI units. The default" \
                              " value is 2.0E-9m^s/s but this can be changed as needed. All physical" \
                              " quantities in Camino (including b-value) use SI units (seconds, " \
                              "meters, teslas) so it may be necessary to convert to SI before " \
                              "specifying them.")

    separateruns = traits.Bool(argstr='-separateruns', desc="Runs a separate simulation in each voxel")

    seed = traits.Int(argstr='-seed %d', units='NA', desc="Specifies the seed for the random number "\
                       "generators to use. Since it is a Monte-Carlo simulation, it makes extensive "\
                       "use of random numbers when running. Specifying different seeds will cause "\
                       "different sequences of random numbers to be generated and hence different "\
                       "trajectories of spins to be developed as (slightly) different synthetic "\
                       "measurements to be synthesised. A seed can be any integer, although it is "\
                       "better to use large numbers (8 or 9 digits) as more complex seeds give lower "\
                       "correlations in random number sequences.")

    duration = traits.Float(argstr='-duration %s', units='NA', desc="Specifies the duration "\
                                       "of the dynamics (in seconds) if no scheme file is used. "\
                                       "You can find out what this should be by using the longest "\
                                       "acquisition in your scheme file. The simulation should be long enough"\
                                       "to run at least from the onset of the first gradient block to the end "\
                                       "of the last.")

    trajectories_file = traits.Either(traits.Bool, traits.File(),hash_files=False, argstr='-trajfile %s', units='NA', desc="Specifies the name of the trajfile "\
                                   "generated. A trajfile contains the complete trajectories of all spins in a "\
                                   "given simulation and may be parsed into a set of measurements at a later date.")

    scan = traits.Int(argstr='-scan %d', units='NA', desc="This command reads the trajectories file specified "\
                      "and synthesise diffusion weighted data using myscheme.scheme. Due to the size of the files "\
                      "involved this will take at least several seconds and may take several minutes for larger files.")

    packing = traits.Enum('square', 'hex', argstr='-packing %s',desc="The simulation supports both of these configurations,"\
                          "Cylinders in the simulation do not necessarily have to be abutting (i.e. close-packed, so that "\
                          "their edges touch), although this is certainly possible. The size and spacing of cylinders is "\
                          "controlled by the -cylinderrad and -cylindersep options respectively. -cylinderrad specifies "\
                          "the RADIUS of all cylinders and -cylindersep the separation between cylinders. "\
                          "The cylindersep variable is defined as the distance between the central axes of the cylinders "\
                          "and hence must be at least twice the cylinder radius. Separations closer than this will cause "\
                          "cylinders to overlap and intersect and may cause unexpected results. Both distances must be "\
                          "specified in meters (NOT in microns.")

    numcylinders = traits.Int(argstr='-numcylinders %s',desc="something to write here")

    voxelsizefrac = traits.Float(argstr='-voxelsizefrac %s',desc="something to write here")

    stats_file = File(argstr='-statsfile %s', desc="generate a stats file, just specify its name "\
                      "from the command line using -statsfile myStatsFile.dat along with the rest of your simulation command")

    cylinder_radius = traits.Float(argstr='-cylinderrad %s',desc="cylinder radius")

    cylinder_separation = traits.Float(argstr='-cylindersep %s',desc="The cylindersep variable is "\
                                       "defined as the distance between the central axes of the cylinders "\
                                       "and hence must be at least twice the cylinder radius. "\
                                       "Separations closer than this will cause cylinders to overlap and "\
                                       "intersect and may cause unexpected results. Both distances must be "\
                                       "specified in meters (NOT in microns).")

    crossangle = traits.Float(argstr='-crossangle %s',desc="The crossing angle is specified in radians "\
                                      "(NOT degrees) using the -crossangle 0.7854. This is approximately pi/4,"\
                                      " or 45 degrees. The crossing angle can take on any value, just make sure"\
                                      " you use radians!")

    gamma = traits.List(traits.Float,minlen=2,maxlen=2,argstr='-gamma %s',desc="Gamma distribution parameters")

    #out_file = traits.File(desc = 'name of output file')

class MonteCarloOutputSpec(TraitedSpec):

    synth_data = File(exists=True, desc='simulated_data')
    trajectories_file = File(exists=True)

class MonteCarlo(StdOutCommandLine):
    """
    Example
    -------

    >>> import nipype.interfaces.camino.datasynth
    >>> mc = datasynth.MonteCarlo()

    >>> mc = datasynth.MonteCarlo()
    >>> mc.inputs.walkers = 100000
    >>> mc.inputs.tmax = 1000
    >>> mc.inputs.barrier_permeability = 0.0
    >>> mc.inputs.voxels = 1
    >>> mc.inputs.initial = 'uniform'
    >>> mc.inputs.schemefile = '59.scheme'
    >>> mc.inputs.substrate = 'inflammation'
    >>> mc.inputs.increments = 1
    >>> mc.inputs.gamma = [1.84037, 7.8E-7]
    >>> mc.inputs.substratesize = 3.5E-5 #0.0000355m = 0.0355mm = 35.5um
    >>> mc.inputs.numcylinders = 100
    >>> mc.inputs.out_file = 'gammacyls_n100_tester_fixed_code.bfloat'
    >>> mc.cmdline
    """

    _cmd = 'datasynth'
    input_spec=MonteCarloInputSpec
    output_spec=MonteCarloOutputSpec

    def _gen_outfilename(self):
        return "synth_data.Bfloat"

    def _gen_filename(self, name):
        if name is 'trajectories_file':
            return 'MC.traj'
        else:
            return super(MonteCarlo, self)._gen_filename(name)

    def _format_arg(self, name, spec, value):
        if name == 'trajectories_file':
            if isinstance(value, bool):
                if value == True:
                    value = self._gen_filename(name)
                else:
                    return ""
        elif name == 'out_file':
            if isdefined(self.inputs.trajectories_file) and self.inputs.trajectories_file:
                return ""
            else:
                return super(MonteCarlo, self)._format_arg(name, spec, value)
        return super(MonteCarlo, self)._format_arg(name, spec, value)

    def _list_outputs(self):
        outputs = self.output_spec().get()
        if isdefined(self.inputs.trajectories_file) and self.inputs.trajectories_file:
            if isinstance(self.inputs.trajectories_file, bool):
                outputs['trajectories_file'] = os.path.abspath(self._gen_filename("trajectories_file"))
            else:
                outputs['trajectories_file'] = os.path.abspath(self.inputs.trajectories_file)
        elif isdefined(self.inputs.out_file) and self.inputs.out_file:
            outputs['synth_data'] = os.path.abspath(self.inputs.out_file)
        else:
            outputs['synth_data'] = os.path.abspath(self._gen_filename("out_file"))
        return outputs #self._outputs_from_inputs(outputs)

class ScanTrajfileInputSpec(StdOutCommandLineInputSpec):

    trajfile = traits.File(exists=True, argstr='-trajfile %s',desc = 'trajectories file')

    schemefile = traits.File(exists=True, argstr='-schemefile %s',desc = 'scheme file')

    out_file = traits.File(desc = 'name of output file (.Bfloat format)')

class ScanTrajfileOutputSpec(TraitedSpec):

    synth_data = File(exists=True, desc='simulated_data')

class ScanTrajfile(StdOutCommandLine):
    """
    Example:
    -------
    >>> from nipype.interfaces.camino import datasynth
    >>> st = datasynth.ScanTrajfile()
    >>> st.inputs.trajfile = <trajectories filename>
    >>> st.inputs.schemefile = < scheme file >
    >>> st.inputs.out_file = < output file >
    >>> st.run()                  # doctest: +SKIP
    """

    _cmd = 'scan '
    input_spec=ScanTrajfileInputSpec
    output_spec=ScanTrajfileOutputSpec

    def _list_outputs(self):
        outputs = self.output_spec().get()
        outputs['synth_data'] = os.path.abspath(self._gen_outfilename())
        return outputs

    def _gen_outfilename(self):
        outfilename = self.inputs.out_file
        return outfilename


# Stati18n v0.2
# stati18n.py
# Created by Florian Rotagnon
# Json parsing added by Kyrre Havik Eriksen
# Licensed under MIT

import os
import sys
import json as JSONParser
import codecs

__author__ = 'Florian Rotagnon'

class FileManager:
    def __init__(self):
        pass

    @staticmethod
    def get_content(file_name):
        if os.path.isfile(file_name):
            with open(file_name) as json_file:
                return JSONParser.load(json_file)
        else:
            raise FileNotFoundError

    @staticmethod
    def __create_css(name):
        print("create new css file "+name)
        fp = codecs.open(name, 'w', 'utf-8')
        fp.write("/*!\n * Generated by Stati18nJson v0.2\n * "
           + name
           + "\n * Created by Florian Rotagnon\n * Json parsing by Kyrre Havik Eriksen\n"
           + " * Licensed under MIT\n */\n ")
        return fp

    @classmethod
    def create_stati18n(cls, json):
        fps = {}
        minify = ("true" == json['metadata']['minified'])
        host = json['metadata']['host']
        host = '' if host is None else host
        file_name = json['metadata']['name']
        fps['main'] = cls.__create_css(file_name + '.css')

        # what is dep?
        dep = json['metadata']['lang']

        fix_text_map = {}

        if not minify:
            fps['main'].write("\n#stati18n-infos{\n	content  : '" + file_name + "," + host + "," + dep + "';\n}")
        else:
            fps['main'].write("#stati18n-infos{content:" + file_name + "," + host + "," + dep + ";}")

        for key in json:
            if key == 'metadata':
                continue
            id = key
            value = json[key]
            insertion = value.get('insertion', '')

            if "before" != insertion and "after" != insertion:
                insertion = "after"

            language = json['metadata']['lang']
            i = 'main'

            if not language in fps:
                fps[language] = cls.__create_css(file_name + "-" + language + '.css')

            i = language
            fix = value.get('fix', False)

            if fix:
                fix_text_map[language + "§§" + id] = value['message']
            elif not minify:
                fps[i].write("\n.stati18n.s18n-"
                             + id + ":" + insertion
                             + " {\n\tcontent: \""
                             + value['message'] + "\";\n}")
            else:
                fps[i].write(".stati18n.s18n-" + id
                             + ":" + insertion
                             + "{content:\"" + value['message']
                             + "\";}")

        if not minify:
            fps['main'].write("\n#stati18n-fixed-values {\n\tcontent: '")
        else:
            fps['main'].write("#stati18n-fixed-values{content:'")

        for key in fix_text_map:
            fps['main'].write(key + '§§' + fix_text_map[key] + ';;')

        if not minify:
            fps['main'].write("'\n};")
        else:
            fps['main'].write("'};")

        for fp in fps:
            fps[fp].close()

#Script start
if 2 != len(sys.argv):
    print("Usage: python " + sys.argv[0] + " [name.json]")
    exit(1)

try:
    domArr = FileManager.get_content(sys.argv[1])
    FileManager.create_stati18n(domArr)
except FileNotFoundError:
    print("File not found : " + sys.argv[1])
else:
    print("Stati18n  : compilation success")


# import declaration
import pygame
from pygame.locals import *
import numpy as np
import time
import random
import sys

def create_color_list(saturation_number=5, _max=255,_min=0):
    saturation_list = [int(_min + (_max+_min)/(saturation_number-1)*saturation)
                       for saturation in range(saturation_number)]
    color_list = ((1,1,1),(1,0,0),(0,1,0),(0,0,1),
        (0,0.5,0.5),(0.5,0,0.5),(0.5,0.5,0))

    ret = [[[RGB*sat for RGB in color]
            for sat in saturation_list]
           for color in color_list]
    return ret

class Cells:
    def __init__(self, screen,size,menu_height,color_list):
        self.screen = screen
        self.unit_size,self.row_num, self.col_num = size
        self.menu_height = menu_height
        self.color_list = color_list

    def draw_cell_frame(self,x,y):
        pygame.draw.rect(self.screen, self.color_list[0][1],
            Rect((x-1)*self.unit_size,(y-1)*self.unit_size+self.menu_height,self.unit_size, self.unit_size),1)

    def draw_living_cell(self,x,y,condition):
        pygame.draw.rect(self.screen, self.color_list[1][int(condition)],
            Rect((x-1)*self.unit_size,(y-1)*self.unit_size+self.menu_height,self.unit_size, self.unit_size))

class Menu:
    def __init__(self,screen,menu_size,color_list):
        self.screen = screen
        self.size = menu_size
        self.color_list = color_list
        self.font = pygame.font.Font(None, 30)

    def draw_rect(self,_rect):
        pygame.draw.rect(self.screen, self.color_list[1][1],Rect(*_rect))
        pygame.draw.rect(self.screen, self.color_list[1][1],Rect(*_rect),1)

    def draw_main_menu_bg(self):
        self.draw_rect((0,0,self.size[0],self.size[1]))

    def draw_main_menu_button(self,pos,message):
        b_pos = (pos*self.size[1],0,self.size[1],self.size[1])
        self.draw_rect(b_pos)
        text = self.font.render(message,True,self.color_list[0][1])
        self.screen.blit(text, Rect(*b_pos))

class Units:
    def __init__(self,screen,cell_size, menu_size, definition):
        color_list = create_color_list(definition[2])
        self.definition = definition
        self.cells = Cells(screen, cell_size,menu_size[1],color_list)
        self.menu = Menu(screen, menu_size,color_list)

class Game:
    def __init__(self,size,units):
        self.units = units
        self.cells,self.definition = self.units.cells,self.units.definition
        self.state = np.random.randint(self.definition[2],size=(size[2]+2,size[1]+2),dtype='int8')
        self.c_len,self.r_len = len(self.state),len(self.state[0])
        self.max = self.definition[2]-1
        self.old = [i for i in range(1,self.definition[2]-1)]

    def show_board(self):
        for y in range(1,self.c_len-1):
            for x in range(1,self.r_len-1):
                if self.state[y][x]:
                    self.cells.draw_living_cell(x,y,self.state[y][x])
                # self.cells.draw_cell_frame(x,y)

    def make_next_state(self):
        table = np.array(
            [
                [
                    [self.state[y-1:y+2,x-1:x+2][1][1],np.sum(self.state[y-1:y+2,x-1:x+2][self.state[y-1:y+2,x-1:x+2]==self.max])/self.max-self.state[y-1:y+2,x-1:x+2][1][1]//self.max]
                    for x in range(1,self.r_len-1)
                ]
                for y in range(1,self.c_len-1)
            ]
        )
        next_state = np.array(
            [
                [
                    cell[0]-1 if (cell[0] in self.old) or
                        (cell[0]==self.max and not cell[1] in self.definition[0])
                    else self.max if cell[0]==0 and cell[1] in self.definition[1]
                    else cell[0] for cell in row
                ]
                for row in table
            ]
        )

        self.state[1:self.c_len-1,1:self.r_len-1] = next_state

    def show_menu(self):

        menu = self.units.menu
        menu.draw_main_menu_bg()
        menu.draw_main_menu_button(0,"0")
        menu.draw_main_menu_button(1,"1")

    def __call__(self):
        start = time.time()
        self.show_board()
        time1 = time.time() -start
        start = time.time()
        self.show_menu()
        time2 = time.time() -start
        start = time.time()
        self.make_next_state()
        time3 = time.time() -start
        print('-'*20 + '\nshowboard{0},\nshowmenu{1},\ncalcstate{2}'.format(time1,time2,time3))

def main():
    # main function
    pygame.init()
    cell_size = [4,200,200] #cellsize, col, row
    menu_size = [cell_size[0]*cell_size[1],30]
    definition = ((3,4,5),(2,),4) # S, B, C
    screen = pygame.display.set_mode((cell_size[0]*cell_size[1],cell_size[0]*cell_size[2]+menu_size[1]))
    units = Units(screen,cell_size,menu_size,definition)
    game = Game(cell_size,units)
    while True:
        pygame.time.wait(20)
        screen.fill((0,0,0))
        game()
        pygame.display.update()

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                    x,y = event.pos

if __name__ == '__main__':
    main()


from django import template
from django.utils.timesince import timesince
from shcbelpa.models import Type, Season, Team, Game, SeasonPlayerStats, Type
from syncr.picasaweb.models import Photo
from django.core.urlresolvers import reverse
from django.utils.translation import gettext_lazy as _
import datetime
from decorators.shcbelpa import cache

register = template.Library()

@register.simple_tag
def current_season(date):
    season = Season.objects.get(start_date__lte=date,end_date__gte=date)
    return season

def result_to_list(result):
    if not result:
        return []
    else:
        tmp1 = result.split(":")
        if len(tmp1) == 1:
            return []
        else:
            tmp2 = tmp1[1].split(" ")
    return (tmp1[0],tmp2[0])

@register.simple_tag
def show_author(user):
    if user:
        return "%s %s" % (user.first_name, user.last_name)
    else:
        return ""

@register.simple_tag
def home_goals(game):
    list = result_to_list(game.result)
    if len(list) == 2:
        return list[0]
    return ""

@register.simple_tag
def away_goals(game):
    list = result_to_list(game.result)
    if len(list) == 2:
        return list[1]
    return ""

@register.simple_tag
def show_team(game, team):
    if game.is_published:
        url = game.get_absolute_url()
        return "%s" % (url,team.name)
    else:
        #url = reverse("game_preview", args=[game.id])
        return "%s" % team.name

@register.simple_tag
def show_photo_small(player):
    if player.photo:
        return player.photo.extra_thumbnails_tag.get('small')
    else:
        return ""

@register.simple_tag
def show_photo_large(player):
    if player.photo:
        return player.photo.thumbnail_tag
    else:
        return ""

def get_game(season, team):
    # last game of the current season decides which type do display
    game = Season.objects.get_last_game(season, team)
    if game is None:
        # if no game found, display the next game in this season
        game = Season.objects.get_next_game(season, team)
        if game is None:
            # display last game of last season
            season = season.get_previous_by_start_date()
            game = Season.objects.get_last_game(season, team)
    return game

@cache(3600)
@register.simple_tag
def show_season_link(team_id):
    season = current_season(datetime.datetime.now())
    team = Team.objects.get(legacy_id=team_id)

    if len(Game.objects.filter(team=team, season=season, type__url_part='playoff')) > 0:
        return reverse("game_list", args=[team.legacy_id,season.url_part,"playoff"])

    if len(Game.objects.filter(team=team, season=season, type__url_part='regular')) > 0:
        return reverse("game_list", args=[team.legacy_id,season.url_part,"regular"])

    if len(Game.objects.filter(team=team, season=season, type__url_part='friendly')) > 0:
        return reverse("game_list", args=[team.legacy_id,season.url_part,"friendly"])

    return reverse("game_list", args=[team.legacy_id,season.url_part,"regular"])

@cache(3600)
@register.simple_tag
def show_stats_link(team_id):
    season = current_season(datetime.datetime.now())
    team = Team.objects.get(legacy_id=team_id)

    type = Type.objects.get(url_part='playoff')
    if len(SeasonPlayerStats.objects.getSeasonStats(season, team, type)) > 0:
        return reverse("stats_list", args=[team.legacy_id,season.url_part,"playoff"])

    type = Type.objects.get(url_part='regular')
    if len(SeasonPlayerStats.objects.getSeasonStats(season, team, type)) > 0:
        return reverse("stats_list", args=[team.legacy_id,season.url_part,"regular"])

    type = Type.objects.get(url_part='friendly')
    if len(SeasonPlayerStats.objects.getSeasonStats(season, team, type)) > 0:
        return reverse("stats_list", args=[team.legacy_id,season.url_part,"friendly"])

    return reverse("stats_list", args=[team.legacy_id,season.url_part,"regular"])

@register.filter(name='ago')
def ago(date):
    ago = timesince(date)
    # selects only the first part of the returned string
    return ago.split(",")[0]