import lxml.etree
from rfctools_common.parser import XmlRfc
from rfctools_common import log
import math
import sys
import difflib
import six
import re
from lxml.html import builder as E
from xmldiff.EditItem import EditItem
from xmldiff.EditDistance import ComputeEdits, DoWhiteArray
# from xmldiff.zzs import EditItem

try:
    import debug
    assert debug
except ImportError:
    pass

TextElements = [
    "bcp14", "br", "cref", "em", "eref", "iref", "strong", "relref", "sub",
    "sup", "tt", "xref", "vspace"
]

V2NonTextElements = [
    "list", "figure"
]

tagMatching = {
    "c": {"td"},
    "dd": {"t"},
    "dl": {"list"},
    "li": {"t"},
    "list": {"dl", "ol", "ul"},
    "ol": {"list"},
    "t": {"dd", "li"},
    "table": {"texttable"},
    "td": {"c"},
    "texttable": {"table"},
    "th": {"ttcol"},
    "ttcol": {"th"},
    "ul": {"list"},
}

PreserveSpace = ['artwork', 'sourcecode']

diffCount = 0
if six.PY2:
    nbsp = unichr(0xa0)
else:
    nbsp = chr(0xa0)

def ChangeTagMatching(newMatching):
    global tagMatching

    tagMatching = newMatching

class SourceFileSet(object):
    def __init__(self):
        self.leftFiles = []
        self.rightFiles = []
        self.side = True  # Left

    def AddSourceFile(self, fileName):
        if self.side:
            if fileName not in self.leftFiles:
                self.leftFiles.append(fileName)
        else:
            if fileName not in self.rightFiles:
                self.rightFiles.append(fileName)

    def LeftDone(self):
        self.side = False

    def LineFormat(self, xmlNode, fRight):
        if hasattr(xmlNode, "base"):
            if fRight:
                fileNo = self.rightFiles.index(xmlNode.base)
                return "R{0}_{1}".format(xmlNode.sourceline, fileNo)
            else:
                fileNo = self.leftFiles.index(xmlNode.base)
                return "L{0}_{1}".format(xmlNode.sourceline, fileNo)
        else:
            return ("R{0}_0" if fRight else "L{0}_0").format(xmlNode.sourceline)

    def Clear(self):
        self.leftFiles = []
        self.rightFiles = []
        self.side = True  # Left

SourceFiles = SourceFileSet()

def BuildDiffTree(xmlNode, options):
    """ Build the Diff tree from the xml tree """

    global diffCount, SourceFiles

    if not isinstance(xmlNode, lxml.etree._ElementTree):
        sys.exit(2, "bad parameter")

    #  We have two problems here - the first is that lxml does
    #  not bother keeping the root elements as separate things
    #  so life is a pain

    diffCount = 0
    root = DiffDocument(xmlNode)

    # Grab nodes before the root
    element = xmlNode.getroot().getprevious()
    while element is not None:
        if element.tag is lxml.etree.PI:
            root.children.insert(0, DiffPI(element, root))

        element = element.getprevious()

    # Now process the root element itself
    root.children.append(root.createNode(xmlNode.getroot(), root))

    # Now process any elements after to root element

    element = xmlNode.getroot().getnext()
    while element is not None:
        if element.tag is lxml.etree.PI:
            root.children.append(DiffPI(element, root))

        element = element.getnext()

    if options.debug:
        print("Number of diff nodes is {0}".format(diffCount))

    return root

# Control how elements are converted from a tree to a pargaraph.
# The values here mean:
# 1 - This is always just converted to a paragraph.
# 2 - These are v3 elements which can be either a paragraph or
#     a tree (i.e. have other items inside which are paragraphs)
# 3 - These are v2 elements which may not be paragraphs
# 4 - These are v3 element which are paragraphs, but v2 elements
#     which may not be paragraphs.
#
# We ignore those elements which can only simple text and do not
# treat them as paragarphs

ParagraphMarkers = {
    'annotation': 1,
    'artwork': 2,  # need to deal with perserveSpace
    'blockquote': 2,
    'c': 3,
    'dd': 2,
    'dt': 1,
    'li': 1,
    'refcontent': 1,
    'sourcecode': 1,  # need to deal with perserveSpace
    't': 4,
    'td': 2,
    'th': 2,
    'ttcol': 3,
    '{http://relaxng.org/ns/structure/1.0}desc': 1,
    '{http://relaxng.org/ns/structure/1.0}svgTitle': 1,
    '{http://relaxng.org/ns/structure/1.0}a': 1,
    '{http://relaxng.org/ns/structure/1.0}text': 1,
    '{http://relaxng.org/ns/structure/1.0}textArea': 1,
    '{http://relaxng.org/ns/structure/1.0}tspan': 1
    }

def AddParagraphs(root):
    if not isinstance(root, DiffElement):
        for c in root.children:
            AddParagraphs(c)
        return root
    if root.xml.tag in ParagraphMarkers:
        if ParagraphMarkers[root.xml.tag] == 1 or \
           ParagraphMarkers[root.xml.tag] == 3:
            p = DiffParagraph(root.xml, root)
            p.children = root.children
            root.children = [p]
            return root
        elif ParagraphMarkers[root.xml.tag] == 2:
            newChildren = []
            p = DiffParagraph(root.xml, root)
            for child in root.children:
                if isinstance(child, DiffComment) and False:
                    if len(p.children) > 0:
                        newChildren.append(p)
                        p = DiffParagraph(root.xml, root)
                    newChildren.append(child)
                elif not isinstance(child, DiffElement):
                    p.children.append(child)
                elif child.xml.tag in TextElements:
                    p.children.append(child)
                else:
                    if len(p.children) > 0:
                        newChildren.append(p)
                        p = DiffParagraph(root.xml, root)
                    newChildren.append(child)
            if len(p.children) > 0:
                newChildren.append(p)
            root.children = newChildren
        elif ParagraphMarkers[root.xml.tag] == 4:
            newChildren = []
            p = DiffParagraph(root.xml, root)
            for child in root.children:
                if isinstance(child, DiffComment) and False:
                    if len(p.children) > 0:
                        newChildren.append(p)
                        p = DiffParagraph(root.xml, root)
                    newChildren.append(child)
                elif not isinstance(child, DiffElement):
                    p.children.append(child)
                elif child.xml.tag not in V2NonTextElements:
                    p.children.append(child)
                else:
                    if len(p.children) > 0:
                        newChildren.append(p)
                        p = DiffParagraph(root.xml, root)
                    newChildren.append(child)
            if len(p.children) > 0:
                newChildren.append(p)
            root.children = newChildren

    for c in root.children:
        AddParagraphs(c)
    return root

def DecorateSourceFile(diffRoot, sourceLines):
    diffRoot.decorateSource(sourceLines)

class DiffRoot(object):
    """ Root of the diff objects. """

    def __init__(self, xmlNode, parent):
        global diffCount

        self.xml = xmlNode
        self.children = []
        self.deleted = False
        self.inserted = False
        self.matchNode = None
        self.parent = parent
        diffCount += 1
        self.index = diffCount
        self.deleteTree = False
        self.preserve = False

        if hasattr(xmlNode, "base"):
            baseFile = xmlNode.base
            SourceFiles.AddSourceFile(baseFile)

    def ToString(self):
        node = E.DIV()
        node.text = "Need to override something"
        return node

    def toText(self):
        return lxml.etree.tostring(self.xml)  # with_tail=False

    def ToHtml(self, parent):
        node = E.LI()
        node.text = "Need to override something"
        parent.append(node)

    @staticmethod
    def get_children(n):
        return n.children

    @staticmethod
    def InsertCost(left):
        item = EditItem()
        item.setOperation(EditItem.OP_INSERT, None, left)
        if isinstance(left, DiffElement) or isinstance(left, DiffComment) or \
           isinstance(left, DiffText) or isinstance(left, DiffParagraph):
            item.cost = 10
        else:
            item.cost = 1
        return item

    @staticmethod
    def DeleteCost(left):
        item = EditItem()
        item.setOperation(EditItem.OP_DELETE, left, None)
        item.cost = left.deleteCost()
        return item

    def deleteCost(self):
        """ Compute the deletion cost of a node
            The deletion cost for an element needs to be higher
            that that for text so that doing a rename on texts
            is the preferred operation
        """
        if isinstance(self, DiffElement) or isinstance(self, DiffComment) or \
           isinstance(self, DiffText) or isinstance(self, DiffParagraph):
            cost = 10
        else:
            cost = 1
        return cost

    @staticmethod
    def UpdateCost(left, right):
        #  If the types are not the same, then the cost is extremely high
        if type(left) is not type(right):
            item = EditItem()
            item.setOperation(EditItem.OP_RENAME, left, right)
            item.cost = 100000
            return item
        return left.updateCost(right)

    def updateCost(self, right):
        return 100

    def _serialize(self, element):
        if sys.version > '3':
            return lxml.html.tostring(element, pretty_print=True, method='html',
                                      encoding='ascii').decode('ascii')
        else:
            return lxml.html.tostring(element, pretty_print=True, method='html',
                                      encoding='ascii').decode('ascii')

    def markInsertTrees(self):
        insertTree = True
        for child in self.children:
            insertTree &= child.markInsertTrees()
        insertTree &= (self.matchNode is None)
        self.insertTree = insertTree
        return insertTree

    def markDeleteTrees(self):
        deleteTree = True
        for child in self.children:
            deleteTree &= child.markDeleteTrees()
        deleteTree &= (self.matchNode is None)
        self.deleteTree = deleteTree
        return deleteTree

    def createNode(self, xml, parent):
        if xml.tag is lxml.etree.PI:
            return DiffPI(xml, parent)
        if xml.tag is lxml.etree.Comment:
            return DiffComment(xml, parent)
        textTest = "foo bar" + xml.tag
        return DiffElement(xml, parent)

    def getPredecessor(self, searchNode):
        prevSib = None
        for child in self.children:
            if searchNode.isMatchNode(child):
                return prevSib
            prevSib = child
        return None

    def getSuccessor(self, searchNode):
        found = False
        for child in self.children:
            if found:
                return child
            if searchNode.isMatchNode(child):
                found = True

        return None

    def isMatchNode(self, other):
        if self == other:
            return True
        if self.deleted:
            if len(self.children) != 1:
                return False
            return self.children[0].isMatchNode(other)
        if other.deleted:
            if len(other.children) != 1:
                return False
            return self.isMatchNode(other.children[0])
        return False

    def insertAfter(self, siblingNode, newNode):
        """ Insert newNode after siblingNode """
        i = 0
        for child in siblingNode.parent.children:
            if siblingNode.isMatchNode(child):
                break
            i += 1
        if i == len(siblingNode.parent.children):
            log.error("ICE: insertAfter failed to find node.")
            siblingNode.parent.children.append(newNode)
        else:
            siblingNode.parent.children.insert(i+1, newNode)
        newNode.parent = siblingNode.parent

    def insertBefore(self, siblingNode, newNode):
        i = 0
        for child in siblingNode.parent.children:
            if siblingNode.isMatchNode(child):
                break
            i += 1
        if i == len(siblingNode.parent.children):
            # assert false
            log.error("ICE: insertBefore failed to find node.")
            siblingNode.parent.children.append(newNode)
        else:
            siblingNode.parent.children.insert(i, newNode)
        newNode.parent = siblingNode.parent

    def fixPreserveSpace(self, node, text):
        if self.preserve:
            text = text.splitlines(1)
            n = None
            children = list(node)
            if len(children) > 0:
                n = children[-1]
            for line in text:
                if n is None:
                    if node.text is None:
                        node.text = line.replace(' ', nbsp)
                    else:
                        node.text += line.replace(' ', nbsp)
                else:
                    if n.tail is None:
                        n.tail = line.replace(' ', nbsp)
                    else:
                        n.tail += line.replace(' ', nbsp)
                if line[-1:] == '\n':
                    n = E.BR()
                    node.append(n)
        else:
            if node.text is None:
                node.text = text
            else:
                node.text += text

    def fixPreserveNL(self, node, text):
        if self.preserve or True:
            text = text.splitlines(1)
            n = None
            children = list(node)
            if len(children) > 0:
                n = children[-1]
            for line in text:
                if n is None:
                    if node.text is None:
                        node.text = line
                    else:
                        node.text += line
                else:
                    if n.tail is None:
                        n.tail = line
                    else:
                        n.tail += line
                if line[-1:] == '\n':
                    n = E.BR()
                    node.append(n)
        else:
            if node.text is None:
                node.text = text
            else:
                node.text += text

    def diffTextToHtml(self, leftText, rightText, node):
        if self.preserve:
            n = E.SPAN()
            n.attrib['class'] = 'artwork'
            node.append(n)
            node = n
        if not rightText:
            if not leftText:
                return
            n = E.SPAN()
            n.attrib["class"] = 'left'
            self.fixPreserveSpace(n, leftText)
            node.append(n)
        elif not leftText:
            n = E.SPAN()
            n.attrib['class'] = 'right'
            self.fixPreserveSpace(n, rightText)
            node.append(n)
        else:
            leftArray = self.doWhiteArray(leftText)
            rightArray = self.doWhiteArray(rightText)
            result = list(ComputeEdits(leftArray, rightArray))

            for op, i1, i2, j1, j2 in result:
                if op == 'equal':
                    if i1 == i2 and j1 == j2:
                        continue
                    bothNode = E.SPAN()
                    self.fixPreserveNL(bothNode, (''.join(leftArray[i1:i2])))
                    node.append(bothNode)
                elif op == 'remove':
                    leftNode = E.SPAN()
                    leftNode.attrib['class'] = 'left'
                    self.fixPreserveNL(leftNode, (''.join(leftArray[i1:i2])+u' '))
                    node.append(leftNode)
                elif op == 'insert':
                    rightNode = E.SPAN()
                    rightNode.attrib['class'] = 'right'
                    rightNode.text = ''
                    self.fixPreserveNL(rightNode, (''.join(rightArray[j1:j2])+u' '))
                    node.append(rightNode)
                else:
                    n = E.SPAN()
                    n.attrib['class'] = 'error'
                    self.fixPreserveNL(n, ''.join(leftArray[i1:i2])+u"*" +
                                       (''.join(rightArray[j1:j2])+u' '))
                    node.append(n)

    def doWhiteArray(self, text):
        return DoWhiteArray(text)

class DiffDocument(DiffRoot):
    """ Represent the XML document.  We want to have a common
        node type that is always at the root for comparisions
    """

    def __init__(self, xmlNode):
        DiffRoot.__init__(self, xmlNode, None)

    def ToString(self):
        result = E.DIV()
        result.attrib['class'] = 'center'

        ul = E.UL()
        result.append(ul)
        ul.attrib['class'] = 'mktree'
        ul.attrib['id'] = 'diffRoot'

        #  Insert xml declarations

        n = E.LI()
        n.attrib['whereLeft'] = "L0_0"
        n.attrib['whereRight'] = "R0_0"
        leftText = '<?xml version="{0}" encoding="{1}"?>'.format(self.xml.docinfo.xml_version,
                                                                 self.xml.docinfo.encoding)
        rightText = '<?xml version="{0}" encoding="{1}"?>'. \
            format(self.matchNode.xml.docinfo.xml_version,
                   self.matchNode.xml.docinfo.encoding)
        self.diffTextToHtml(leftText, rightText, n)
        ul.append(n)

        # Insert DTD declaration if one exists
        n = E.LI()
        if self.xml.docinfo.doctype or self.matchNode.xml.docinfo.doctype:
            self.diffTextToHtml(self.xml.docinfo.doctype, self.matchNode.xml.docinfo.doctype, n)
            ul.append(n)

        # now put all of the children into HTML

        for child in self.children:
            child.ToHtml(ul)

        return self._serialize(result)

    def updateCost(self, right):
        return 0

    def decorateSource(self, sourceLines):
        for child in self.children:
            child.decorateSource(sourceLines)

    def applyEdits(self, editList):
        newEdits = []
        for edit in editList:
            if edit.operation == EditItem.OP_DELETE:
                edit.left.deleted = True
            elif edit.operation == EditItem.OP_MATCH:
                edit.left.matchNode = edit.right
                edit.right.matchNode = edit.left
            elif edit.operation == EditItem.OP_RENAME:
                edit.left.matchNode = edit.right
                edit.right.matchNode = edit.left
            else:
                newEdits.append(edit)

        self.matchNode.markInsertTrees()
        self.markDeleteTrees()

        while True:
            editList = newEdits
            newEdits = []

            for edit in editList:
                # Already processed
                if edit.right.matchNode is not None:
                    continue

                if edit.right.insertTree:
                    matchingParent = edit.right.parent.matchNode
                    if matchingParent is None:
                        # we don't know where it goes yet
                        newEdits.append(edit)
                        continue

                    # If a node has no children, then we can add it as a child
                    if edit.right.parent.matchNode.children is None or \
                       len(edit.right.parent.matchNode.children) == 0:
                        if edit.right.parent.matchNode.children is None:
                            edit.right.parent.matchNode.children = []
                        edit.right.parent.matchNode.children.append(
                            edit.right.cloneTree(edit.right.parent.matchNode))
                        continue

                    # If we have a matched preceeding node, put it after that one
                    sibling = edit.right.parent.getPredecessor(edit.right)
                    if sibling is not None:
                        if sibling.matchNode is not None:
                            newNode2 = edit.right.cloneTree(matchingParent)
                            matchingParent.insertAfter(sibling.matchNode, newNode2)
                            continue

                    # If we have a matching successor node, put it after that one
                    sibling = edit.right.parent.getSuccessor(edit.right)
                    if sibling is not None:
                        if sibling.matchNode is not None:
                            newNode2 = edit.right.cloneTree(matchingParent)
                            matchingParent.insertBefore(sibling.matchNode, newNode2)
                            continue

                    # If all of the left children are deleted and a new right is added.
                    allDeleted = True
                    for child in matchingParent.children:
                        if not child.deleteTree:
                            allDeleted = False
                            break

                    if allDeleted:
                        newNode2 = edit.right.cloneTree(matchingParent)
                        matchingParent.children.append(newNode2)
                        continue

                    newEdits.append(edit)
                    continue

                # Nodes which have undealt with children are deferred
                f = True
                for child in edit.right.children:
                    if child.matchNode is None and not child.insertTree:
                        f = False
                        break
                if not f:
                    newEdits.append(edit)
                    continue

                # Get the list of children that we need to match
                matchList = []
                for child in edit.right.children:
                    if not child.inserted and not child.insertTree:
                        matchList.append(child.matchNode)
                if len(matchList) == 0:
                    newEdits.append(edit)
                    continue

                # Build the list of all common ancestors of these nodes
                commonParents = None
                for child in matchList:
                    c = child.parent
                    ancestorList = []
                    while c is not None:
                        ancestorList.append(c)
                        c = c.parent
                    ancestorList = ancestorList[::-1]
                    if commonParents is None:
                        commonParents = ancestorList
                    else:
                        for i in range(min(len(ancestorList), len(commonParents))):
                            if ancestorList[i] != commonParents[i]:
                                commonParents = commonParents[:i]
                                break
                matchParent = commonParents[-1]

                # create the new node
                newNode = edit.right.clone()
                newNode.parent = matchParent
                newNode.inserted = True
                newNode.matchNode = edit.right
                edit.right.matchNode = newNode

                #

                i = 0
                iX = -1
                interums = []
                for child in edit.right.children:
                    if child.insertTree:
                        newNode2 = child.cloneTree(None)
                        newNode.children.append(newNode2)
                        continue
                    while i != len(matchParent.children):
                        if matchParent.children[i].isMatchNode(child.matchNode):
                            if len(interums) != 0:
                                if iX != -1:
                                    for ii in interums:
                                        newNode.children.append(matchParent.children[ii])
                                        matchParent.children[ii] = newNode
                                        del matchParent.children[ii]
                                        i -= 1
                                interums = []
                            newNode.children.append(matchParent.children[i])
                            matchParent.children[i].parent = newNode
                            del matchParent.children[i]
                            if iX == -1:
                                iX = i
                            break
                        else:
                            interums.append(i)
                        i += 1

                if iX == -1:
                    iX = 0
                matchParent.children.insert(iX, newNode)

            if len(editList) == len(newEdits):
                break

        log.note("Number of edits left = " + str(len(newEdits)))
        for edit in newEdits:
            log.note(edit.toString())
        return len(newEdits)

class DiffPI(DiffRoot):
    def __init__(self, xmlNode, parent):
        DiffRoot.__init__(self, xmlNode, parent)

    def ToHtml(self, parent):
        root2 = E.LI()
        parent.append(root2)
        root = E.SPAN()
        root2.append(root)
        if self.inserted:
            root.attrib['class'] = 'right'
            root.attrib["whereRight"] = SourceFiles.LineFormat(self.xml, True)
        elif self.deleted:
            root.attrib['class'] = 'left'
            root.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
        elif self.matchNode is None:
            root.attrib['class'] = 'error'
        else:
            root.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            root.attrib["whereRight"] = SourceFiles.LineFormat(self.matchNode.xml, True)
            if self.xml.target == self.matchNode.xml.target:
                if self.xml.text == self.matchNode.xml.text:
                    pass
                else:
                    root.text = "<?{0} ".format(self.xml.target)
                    s = E.SPAN()
                    s.attrib['class'] = 'left'
                    s.text = self.xml.text
                    root.append(s)
                    s = E.SPAN()
                    s.attrib['class'] = 'right'
                    s.text = self.matchNode.xml.text
                    root.append(s)
                    s.tail = "?>"
                    return
            else:
                root.text = "<?"
                s = E.SPAN()
                s.attrib['class'] = 'left'
                s.text = self.xml.target
                root.append(s)
                s = E.SPAN()
                s.attrib['class'] = 'right'
                s.text = self.matchNode.xml.target
                root.append(s)
                s.tail = ' '
                s = E.SPAN()
                s.attrib['class'] = 'left'
                s.text = self.xml.text
                root.append(s)
                s = E.SPAN()
                s.attrib['class'] = 'right'
                s.text = self.matchNode.xml.text
                root.append(s)
                s.tail = "?>"
                return

        root.text = "<?{0} {1}?>".format(self.xml.target, self.xml.text)

    def cloneTree(self, parent):
        clone = DiffPI(self.xml, parent)
        clone.matchNode = self
        clone.inserted = True
        self.matchNode = clone
        return clone

    def updateCost(self, right):
        if self.xml.target == right.xml.target:
            if self.xml.text == right.xml.text:
                return 0
            else:
                return 50
        return 100

class DiffComment(DiffRoot):
    def __init__(self, xmlNode, parent):
        DiffRoot.__init__(self, xmlNode, parent)
        self.preserve = True

    def ToHtml(self, parent):
        node = E.LI()
        parent.append(node)
        myLine = "<--" + self.xml.text.replace(' ', nbsp) + "-->"
        while myLine[0] == '\n':
            myLine = myLine[1:]
        while myLine[-1] == '\n':
            myLine = myLine[:-1]
        if self.inserted:
            n = E.SPAN()
            n.attrib['class'] = 'artwork right'
            node.attrib["whereRight"] = SourceFiles.LineFormat(self.xml, True)
            self.fixPreserveSpace(n, myLine)
            node.append(n)
        elif self.deleted:
            n = E.SPAN()
            n.attrib['class'] = 'artwork left'
            node.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            self.fixPreserveSpace(n, myLine)
            node.append(n)
        elif self.matchNode is None:
            n = E.SPAN()
            root.attrib['class'] = 'artwork error'
            self.fixPreserveSpace(n, myLine)
            node.append(n)
        else:
            node.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            node.attrib["whereRight"] = SourceFiles.LineFormat(self.matchNode.xml, True)
            left = myLine
            right = "<--" + self.matchNode.xml.text.replace(' ', nbsp) + "-->"
            self.diffTextToHtml(left, right, node)

    def toText(self):
        return "\n<--" + self.xml.text.replace(' ', nbsp) + "-->\n"

    def cloneTree(self, parent):
        clone = DiffComment(self.xml, parent)
        clone.matchNode = self
        clone.inserted = True
        self.matchNode = clone
        return clone

    def updateCost(self, right):
        if self.xml.text == right.xml.text:
            return 0
        return 3

class DiffElement(DiffRoot):
    def __init__(self, xmlNode, parent):
        if not isinstance(xmlNode, DiffElement):
            DiffRoot.__init__(self, xmlNode, parent)
            preserve = xmlNode.tag == 'artwork'
            if xmlNode.text is not None and xmlNode.text.rstrip() != '':
                n = DiffText(xmlNode.text, xmlNode, self)
                n.preserve = preserve
                self.children.append(n)

            for c in xmlNode.iterchildren():
                n = self.createNode(c, self)
                self.children.append(n)
                if c.tail is not None and c.tail.rstrip() != '':
                    n = DiffText(c.tail, xmlNode, self)
                    n.preserve = preserve
                    self.children.append(n)
                    c.tail = None
        else:
            DiffRoot.__init__(self, xmlNode.xml, parent)

    def cloneTree(self, root):
        clone = DiffElement(self, root)
        clone.matchNode = self
        clone.inserted = True
        self.matchNode = clone

        for child in self.children:
            clone.children.append(child.cloneTree(clone))
        return clone

    def clone(self):
        clone = DiffElement(self, None)
        return clone

    def ToHtml(self, parent):
        # If we have the right doc info - then emit the <?xml?> line

        root = E.LI()
        parent.append(root)
        anchor = root
        if self.deleted:
            # anchor.attrib['onclick'] = 'return sync2here(1, {0}, -1, 0)'.
            # format(self.xml.sourceline)
            node = E.SPAN()
            node.attrib["class"] = 'left'
            root.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            node.text = "<" + self.xml.tag
            anchor.append(node)
            if len(self.xml.attrib):
                for key in self.xml.attrib.iterkeys():
                    node.text = node.text + " " + key + '="' + self.xml.attrib[key] + '"'
        elif self.inserted:
            # anchor.attrib['onclick'] = 'return sync2here(-1, 0, 1, {0})'.
            # format(self.xml.sourceline)
            node = E.SPAN()
            node.attrib['class'] = 'right'
            root.attrib["whereRight"] = SourceFiles.LineFormat(self.xml, True)
            node.text = "<" + self.xml.tag
            anchor.append(node)
            if len(self.xml.attrib):
                for key in self.xml.attrib.iterkeys():
                    node.text = node.text + " " + key + '="' + self.xml.attrib[key] + '"'
        elif self.matchNode is None:
            # anchor.attrib['onclick'] = 'return sync2here(1, {0}, -1, 1)'.
            # format(self.xml.sourceline)
            node = E.SPAN()
            node.attrib['class'] = 'error'
            node.text = "<" + self.xml.tag
            anchor.append(node)
            if len(self.xml.attrib):
                for key in self.xml.attrib.iterkeys():
                    node.text = node.text + " " + key + '="' + self.xml.attrib[key] + '"'
        else:
            root.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            root.attrib["whereRight"] = SourceFiles.LineFormat(self.matchNode.xml, True)
            # anchor.attrib['onclick'] = 'return sync2here(1, {0},  1, {1})' \
            #      .format(self.xml.sourceline, self.matchNode.xml.sourceline)
            if self.xml.tag == self.matchNode.xml.tag:
                anchor.text = "<" + self.xml.tag
            else:
                anchor.text = "<"
                node = E.SPAN()
                node.attrib['class'] = 'left'
                node.text = self.xml.tag
                anchor.append(node)
                node = E.SPAN()
                node.attrib['class'] = 'right'
                node.text = self.matchNode.xml.tag
                anchor.append(node)
            if len(self.xml.attrib):
                for key in self.xml.attrib.iterkeys():
                    if key in self.matchNode.xml.attrib:
                        if self.xml.attrib[key] == self.matchNode.xml.attrib[key]:
                            node = E.SPAN()
                            node.text = " " + key + '="' + self.xml.attrib[key] + '"'
                            anchor.append(node)
                        else:
                            leftText = " " + key + '="' + self.xml.attrib[key] + '"'
                            rightText = " " + key + '="' + self.matchNode.xml.attrib[key] + '"'
                            self.diffTextToHtml(leftText, rightText, anchor)
                    else:
                        node = E.SPAN()
                        node.attrib['class'] = 'left'
                        node.text = " " + key + '="' + self.xml.attrib[key] + '"'
                        anchor.append(node)

            for key in self.matchNode.xml.attrib.iterkeys():
                if key not in self.xml.attrib:
                    node = E.SPAN()
                    node.attrib['class'] = 'right'
                    node.text = " " + key + '="' + self.matchNode.xml.attrib[key] + '"'
                    anchor.append(node)

        if len(self.children):
            s = E.SPAN()
            s.text = ">"
            if self.deleted:
                s.attrib['class'] = 'left'
            elif self.inserted:
                s.attrib['class'] = 'right'
            anchor.append(s)
            ul = E.UL()
            for child in self.children:
                child.ToHtml(ul)

            li = E.LI()
            s = E.SPAN()
            li.append(s)
            s.text = "</" + self.xml.tag + ">"
            if self.deleted:
                s.attrib['class'] = 'left'
                li.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            elif self.inserted:
                s.attrib['class'] = 'right'
                li.attrib["whereRight"] = SourceFiles.LineFormat(self.xml, True)
            else:
                li.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
                li.attrib["whereRight"] = SourceFiles.LineFormat(self.matchNode.xml, True)
                # li.attrib["src"] = "DiffElement-End"

            ul.append(li)
            root.append(ul)
        else:
            s = E.SPAN()
            s.text = "/>"
            if self.deleted:
                s.attrib['class'] = 'left'
            elif self.inserted:
                s.attrib['class'] = 'right'
            anchor.append(s)

    def updateCost(self, right):
        if self.xml.tag == right.xml.tag:
            return 0
        if tagMatching is not None:
            if self.xml.tag in tagMatching and right.xml.tag in tagMatching[self.xml.tag]:
                return 0
        return 100

    def decorateSource(self, sourceLines):
        source = sourceLines[self.xml.sourceLine]
        if self.deleted:
            pass
        elif self.inserted:
            pass
        elif self.matchNode is None:
            pass
        else:
            if self.xml.tag != self.matchNode.xml.tag:
                source.replace("&"+self.xml.tag, "<span class='left'>&" + self.xml.tag)

        sourceLines[self.xml.sourceLine] = source

class DiffText(DiffRoot):
    def __init__(self, text, xmlNode, parent):
        DiffRoot.__init__(self, xmlNode, parent)
        self.text = text

    def cloneTree(self, parent):
        clone = DiffText(self.text, self.xml, parent)
        clone.matchNode = self
        clone.inserted = True
        self.matchNode = clone
        return clone

    def toText(self):
        if not self.preserve:
            return self.text.replace('\n', ' ')
        return self.text

    def ToHtml(self, parent):
        node = E.LI()
        parent.append(node)
        if self.deleted:
            n = E.SPAN()
            n.attrib["class"] = 'left'
            node.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            n.text = self.text
            node.append(n)
        elif self.inserted:
            n = E.SPAN()
            n.attrib["class"] = 'right'
            node.attrib["whereRight"] = SourceFiles.LineFormat(self.xml, True)
            n.text = self.text
            node.append(n)
        elif self.matchNode is None:
            n = E.SPAN()
            n.attrib["class"] = 'error'
            n.text = self.text
            node.append(n)
        else:
            node.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            node.attrib["whereRight"] = SourceFiles.LineFormat(self.matchNode.xml, True)
            if self.text == self.matchNode.text:
                node.text = self.text
            else:
                self.diffTextToHtml(self.text, self.matchNode.text, node)

    def updateCost(self, right):
        if self.text == right.text:
            return 0
        return 3

    def decorateSource(self, sourceLines):
        pass

class DiffParagraph(DiffRoot):
    def __init__(self, node, parent):
        if not isinstance(node, DiffParagraph):
            DiffRoot.__init__(self, node, parent)
        else:
            DiffRoot.__init__(self, node.xml, parent)
        self.preserve = False
        if '{http://www.w3.org/XML/1998/namespace}space' in self.xml.attrib:
            if self.xml.attrib['{http://www.w3.org/XML/1998/namespace}space'] == 'preserve':
                self.preserve = True
        elif self.xml.tag in PreserveSpace:
            self.preserve = True

    def cloneTree(self, root):
        clone = DiffParagraph(self, root)
        clone.matchNode = self
        clone.inserted = True
        self.matchNode = clone

        for child in self.children:
            clone.children.append(child.cloneTree(clone))
        return clone

    def decorateSource(self):
        for child in self.children:
            child.decorateSource(sourceLines)

    def toText(self):
        text = ""
        for child in self.children:
            x = child.toText()
            if type(x).__name__ == 'bytes':
                x = x.decode('utf-8')
            if isinstance(x, DiffComment):
                x = "\n" + text + "\n"
            text += x

        if self.preserve:
            text = text.replace(' ', u'\xa0')
        return text

    def ToHtml(self, parent):

        node = E.LI()
        parent.append(node)
        if self.deleted:
            n = E.SPAN()
            n.attrib["class"] = 'left'
            node.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            self.fixPreserveSpace(n, self.toText())
            node.append(n)
        elif self.inserted:
            n = E.SPAN()
            n.attrib['class'] = 'right'
            node.attrib["whereRight"] = SourceFiles.LineFormat(self.xml, True)
            self.fixPreserveSpace(n, self.toText())
            node.append(n)
        elif self.matchNode is None:
            n = E.SPAN()
            n.attrib['class'] = 'error'
            self.fixPreserveSpace(n, self.toText())
            node.append(n)
        else:
            self.diffTextToHtml(self.toText(), self.matchNode.toText(), node)
            node.attrib["whereLeft"] = SourceFiles.LineFormat(self.xml, False)
            node.attrib["whereRight"] = SourceFiles.LineFormat(self.matchNode.xml, True)

    def updateCost(self, right):
        leftText = self.toText()
        rightText = right.toText()
        differ = difflib.SequenceMatcher(a=leftText, b=rightText)
        ratio = differ.quick_ratio()
        return 10 - int(math.floor(ratio*10))


# Copyright 2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

import astakos.oa2.models as oa2_models

from astakos.oa2.backends import base as oa2base
from astakos.oa2.backends import base as errors

from django import http
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.core.urlresolvers import reverse
from django.conf.urls.defaults import patterns, url
from django.http import HttpResponseNotAllowed
from django.utils.encoding import smart_str, iri_to_uri
from django.views.decorators.csrf import csrf_exempt

from synnefo.lib import join_urls
from synnefo.util import urltools

import urllib

import logging
logger = logging.getLogger(__name__)

class DjangoViewsMixin(object):

    def auth_view(self, request):
        oa2request = self.build_request(request)
        oa2response = self.authorize(oa2request, accept=False)
        return self._build_response(oa2response)

    @csrf_exempt
    def token_view(self, request):
        if request.method != 'POST':
            return HttpResponseNotAllowed(['POST'])

        oa2request = self.build_request(request)
        oa2response = self.grant_token(oa2request)
        return self._build_response(oa2response)

class DjangoBackendORMMixin(object):

    def get_client_by_credentials(self, username, password):
        try:
            return oa2_models.Client.objects.get(identifier=username,
                                                 secret=password)
        except oa2_models.Client.DoesNotExist:
            raise errors.InvalidClientID("No such client found")

    def get_client_by_id(self, clientid):
        try:
            return oa2_models.Client.objects.get(identifier=clientid)
        except oa2_models.Client.DoesNotExist:
            raise errors.InvalidClientID("No such client found")

    def get_authorization_code(self, code):
        try:
            return oa2_models.AuthorizationCode.objects.get(code=code)
        except oa2_models.AuthorizationCode.DoesNotExist:
            raise errors.OA2Error("No such authorization code")

    def get_token(self, token):
        try:
            return oa2_models.Token.objects.get(code=token)
        except oa2_models.Token.DoesNotExist:
            raise errors.OA2Error("No such token")

    def delete_authorization_code(self, code):
        code.delete()
        logger.info(u'%r deleted' % code)

    def delete_token(self, token):
        token.delete()
        logger.info(u'%r deleted' % token)

    def check_credentials(self, client, username, secret):
        if not (username == client.get_id() and secret == client.secret):
            raise errors.InvalidAuthorizationRequest("Invalid credentials")

class DjangoBackend(DjangoBackendORMMixin, oa2base.SimpleBackend,
                    DjangoViewsMixin):

    code_model = oa2_models.AuthorizationCode.objects
    token_model = oa2_models.Token.objects
    client_model = oa2_models.Client.objects

    def _build_response(self, oa2response):
        response = http.HttpResponse()
        response.status_code = oa2response.status
        response.content = oa2response.body
        for key, value in oa2response.headers.iteritems():
            response[smart_str(key)] = smart_str(value)
        return response

    def build_request(self, django_request):
        params = {
            'method': django_request.method,
            'path': django_request.path,
            'GET': django_request.GET,
            'POST': django_request.POST,
            'META': django_request.META,
            'secure': settings.DEBUG or django_request.is_secure(),
            #'secure': django_request.is_secure(),
        }
        # TODO: check for valid astakos user
        if django_request.user.is_authenticated():
            params['user'] = django_request.user
        return oa2base.Request(**params)

    def get_url_patterns(self):
        _patterns = patterns(
            '',
            url(r'^%s/?$' % join_urls(self.endpoints_prefix,
                                      self.authorization_endpoint.rstrip('/')),
                self.auth_view,
                name='%s_authenticate' % self.id),
            url(r'^%s/?$' % join_urls(self.endpoints_prefix,
                                      self.token_endpoint.rstrip('/')),
                self.token_view,
                name='%s_token' % self.id),
        )
        return _patterns

    def is_uri(self, string):
        validator = URLValidator()
        try:
            validator(string)
        except ValidationError:
            return False
        else:
            return True

    def get_login_uri(self):
        return reverse('login')

    @staticmethod
    def urlencode(params):
        if hasattr(params, 'urlencode') and \
                callable(getattr(params, 'urlencode')):
            return params.urlencode()
        for k in params:
            params[smart_str(k)] = smart_str(params.pop(k))
        return urllib.urlencode(params)

    @staticmethod
    def normalize(url):
        return urltools.normalize(iri_to_uri(url))

class AstakosBackend(DjangoBackend):
    pass


import os
import random
import datetime
import argparse
import json

import torch
import tensorboardX as tbx
import numpy as np
import roboschool
import gym
import pickle

import matplotlib.pyplot as plt

from agent import ActorCriticAgent as Agent
from utils import Logger
from buffer import ReplayBuffer, collate_buffer

def get_args():
    parser = argparse.ArgumentParser(description='Actor-Criticの設定')

    parser.add_argument('--max-step', type=float, default=5e+5, help='(int) 最大ステップ数. default: 5e+5')
    parser.add_argument('--gamma', type=float, default=0.99, help='(float) 減衰率. default: 0.99')
    parser.add_argument('--lr', type=float, default=3e-4, help='(float) 学習率. default: 3e-4')
    parser.add_argument('--seed', type=int, default=2, help='(int) 評価環境のseed値. default: 2')
    parser.add_argument('--save-step', type=int, default=500, help='(int) モデルの保存タイミング')
    parser.add_argument('--expl', type=int, default=10000, help='(int) ランダム行動ステップ数')
    parser.add_argument('--device', type=int, default=-1, help='(int) デバイス. -1はcpu, 0以上はGPUの番号. default: -1')

    parser.add_argument('--optim', type=str, default='adam', choices=['adam', 'sgd', 'momentum_sgd'],
                        help='(str) 最適化関数. adam, sgd, momentum_sgdから選択. default: adam')
    parser.add_argument('--sigma-beta', type=float, default=0.1, help='(float) ⾏動⽅策のノイズ. default: 0.1')

    parser.add_argument('--actor-layer', type=int, default=2, help='(int) Actorのネットワーク層の数. default: 2')
    parser.add_argument('--critic-layer', type=int, default=3, help='(int) Criticのネットワーク層の数. default: 3')
    parser.add_argument('--actor-dim', type=int, default=256, help='(int) Actorの中間層の次元数. default: 256')
    parser.add_argument('--critic-dim', type=int, default=256, help='(int) Criticの中間層の次元数. default: 256')

    parser.add_argument('--eval-seed', type=int, default=5, help='(int) 学習環境のseed値. default: 5')
    parser.add_argument('--eval-step', type=int, default=10000, help='(int) 評価のタイミング. default: 10000')
    parser.add_argument('--eval-episodes', type=int, default=10, help='(int) 評価のエピソード数. default: 10')
    parser.add_argument('--batch-size', type=int, default=256, help='(int) 経験再生におけるバッチサイズ. default: 256')
    parser.add_argument('--div-step', action='store_true', help='評価時の割引報酬和をstep数で破る.')

    parser.add_argument('--env', type=str, default='Pendulum-v0', help='(str) Gymにおける環境名. default: Pendulum-v0')
    args = parser.parse_args()

    if args.device >= 0 and torch.cuda.is_available():
        args.device = f'cuda:{args.device}'
    else:
        args.device = 'cpu'

    return args

def main():
    args = get_args()
    # -- ハイパーパラメータ --
    param = dict()
    param['model'] = 'Actor-Critic'
    param['max_step'] = args.max_step
    param['gamma'] = args.gamma
    param['lr'] = args.lr
    param['batch_size'] = args.batch_size
    param['seed'] = args.seed
    param['eval_seed'] = args.eval_seed
    param['eval_episodes'] = args.eval_episodes
    param['expl'] = args.expl
    param['optim'] = args.optim
    param['sigma_beta'] = args.sigma_beta
    param['env'] = args.env
    param['div_step'] = args.div_step

    param['actor_layer'] = args.actor_layer
    param['critic_layer'] = args.critic_layer
    param['actor_hidden_dim'] = args.actor_dim
    param['critic_hidden_dim'] = args.critic_dim

    # -- 環境のインスタンス生成 --
    env = gym.make(args.env)

    # -- 実験の日時とディレクトリ作成 --
    now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    save_dir = os.path.join('./log/actor-critic', now)
    os.makedirs(save_dir, exist_ok=True)

    # ハイパーパラメータの保存
    with open(os.path.join(save_dir, 'hyperparam.json'), 'w') as f:
        json.dump(param, f, indent=4)

    # -- ログの作成 --
    logger = Logger(os.path.join(save_dir, 'experiment.log'))
    logger.log(f'date : {now}')
    logger.log(f'save directory : {save_dir}')

    logger.log(' -- hyperparameter -- ')
    logger.log('{')
    for k, v in param.items():
        logger.log(f'    {k}: {v}')
    logger.log('}')

    writer = tbx.SummaryWriter(save_dir)

    # seedの設定
    env.seed(args.seed)
    torch.manual_seed(args.seed)
    np.random.seed(args.seed)
    random.seed(args.seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed_all(args.seed)
        torch.backends.cudnn.deterministic = True

    agent = Agent(action_space=env.action_space, observation_space=env.observation_space,
                  optim=args.optim, lr=args.lr, gamma=args.gamma, device=args.device, sigma=args.sigma_beta,
                  actor_layer=args.actor_layer, critic_layer=args.critic_layer,
                  actor_hidden_dim=args.actor_dim, critic_hidden_dim=args.critic_dim)

    # replay buffer
    replay_buffer = ReplayBuffer(args.max_step)

    # -- start training
    state = env.reset()
    episode = 1
    save_episodes = []
    eval_rewards = []
    train_actor_loss = []
    train_critic_loss = []
    eval_reward_ = -10000.00

    logger.log('   Step   | Episode | L(actor) | L(critic) | reward ')

    for t in range(int(args.max_step)):
        if t < args.expl:
            # ランダム行動
            action = env.action_space.sample()
        else:
            if args.device == 'cpu':
                action = agent.select_exploratory_action(torch.tensor([state], dtype=torch.float32).to(args.device))[0].detach().numpy()
            else:
                action = agent.select_exploratory_action(torch.tensor([state], dtype=torch.float32).to(args.device))[0].cpu().detach().numpy()

        next_state, reward, done, info = env.step(action) # ランダム行動
        replay_buffer.add(state, action, next_state, reward, done)
        state = next_state
        if done:
            state = env.reset()
            episode += 1
            # save model
            if episode % args.save_step == 0:
                save_episodes.append(episode)
                agent.save_models(os.path.join(save_dir, f'actor_critic_{episode}.pth'))

        # evaluation
        if t % args.eval_step == 0:
            eval_reward = agent.eval(env=gym.make(args.env), n_episode=args.eval_episodes,seed=args.eval_seed, mean=args.div_step)
            eval_rewards.append(eval_reward)
            writer.add_scalar("reward", eval_reward.mean(), episode)
            if agent.actor_loss is not None:
                print('\r', end='')
                logger.log(
                    f'{t:8}  | {episode:7} | {agent.actor_loss:.7} |  {agent.critic_loss:.7} | {eval_reward.mean():.3f}')
            eval_reward_ = eval_reward.mean()

        if len(replay_buffer) > args.batch_size:
            states, actions, next_states, rewards, dones = collate_buffer(replay_buffer, args.batch_size)
            agent.train(states, actions, next_states, rewards, dones)

            writer.add_scalar("loss/critic_loss", agent.critic_loss, t)
            writer.add_scalar("loss/actor_loss", agent.actor_loss, t)
            train_critic_loss.append(agent.critic_loss)
            train_actor_loss.append(agent.actor_loss)
            if t % env._max_episode_steps == 0:
                print('\r', end='')
                print(f'\r{t:8}  | {episode:7} | {agent.actor_loss:.7} |  {agent.critic_loss:.7} | {eval_reward_:.3f}', end='')

    env.close()

    plot(eval_rewards, args.eval_step, os.path.join(save_dir, f'plot_{args.seed}.png'))
    with open(os.path.join(save_dir, f'history_{args.seed}.pickle'), 'wb') as f:
        pickle.dump(eval_rewards, f)
    with open(os.path.join(save_dir, f'actor_loss_{args.seed}.pickle'), 'wb') as f:
        pickle.dump(train_actor_loss, f)
    with open(os.path.join(save_dir, f'critic_loss_{args.seed}.pickle'), 'wb') as f:
        pickle.dump(train_critic_loss, f)

    rewards = []
    for episode in save_episodes:
        agent.load_models(os.path.join(save_dir, f'actor_critic_{episode}.pth'))
        reward = agent.eval(env=gym.make(args.env), n_episode=args.eval_episodes, seed=args.eval_seed, mean=args.div_step)
        rewards.append(reward)

    # visualize
    boxplot(rewards, save_episodes, os.path.join(save_dir, f'boxplot_{args.seed}.png'))

def boxplot(rewards, labels, path):
    plt.boxplot(rewards, labels=labels, showfliers=False)
    plt.title('reward history')
    plt.xlabel('episode')
    plt.ylabel('reward')
    plt.savefig(path)
    plt.clf()

def plot(rewards, step, path):
    rewards = np.array(rewards)
    x = (np.arange(rewards.shape[0]) + 1) * step
    y = np.percentile(rewards, [0, 25, 50, 75, 100], axis=1)
    plt.plot(x, y[0], color='black') # min
    plt.plot(x, y[4], color='black') # max
    plt.fill_between(x, y[1], y[3], color='gray', alpha=0.5) # 25%, 75%
    plt.plot(x, y[2], color='red') # mean
    plt.title('reward history')
    plt.xlabel('episode')
    plt.ylabel('reward')
    plt.savefig(path)
    plt.clf()

if __name__ == '__main__':
    main()


'''
Loads the help frame for the users login
'''
# for logging events
import logging
# For the GUI
import tkinter
# For opening the wiki
import webbrowser

# To take the user back to the login screen
from scr.menus.login_menu import main_login
# For title creation
from scr.menus.title import Title

class Help(tkinter.Frame):  # pylint: disable=too-many-ancestors
    '''
    Loads all of the help screens methods
    '''

    def __init__(self, settings_path, colour_data, parent, *args, **kwargs):
        '''
        Loads the help frame of the GUI
        '''
        logging.info("Loading help frame")
        # The the relevant frame methods
        tkinter.Frame.__init__(self, parent, *args, **kwargs)

        self.settings_path = settings_path

        self.colour_data = colour_data

        self.parent = parent

        self.config(bg=self.colour_data["background"])

        Title("Help:", self.colour_data, self).pack(fill=tkinter.X, expand=True,
                                                    side=tkinter.TOP, padx=5, pady=1)

        # The main help text container
        help_message = (
            "Welcome to Login Access Help, if you have forgeten your "
            "password please press the password rest button below. "
            "If you need help with loging in, creating an account or anything else "
            "please take a look at the wiki by pressing the button below. "
            "Thank you\n"
        )
        message = tkinter.Message(
            self,
            text=help_message,
            bg=self.colour_data["background"],
            fg=self.colour_data["foreground"],
            font=self.colour_data["font"],
            anchor=tkinter.W
        )
        message.pack(fill=tkinter.BOTH, expand=True,
                     side=tkinter.TOP, padx=5, pady=3)
        message.bind(
            "<Configure>", lambda e: message.config(width=e.width-10))

        Buttons(self.settings_path, self.parent, self.colour_data, self).pack(
            fill=tkinter.X, expand=True, side=tkinter.TOP, padx=5, pady=1)

class Buttons(tkinter.Frame):  # pylint: disable=too-many-ancestors
    '''
    Loads all of the help screens buttons
    '''

    def __init__(self, settings_path, top_parent, colour_data, parent, *args, **kwargs):
        '''
        Loads the button frame of the GUI
        '''
        logging.info("Loading help buttons")
        # The the relevant frame methods
        tkinter.Frame.__init__(self, parent, *args, **kwargs)

        self.colour_data = colour_data

        self.settings_path = settings_path

        self.top_parent = top_parent
        self.parent = parent

        self.config(bg=self.colour_data["background"])

        # Back button
        tkinter.Button(
            self,
            text="Back",
            bg=self.colour_data["btn_background"],
            activebackground=self.colour_data["btn_active"],
            fg=self.colour_data["foreground"],
            font=self.colour_data["font"],
            command=lambda: self.back()  # pylint: disable=unnecessary-lambda
        ).pack(fill=tkinter.BOTH, side=tkinter.LEFT, expand=True, padx=2, pady=2)

        # Wiki butten to load the full github wiki
        tkinter.Button(
            self,
            text="Wiki",
            bg=self.colour_data["btn_background"],
            activebackground=self.colour_data["btn_active"],
            fg=self.colour_data["foreground"],
            font=self.colour_data["font"],
            command=lambda: self.load_wiki()  # pylint: disable=unnecessary-lambda
        ).pack(fill=tkinter.BOTH, side=tkinter.LEFT, expand=True, padx=2, pady=3)

        # Password reset button
        tkinter.Button(
            self,
            text="Reset Password",
            bg=self.colour_data["btn_background"],
            activebackground=self.colour_data["btn_active"],
            fg=self.colour_data["foreground"],
            font=self.colour_data["font"],
            command=lambda: self.reset_password()  # pylint: disable=unnecessary-lambda
        ).pack(fill=tkinter.BOTH, side=tkinter.LEFT, expand=True, padx=2, pady=3)

    def back(self):
        '''
        Takes the user back to the login screen
        '''
        logging.info("Exiting the help menu")
        self.parent.destroy()
        main_login.LoginMenu(self.settings_path, self.top_parent).pack(
            fill=tkinter.BOTH,
            expand=True
        )

        return

    def load_wiki(self):  # pylint: disable=no-self-use
        '''
        Opens the github wiki page
        https://github.com/Elephant34/Login-Access/wiki
        '''
        logging.info("Loading the wiki page")

        webbrowser.open(
            "https://github.com/Elephant34/Login-Access/wiki/")

        return

    def reset_password(self):
        '''
        Loads the settings for reseting a users password
        '''

        # BUG- Work in progress

        return


#Sacher_playground.py
"""
Make sure you're in the lantz32 environment to run this code properly
"""

import visa
import types
import ctypes
import sys
import numpy as np
import logging
from ctypes.wintypes import DWORD
from ctypes.wintypes import WORD
import ctypes.wintypes
import time

eposlib = ctypes.windll.LoadLibrary('C:\\Users\\Carbro\\Desktop\\Charmander\\EposCmd.dll')

DeviceName = b'EPOS'
ProtocolStackName = b'MAXON_RS232'
InterfaceName = b'RS232'

"""
    ok before I dive into this giant Sacher class thing let me just list here all the functions that are being defined in this class:

    These are the important ones:
    1) __init__(self, name, address, reset=False)
    2) open(self)
    3) initialize(self)
    4) get_offset(self)
    5) get_motor_position(self)
    6) do_get_wavelength(self)
    7) set_new_offset(self, new_offset)
    8) set_coeffs(self, a, b, c, min_wl, max_wl)
    9) do_set_wavelength(self, wavelength)
    10) set_target_position(self, target, absolute, immediately)

    And then there are these random ones:
    11) get_bit(self, byteval,idx)
    12) _u32todouble(self, uinput)
    13) _doubletou32(dinput)
    14) __del__(self)
    15) close(self)
    16) get_motor_current(self)
    17) find_home(self)
    18) restore(self)
    19) fine_tuning_steps(self, steps)
    20) is_open(self)
    21) clear_fault(self)

    They're labeled as #1, #2, ..., so you can find them with some ctrl+f
    """

class Sacher_EPOS():

#1)
    def __init__(self, name, address, reset=False):
        self._port_name = address
        self._is_open = False
        self._HPM = True
        self.open()
        self.initialize()
    """
    This is the special "__init__" function that gets runs automatically
    """

#2)
    def open(self):
        eposlib.VCS_OpenDevice.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(DWORD)]
        eposlib.VCS_OpenDevice.restype = ctypes.wintypes.HANDLE
        buf = ctypes.pointer(DWORD(0))
        ret = ctypes.wintypes.HANDLE()
        ret = eposlib.VCS_OpenDevice(DeviceName, ProtocolStackName, InterfaceName, self._port_name, buf)
        self._keyhandle = ret
        if int(buf.contents.value) >= 0:
            self._is_open = True
            self._keyhandle = ret
        return

#3)
    """
    This uses u32todouble three times, for the three coefficients

    Okay here's the important part:
    self._coefA = CastedObjectData[0]

    self._doubleA = self._u32todouble(self._coefA)
    self._doubleB = self._u32todouble(self._coefB)
    self._doubleC = self._u32todouble(self._coefC)
    self._currentwl = self._doubleA*(self._offset)**2.0 + self._doubleB*self._offset + self._doubleC

    """
    def initialize(self):
        nodeID = ctypes.wintypes.WORD(0)
        buf = ctypes.wintypes.DWORD(0)
        BaudRate = DWORD(38400)
        Timeout = DWORD(100)
        ret = eposlib.VCS_SetProtocolStackSettings(self._keyhandle,BaudRate,Timeout,ctypes.byref(buf))
        if ret == 0:
            errbuf = ctypes.create_string_buffer(64)
            raise ValueError(errbuf.value)
        buf = ctypes.wintypes.DWORD(0)
        ret = eposlib.VCS_ClearFault(self._keyhandle,nodeID,ctypes.byref(buf))
        if ret == 0:
            errbuf = ctypes.create_string_buffer(64)
            eposlib.VCS_GetErrorInfo(buf, errbuf, WORD(64))
            raise ValueError(errbuf.value)
        buf = ctypes.wintypes.DWORD(0)
        plsenabled = ctypes.wintypes.DWORD(0)
        ret = eposlib.VCS_GetEnableState(self._keyhandle,nodeID,ctypes.byref(plsenabled),ctypes.byref(buf))
        if ret == 0:
            errbuf = ctypes.create_string_buffer(64)
            eposlib.VCS_GetErrorInfo(buf, errbuf, WORD(64))
            raise ValueError(errbuf.value)
        if int(plsenabled.value) != 0:
            logging.warning(__name__ + ' EPOS motor enabled, disabling before proceeding.')
            ret = eposlib.VCS_SetDisableState(self._keyhandle,nodeID,ctypes.byref(buf))
            if int(ret) != 0:
                logging.warning(__name__ + ' EPOS motor successfully disabled, proceeding')
            else:
                logging.error(__name__ + ' EPOS motor was not successfully disabled!')
        buf = ctypes.wintypes.DWORD(0)
        Counts = WORD(512) # incremental encoder counts in pulses per turn
        PositionSensorType = WORD(4)
        ret = eposlib.VCS_SetEncoderParameter(self._keyhandle,nodeID,Counts,PositionSensorType,ctypes.byref(buf))

        # Get operation mode, check if it's 1 -- this is "profile position mode"
        buf = ctypes.wintypes.DWORD(0)
        pMode = ctypes.pointer(ctypes.c_int8())
        eposlib.VCS_GetOperationMode.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.c_int8), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetOperationMode.restype = ctypes.wintypes.BOOL
        ret = eposlib.VCS_GetOperationMode(self._keyhandle, nodeID, pMode, ctypes.byref(buf))
        # if mode is not 1, make it 1
        if pMode.contents.value != 1:
            eposlib.VCS_SetOperationMode.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.c_int8, ctypes.POINTER(ctypes.wintypes.DWORD)]
            eposlib.VCS_SetOperationMode.restype = ctypes.wintypes.BOOL
            pMode_setting = ctypes.c_int8(1)
            ret = eposlib.VCS_SetOperationMode(self._keyhandle, nodeID, pMode_setting, ctypes.byref(buf))
        eposlib.VCS_GetPositionProfile.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetPositionProfile.restype = ctypes.wintypes.BOOL
        pProfileVelocity = ctypes.pointer(ctypes.wintypes.DWORD())
        pProfileAcceleration = ctypes.pointer(ctypes.wintypes.DWORD())
        pProfileDeceleration = ctypes.pointer(ctypes.wintypes.DWORD())
        ret = eposlib.VCS_GetPositionProfile(self._keyhandle, nodeID, pProfileVelocity, pProfileAcceleration, pProfileDeceleration,ctypes.byref(buf))

        #print(pProfileVelocity.contents.value, pProfileAcceleration.contents.value, pProfileDeceleration.contents.value)

        if (int(pProfileVelocity.contents.value) > int(11400) or int(pProfileAcceleration.contents.value) > int(60000) or int(pProfileDeceleration.contents.value) > int(60000)):
            eposlib.VCS_GetPositionProfile.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD)]
            eposlib.VCS_GetPositionProfile.restype = ctypes.wintypes.BOOL
            pProfileVelocity = ctypes.wintypes.DWORD(429)
            pProfileAcceleration = ctypes.wintypes.DWORD(429)
            pProfileDeceleration = ctypes.wintypes.DWORD(429)
            logging.warning(__name__ + ' GetPositionProfile out of bounds, resetting...')
            ret = eposlib.VCS_SetPositionProfile(self._keyhandle, nodeID, pProfileVelocity, pProfileAcceleration, pProfileDeceleration,ctypes.byref(buf))

        self._offset = self.get_offset()

        """DC - These are hardcoded values I got from the LabVIEW program -- I don't think any documentation exists on particular object indices"""

        """Coefficient A"""
        eposlib.VCS_GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetObject.restype = ctypes.wintypes.BOOL
        StoredPositionObject = ctypes.wintypes.WORD(8204)
        StoredPositionObjectSubindex = ctypes.c_uint8(1)
        StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4)
        ObjectData = ctypes.c_void_p()
        ObjectDataArray = (ctypes.c_uint32*1)()
        ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32))
        StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0))
        ret = eposlib.VCS_GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf))
        # Cast the object data to uint32
        CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_uint32))
        self._coefA = CastedObjectData[0]

        """Coefficient B"""
        eposlib.VCS_GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetObject.restype = ctypes.wintypes.BOOL
        StoredPositionObject = ctypes.wintypes.WORD(8204)
        StoredPositionObjectSubindex = ctypes.c_uint8(2)
        StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4)
        ObjectData = ctypes.c_void_p()
        ObjectDataArray = (ctypes.c_uint32*1)()
        ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32))
        StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0))
        ret = eposlib.VCS_GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf))
        # Cast the object data to uint32
        CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_uint32))
        self._coefB = CastedObjectData[0]

        """Coefficient C"""
        eposlib.VCS_GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetObject.restype = ctypes.wintypes.BOOL
        StoredPositionObject = ctypes.wintypes.WORD(8204)
        StoredPositionObjectSubindex = ctypes.c_uint8(3)
        StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4)
        ObjectData = ctypes.c_void_p()
        ObjectDataArray = (ctypes.c_uint32*1)()
        ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32))
        StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0))
        ret = eposlib.VCS_GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf))
        # Cast the object data to uint32
        CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_uint32))
        self._coefC = CastedObjectData[0]

        """Coefficient D"""
        eposlib.VCS_GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetObject.restype = ctypes.wintypes.BOOL
        StoredPositionObject = ctypes.wintypes.WORD(8204)
        StoredPositionObjectSubindex = ctypes.c_uint8(4)
        StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4)
        ObjectData = ctypes.c_void_p()
        ObjectDataArray = (ctypes.c_uint32*1)()
        ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32))
        StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0))
        ret = eposlib.VCS_GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf))
        # Cast the object data to uint32
        CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_uint32))
        self._coefD = CastedObjectData[0]

        """
        print('coefficients are %s %s %s %s' % (self._coefA, self._coefB, self._coefC, self._coefD))
        This gives the coefficients in some weird form, they're not what you expect them to be
        """

        self._doubleA = self._u32todouble(self._coefA)
        self._doubleB = self._u32todouble(self._coefB)
        self._doubleC = self._u32todouble(self._coefC)
        firstHalf = np.int16(self._coefD >> 16)
        secondHalf = np.int16(self._coefD & 0xffff)
        # Set the minimum and maximum wavelengths for the motor
        self._minwl = float(firstHalf)/10.0
        self._maxwl = float(secondHalf)/10.0
        # print 'first %s second %s' % (firstHalf, secondHalf)
        # This returns '10871' and '11859' for the Sacher, which are the correct
        # wavelength ranges in Angstroms
        #print 'Now calculate the current wavelength position:'
        self._currentwl = self._doubleA*(self._offset)**2.0 + self._doubleB*self._offset + self._doubleC
        #print('Current wavelength: %.3f nm' % self._currentwl)
        print('initializing done')
        print("")
        return True

#4)
    def get_offset(self):
        nodeID = ctypes.wintypes.WORD(0)
        buf = ctypes.wintypes.DWORD(0)
        eposlib.VCS_GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetObject.restype = ctypes.wintypes.BOOL
        #DC - These are hardcoded values I got from the LabVIEW program -- I don't think any documentation exists on particular object indices
        StoredPositionObject = ctypes.wintypes.WORD(8321)
        StoredPositionObjectSubindex = ctypes.c_uint8(0)
        StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4)
        ObjectData = ctypes.c_void_p()
        ObjectDataArray = (ctypes.c_uint32*1)()
        ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_int32))
        StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0))
        ret = eposlib.VCS_GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf))
        # Cast the object data to uint32
        CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_int32))
        if ret == 0:
            logging.error(__name__ + ' Could not read stored position from Sacher EPOS motor')
        print('motor offset value is: %s' % CastedObjectData[0])
        return CastedObjectData[0]

#5)
    def get_motor_position(self):
        nodeID = ctypes.wintypes.WORD(0)
        buf = ctypes.wintypes.DWORD(0)
        pPosition = ctypes.pointer(ctypes.c_long())
        eposlib.VCS_GetPositionIs.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.c_long), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetPositionIs.restype = ctypes.wintypes.BOOL
        ret = eposlib.VCS_GetPositionIs(self._keyhandle, nodeID, pPosition, ctypes.byref(buf))
        print('motor position value is: %s' % pPosition.contents.value)
        return pPosition.contents.value

#6)
    def do_get_wavelength(self):
        self._offset = self.get_offset()
        self._motor_position = self.get_motor_position()
        self._currentwl1 = self._doubleA*(self._offset)**2.0 + self._doubleB*self._offset + self._doubleC
        self._currentwl2 = self._doubleA*(self._motor_position)**2.0 + self._doubleB*self._motor_position + self._doubleC
        print('Current wavelength according to offset: %.3f nm' % self._currentwl1)
        print('Current wavelength according to motor position: %.3f nm' % self._currentwl2)
        return self._currentwl1

    """
    And now we move on to setting things
    """

#7)
    def set_new_offset(self, new_offset):
        """
        This is NOT using the function "eposlib.VCS_MoveToPosition"
        So there's no literal motor movement is going on here
        It's just storing a value in some sort of saved memory on the instrument itself
        """
        nodeID = ctypes.wintypes.WORD(0)
        buf = ctypes.wintypes.DWORD(0)
        eposlib.VCS_SetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_SetObject.restype = ctypes.wintypes.BOOL
        StoredPositionObject = ctypes.wintypes.WORD(8321)
        StoredPositionObjectSubindex = ctypes.c_uint8(0)
        StoredPositionNbBytesToWrite = ctypes.wintypes.DWORD(4)
        ObjectDataArray = (ctypes.c_uint32*1)(new_offset)
        ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32))
        StoredPositionNbBytesWritten = ctypes.pointer(ctypes.wintypes.DWORD(0))
        ret = eposlib.VCS_SetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToWrite, StoredPositionNbBytesWritten, ctypes.byref(buf))
        if ret == 0:
            logging.error(__name__ + ' Could not write stored position from Sacher EPOS motor')
        return

#8)
    def set_coeffs(self, a, b, c, min_wl, max_wl):
        print('')
        print("setting coefficients...")
        nodeID = ctypes.wintypes.WORD(0)
        buf = ctypes.wintypes.DWORD(0)
        eposlib.VCS_SetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_SetObject.restype = ctypes.wintypes.BOOL
        d = (min_wl << 16) + max_wl
        StoredPositionObject = ctypes.wintypes.WORD(8204)

        for subidx, coeff in enumerate([a, b, c]):
            print(subidx, coeff)
            StoredPositionObjectSubindex = ctypes.c_uint8(subidx + 1)
            StoredPositionNbBytesToWrite = ctypes.wintypes.DWORD(4)
            ObjectDataArray = (ctypes.c_uint32*1)(self._doubletou32(coeff))
            ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32))
            StoredPositionNbBytesWritten = ctypes.pointer(ctypes.wintypes.DWORD(0))
            ret = eposlib.VCS_SetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToWrite, StoredPositionNbBytesWritten, ctypes.byref(buf))

        StoredPositionObjectSubindex = ctypes.c_uint8(4)
        StoredPositionNbBytesToWrite = ctypes.wintypes.DWORD(4)
        ObjectDataArray = (ctypes.c_uint32*1)(d)
        ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32))
        StoredPositionNbBytesWritten = ctypes.pointer(ctypes.wintypes.DWORD(0))
        ret = eposlib.VCS_SetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToWrite, StoredPositionNbBytesWritten, ctypes.byref(buf))

        print('Coefficients are %s %s %s' % (self._doubleA, self._doubleB, self._doubleC))

        if ret == 0:
            logging.error(__name__ + ' Could not write stored position from Sacher EPOS motor')
        return

#9)
    def set_target_position(self, target, absolute, immediately):
        """
        This is the function that actually moves the motor
        Since the motor position this thing reads can't be trusted, we're only doing relative movements and not absolute ones
        This means the "absolute" argument should always be set as "false"

        "target" is the target motor position, but be very careful what you want to set this to depending on whether you're doing an absolute or relative movement

        In the "absolute" category,
        True starts an absolute movement, False starts a relative movement
        Since the absolute position can't be trusted at all, we should always do "False", the relative movement

        In the "immediately" category,
        True starts immediately, False waits to end of last positioning
        We typically do True for this without any real issues

        The actual money function is "eposlib.VCS_MoveToPosition"
        """

        nodeID = ctypes.wintypes.WORD(0)
        buf = ctypes.wintypes.DWORD(0)
        ret = eposlib.VCS_SetEnableState(self._keyhandle,nodeID,ctypes.byref(buf))
        pTarget = ctypes.c_long(target)
        pAbsolute = ctypes.wintypes.BOOL(absolute)
        pImmediately = ctypes.wintypes.BOOL(immediately)
        eposlib.VCS_MoveToPosition.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.c_long, ctypes.wintypes.BOOL, ctypes.wintypes.BOOL, ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_MoveToPosition.restype = ctypes.wintypes.BOOL
        ret = eposlib.VCS_MoveToPosition(self._keyhandle, nodeID, pTarget, pAbsolute, pImmediately, ctypes.byref(buf))
        steps_per_second = 14494.0 # hardcoded, estimated roughly, unused now
        nchecks = 0
        while nchecks < 1000:
            self._motor_position = self.get_motor_position()
            self._offset = self.get_offset()
            pMovementState = ctypes.pointer(ctypes.wintypes.BOOL())
            eposlib.VCS_GetMovementState.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.wintypes.BOOL), ctypes.POINTER(ctypes.wintypes.DWORD)]
            eposlib.VCS_GetMovementState.restype = ctypes.wintypes.BOOL
            ret = eposlib.VCS_GetMovementState(self._keyhandle, nodeID, pMovementState, ctypes.byref(buf))
            if pMovementState.contents.value == 1:
                break
            nchecks = nchecks + 1
            time.sleep(0.01)
        ret = eposlib.VCS_SetDisableState(self._keyhandle,nodeID,ctypes.byref(buf))
        return ret

#10)
    def do_set_wavelength(self, wavelength):
        """
        Here's the basic procedure:
        1) Convert the desired target wavelength into a motor position, keeping in mind that we're using the offset as the motor position
        x is what the motor position should be
        2) Calculate difference between the target position and the stored offset
        3) Prompt some confirmation before we crash this plane
        4) Then actually move the motor
        """
        current_wavelength = self.do_get_wavelength()
        current_offset = self.get_offset()

        nodeID = ctypes.wintypes.WORD(0)
        buf = ctypes.wintypes.DWORD(0)
        x = (-1.0*self._doubleB + np.sqrt(self._doubleB**2.0 - 4.0*self._doubleA*(self._doubleC - wavelength))) / (2.0*self._doubleA)
        wavelength_to_pos = int(round(x))
        diff_wavelength_offset = wavelength_to_pos - int(self._offset)

        print('')
        print("The current wavelength, as according to the stored offset, is: %s" % current_wavelength)
        print("You're about to set the wavelength to: %s" % wavelength)
        print("This means moving the motor by %s steps" % diff_wavelength_offset)
        print("Where currently, the stored offset is: %s" % current_offset)
        # confirm = str(input("Is this ok? (y/n)"))
        #
        # if confirm == 'y':
        #     print('Ok then, setting wavelength...')
        #     print('')
        # else:
        #     print("ok then, shutting the whole thing down!")
        #     sys.exit(-1)

        if self._HPM and diff_wavelength_offset < 0:
            self.set_target_position(diff_wavelength_offset - 10000, False, True)
            self.set_target_position(10000, False, True)
        else:
            self.set_target_position(diff_wavelength_offset, False, True)

        self.set_new_offset(current_offset + diff_wavelength_offset)
        current_offset = self.get_offset()
        print("Now the stored offset is: %s" % current_offset)

        return

#11)
    @staticmethod
    def get_bit(byteval, idx):
    # def get_bit(self, byteval,idx):
        return ((byteval&(1<< idx ))!=0)
    """
    We take the two input numbers "byteval" and "idx" and do bitwise operations with them
    but it's not clear what the purpose of these bitwise operations are
    I also don't know what the @staticmethod decorator is supposed to be doing
    """

#12)
    @staticmethod
    def _u32todouble(uinput):
    # def _u32todouble(self, uinput):

        # get sign of number
        sign = Sacher_EPOS.get_bit(uinput,31)
        if sign == False:
            mantissa_sign = 1
        elif sign == True:
            mantissa_sign = -1
        exp_mask =  0b111111
        #print 'uin u is %d' % uinput
        #print 'type uin %s' % type(uinput)
        #print 'binary input is %s' % bin(long(uinput))
        # get sign of exponent
        if Sacher_EPOS.get_bit(uinput,7) == False:
            exp_sign = 1
        elif Sacher_EPOS.get_bit(uinput,7) == True:
            exp_sign = -1

        #print 'exp extract %s' % bin(int(uinput & exp_mask))
        #print 'exp conv %s' % (exp_sign*int(uinput & exp_mask))
        #print 'sign of exponent %s' % self.get_bit(uinput,7)
        #print 'binary constant is %s' % bin(int(0b10000000000000000000000000000000))
        mantissa_mask = 0b01111111111111111111111100000000
        # mantissa_mask = 0b0111111111111111111111110000000

        #print 'mantissa extract is %s' % bin((uinput & mantissa_mask) >> 8)
        mantissa = 1.0/1000000.0*float(mantissa_sign)*float((uinput & mantissa_mask) >> 8)
        #print 'mantissa is %.12f' % mantissa
        # print(1 if Sacher_EPOS.get_bit(uinput,31) else 0, mantissa, 1 if Sacher_EPOS.get_bit(uinput,7) else 0, uinput & exp_mask)
        output = mantissa*2.0**(float(exp_sign)*float(int(uinput & exp_mask)))
        #print 'output is %s' % output
        return output
    """
    ok dc gave some slight explanations here
    This function implements the "really weird/non-standard U32 to floating point conversion in the sacher VIs"
    It'd be gr8 if I knew what U32's were
    unsigned 32 bit something something?
    ah whatever
    Also I'm seeing mantissas and masks, this is bad
    """

#13)
    @staticmethod
    def _doubletou32(dinput):
        mantissa_bit = 0 if int(dinput / abs(dinput)) > 0 else 1
        exp_bit = 1 if -1 < dinput < 1 else 0

        b = np.ceil(np.log10(abs(dinput)))
        a = dinput / 10 ** b
        if dinput < 0:
            a = -a
        # print('a:\t{}\tb:\t{}'.format(a, b))

        d = np.log2(10) * b
        d_ = np.ceil(d)
        c = a * 2 ** (d - d_)
        # print('c:\t{}\td_:{}\toriginal:\t{}'.format(c, d_, c * 2 ** d_))

        return (int(mantissa_bit) << 31) + (int(c * 1e6) << 8) + (int(exp_bit) << 7) + int(abs(d_))

    """
    I think this was a new function that we made

    There might be a labview VI that does this correctly
    """

#14)
    def __del__(self):
        # execute disconnect
        self.close()
        return
    """
    this might be the only self explanatory one
    it disconnects
    """

#15)
    def close(self):
        print('closing EPOS motor.')

        eposlib.VCS_CloseDevice.argtypes = [ctypes.wintypes.HANDLE, ctypes.POINTER(DWORD)]
        eposlib.VCS_CloseDevice.restype = ctypes.wintypes.BOOL
        buf = ctypes.pointer(DWORD(0))
        ret = ctypes.wintypes.BOOL()

        ret = eposlib.VCS_CloseDevice(self._keyhandle, buf)

        #print 'close device returned %s' % buf

        if int(buf.contents.value) >= 0:
            self._is_open = False
        else:
            logging.error(__name__ + ' did not close Sacher EPOS motor correctly.')
        return
    """
    Apparently this closes the EPOS motor
    I don't know what "opening" and "closing" the motor means though
    and yeah also these random variables don't make any sense to me
    """

#16)
    def get_motor_current(self):
        nodeID = ctypes.wintypes.WORD(0)
        eposlib.VCS_GetCurrentIs.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.c_uint8), ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_GetCurrentIs.restype = ctypes.wintypes.BOOL

        motorCurrent = ctypes.c_uint8(0)
        buf = ctypes.wintypes.DWORD(0)
        ret = eposlib.VCS_GetCurrentIs(self._keyhandle, nodeID, ctypes.byref(motorCurrent), ctypes.byref(buf))
        return motorCurrent.value

    """
    Not sure what this is doing yet
    """

#17)
    def find_home(self):
        nodeID = ctypes.wintypes.WORD(0)
        eposlib.VCS_FindHome.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_FindHome.restype = ctypes.wintypes.BOOL

        buf = ctypes.wintypes.DWORD(0)
        ret = eposlib.VCS_FindHome(self._keyhandle, nodeID, ctypes.c_uint8(35), ctypes.byref(buf))
        print('Homing: {}'.format(ret))
        return ret

    """
    Not sure what this is doing yet
    """

#18)
    def restore(self):
        nodeID = ctypes.wintypes.WORD(0)
        eposlib.VCS_FindHome.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.wintypes.DWORD)]
        eposlib.VCS_FindHome.restype = ctypes.wintypes.BOOL
        buf = ctypes.wintypes.DWORD(0)
        ret = eposlib.VCS_Restore(self._keyhandle, nodeID, ctypes.byref(buf))
        print('Restore: {}'.format(ret))
        return ret

    """
    Not sure what this is doing yet
    """

#19)
    def fine_tuning_steps(self, steps):
        current_motor_pos = self.get_motor_position()
        self._offset = self.get_offset()
        self.set_target_position(steps, False, True)
        new_motor_pos = self.get_motor_position()
        #print('New motor position is %s' % new_motor_pos)
        #print 'new offset is %s' % (new_motor_pos-current_motor_pos+self._offset)
        self.set_new_offset(new_motor_pos-current_motor_pos+self._offset)

    """
    Not sure what this is doing yet
    """

#20)
    def is_open(self):
        return self._is_open

#21)
    def clear_fault(self):
        nodeID = ctypes.wintypes.WORD(0)
        buf = ctypes.wintypes.DWORD(0)
        ret = eposlib.VCS_ClearFault(self._keyhandle,nodeID,ctypes.byref(buf))
        print('clear fault buf %s, ret %s' % (buf, ret))
        if ret == 0:
            errbuf = ctypes.create_string_buffer(64)
            eposlib.VCS_GetErrorInfo(buf, errbuf, WORD(64))
            raise ValueError(errbuf.value)
    """
    Not sure what this is doing yet
    """

"""
We're done with the Sacher_EPOS() class at this point
"""


# -*- encoding: ascii -*-
# Python 2 and 3 compatible
from __future__ import division, absolute_import, print_function
from prga.compatible import *

from prga.util import enable_stdout_logging, uno
from prga.flow.context import ArchitectureContext
from prga.exception import PRGAAPIError

from prga_tools.util import find_verilog_top, parse_io_bindings, parse_parameters
from prga_tools.iobind import iobind
from prga_tools.ysgen import generate_yosys_script
from prga_tools.bitchain.simproj.tbgen import generate_testbench_wrapper
from prga_tools.bitchain.simproj.mkgen import generate_makefile

import argparse
import logging
import jinja2 as jj
import os

_logger = logging.getLogger(__name__)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
            description="Simulation project wizard for bitchain-style configuration circuitry")

    parser.add_argument('context', type=str, help="Pickled architecture context object")

    parser.add_argument('-c', '--compiler', type=str, choices=['vcs', 'iverilog'], dest='compiler', default="vcs",
            help="Verilog compiler used to build the simulator")
    parser.add_argument('--fix_io', type=str, dest="io",
            help="Partial or full assignments of IO pads")

    parser.add_argument('-t', '--testbench', type=str, nargs='+', dest="testbench",
            help="Testbench file(s) for behavioral model")
    parser.add_argument('--testbench_top', type=str,
            help="Top-level module name of the testbench. Required if the testbench comprises multiple files/modules")
    parser.add_argument('--testbench_includes', type=str, nargs="+", default=[],
            help="Include directories for the testbench")
    parser.add_argument('--testbench_defines', type=str, nargs="+", default=[],
            help="Macros for the testbench. Use MACRO for valueless macro, and MACRO=VALUE for macros with value")
    parser.add_argument('--testbench_parameters', type=str, nargs="+", default=[],
            help="Parameters for the testbench: PARAMETER0=VALUE0 PARAMETER1=VALUE1 ...")
    parser.add_argument('--testbench_plus_args', type=str, nargs="+", default=[],
            help="Plus arguments to run the testbench. Use ARG for valueless args, and ARG=VALUE for args with value")

    parser.add_argument('-m', '--model', type=str, nargs='+', dest="model",
            help="Source file(s) for the target design")
    parser.add_argument('--model_top', type=str,
            help="Top-level module name of the target design. Required if the design comprises multiple files/modules")
    parser.add_argument('--model_includes', type=str, nargs="+", default=[],
            help="Include directories for the target design")
    parser.add_argument('--model_defines', type=str, nargs="+", default=[],
            help="Macros for the target design. Use MACRO for valueless macro, and MACRO=VALUE for macros with value")
    parser.add_argument('--model_parameters', type=str, nargs="+", default=[],
            help="Parameters for the behavioral model: PARAMETER0=VALUE0 PARAMETER1=VALUE1 ...")

    parser.add_argument('--makefile', type=str,
            help="Name of the generated Makefile. 'Makefile' by default")
    parser.add_argument('--wrapper', type=str,
            help="Name of the generated testbench wrapper. 'TESTBENCH_wrapper.v' by default")
    parser.add_argument('--yosys_script', type=str,
            help="Name of the generated design-specific synthesis script. 'synth.ys' by default")

    args = parser.parse_args()
    enable_stdout_logging(__name__, logging.INFO)

    # get verilog template
    env = jj.Environment(loader=jj.FileSystemLoader(
        os.path.join(os.path.abspath(os.path.dirname(__file__)), 'templates'), ))

    _logger.info("Unpickling architecture context: {}".format(args.context))
    context = ArchitectureContext.unpickle(args.context)
    channel_width = 2 * sum(sgmt.width * sgmt.length for sgmt in itervalues(context.segments))

    _logger.info("Reading target design ...")
    if args.model is None:
        raise PRGAAPIError("Source file(s) for target design not given")
    model_top = find_verilog_top(args.model, args.model_top)
    model_top.parameters = parse_parameters(args.model_parameters)

    _logger.info("Reading testbench ...")
    if args.testbench is None:
        raise PRGAAPIError("Source file(s) for testbench not given")
    testbench_top = find_verilog_top(args.testbench, args.testbench_top)
    testbench_top.parameters = parse_parameters(args.testbench_parameters)

    _logger.info("Assigning IO ...")
    io_assignments = iobind(context, model_top,
            parse_io_bindings(args.io) if args.io is not None else {})
    ostream = open('io.pads', 'w')
    for name, (x, y, subblock) in iteritems(io_assignments):
        ostream.write("{} {} {} {}\n".format(name, x, y, subblock))

    _logger.info("Generating testbench wrapper ...")
    wrapper = uno(args.wrapper, '{}_wrapper.v'.format(testbench_top.name))
    generate_testbench_wrapper(context, env.get_template('tb.tmpl.v'), wrapper, testbench_top, model_top, io_assignments)

    _logger.info("Generating design-specific synthesis script ...")
    script = uno(args.yosys_script, 'synth.ys')
    generate_yosys_script(open(script, 'w'), args.model, model_top, context._yosys_script)

    _logger.info("Generating Makefile ...")
    makefile = uno(args.makefile, 'Makefile')
    ostream = open(makefile, 'w')
    generate_makefile(args.context, env.get_template('tmpl.Makefile'), ostream,
            testbench_top, args.testbench, model_top, args.model, script,
            channel_width, context._vpr_archdef, context._vpr_rrgraph,
            'io.pads', context._verilog_sources, wrapper, args.compiler, args.testbench_plus_args,
            args.testbench_includes, args.testbench_defines, args.model_includes, args.model_defines)

    _logger.info("Simulation project generated. Bye")