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))


#!/usr/bin/env python

import argparse
import os
import subprocess

def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('outdir')
    parser.add_argument('-p', '--prefix', help="add a prefix")
    parser.add_argument('inputs', nargs='+')
    return parser.parse_args()

def main():
    args = get_args()
    for inpath in args.inputs:
        name_parts = inpath.split('/')[-2:]
        if args.prefix:
            name_parts = [args.prefix] + name_parts
        outpath = os.path.join(args.outdir, '-'.join(name_parts))
        subprocess.check_call(['cp', inpath, outpath])

if __name__ == '__main__':
    main()


#!/usr/bin/env python3.7

from collections import defaultdict
from graph_lib.edge import Edge
from graph_lib.exception import InvalidGraphException

"""
Represents the whole graph with additional functions for it
"""

class Graph:
    def __init__(self):
        self.numOfVertices = 0                  # Number of vertices
        self.uniqueVertices = []                # Storing unique vertices
        self.adjacencyList = defaultdict(list)  # Graph G(V,E)
        self.source = -1                        # Source vertex
        self.sink = -1                          # Sink vertex
        self.used = []                          # Storing used vertices

    # Adds Edge(u, v) with capacity c to the graph G(V,E)
    def addEdge(self, edge_):
        for i in range(len(self.adjacencyList[edge_.u])):
            if self.adjacencyList[edge_.u][i].u == edge_.u and self.adjacencyList[edge_.u][i].v == edge_.v:
                raise InvalidGraphException("Error: Edge ({}, {}) already exists. Skipping".format(
                                                    self.adjacencyList[edge_.u][i].u,
                                                    self.adjacencyList[edge_.u][i].v))
        self.checkIfNewVertex(edge_.u, edge_.v)                              # Does an edge contain a unique vertex?
        self.adjacencyList[edge_.u].append(Edge(edge_.u, edge_.v, edge_.c))  # Add a new edge

    # Is it a new vertex?
    def checkIfNewVertex(self, u, v):
        if u not in self.uniqueVertices:
            self.uniqueVertices.append(u)
        if v not in self.uniqueVertices:
            self.uniqueVertices.append(v)

        self.numOfVertices = len(self.uniqueVertices)

    # Checks if this edge is already in the present graph
    def doesAnEdgeAlreadyExist(self, edge_):
        for i in range(len(self.adjacencyList[edge_.u])):
            if self.adjacencyList[edge_.u][i].u == edge_.u and self.adjacencyList[edge_.u][i].v == edge_.v:
                return True
        return False

    # Sets source
    def setSource(self, source_):
        if source_ not in self.uniqueVertices:
            raise InvalidGraphException("Error: Vertex ({}) does not exist".format(source_))
        else:
            self.source = source_

    # Sets sink
    def setSink(self, sink_):
        if sink_ not in self.uniqueVertices:
            raise InvalidGraphException("Error: Vertex ({}) does not exist".format(sink_))
        else:
            self.sink = sink_

    # A recursive function that finds a path between source and sink and calculates its flow
    def pathDFS(self, source, sink, flow):
        if source == sink:
            return flow
        self.used.append(source)  # mark as visited
        for i in range(len(self.adjacencyList[source])):
            edge = self.adjacencyList[source][i]
            if self.used.count(edge.v) == 0 and edge.c > 0:  # if vertex isn't visited and has capacity > 0
                flow_through = self.pathDFS(edge.v, sink, flow if flow < edge.c else edge.c)
                if flow_through > 0:
                    self.adjacencyList[source][i].c -= flow_through
                    self.adjacencyList[source][i].f += flow_through
                    self.used.clear()
                    return flow_through
        return 0

    # A helper function to find max flow between source and sink
    def findMaxFlow(self):
        if self.source == self.sink:
            raise InvalidGraphException("Error: Source == Sink")
        max_flow = 0
        while True:  # till there's a flow that is > 0
            partial_flow = self.pathDFS(self.source, self.sink, float('inf'))
            if partial_flow == 0:
                return max_flow
            max_flow += partial_flow

    # Prints graph G(V,E)
    def print(self):
        for key, value in self.adjacencyList.items():
            for list_key in range(len(self.adjacencyList[key])):
                edge = self.adjacencyList[key][list_key]
                print("u=",edge.u, "v=",edge.v, "f=",edge.f, "c=",edge.c, "old_cap=", edge.old_cap)


#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
"""


# *_*coding:utf-8 *_*
import csv
import datetime
import logging
import os
import threading
import time

from config import *
import pandas as pd
import requests

# account = 'DEMO201812037'
# a = Account('73883669')

prodcut_dict = ["YMH9", "HSIZ8", "HSIF9", "SSIH9"]

class Account:
    def __init__(self, account):
        self.logger = logging.getLogger("SP_Account " + str(account))
        self.account = account
        self.order_id_list = list()
        self.balance = 0  # 总余额
        self.margin = 0  # 已使用保证金
        self.maintain_margin = 0  # 维持保证金
        self.buy_power = 0  # 购买力 约等于余额减去保证金
        self.pos = {}  # 当前仓位
        self.order_status = 0  # 无挂单/有挂单
        self.pos_status = 0  # 有仓位/无仓位
        # self.__rest_root = 'http://bxx.pub'
        self.__rest_root = 'http://47.75.194.25:8081'
        # self.__rest_root = "http://localhost:8081"
        self.login()
        self.check_all_position()
        self.get_account()
        self.get_orders()
        self.count = 1
        # for prod in prodcut_dict:
        #     self.get_depth(prod)

    # 用requests实现各种api方法
    def __dispose_info(self, ret):
        """
        处理返回信息
        :param ret: 返回信息
        :return:
        """
        try:
            if ret['code'] == 1 and ret['data'] == '':
                return ret['msg']
            elif ret['code'] == 1 and ret['data'] != 'null':
                return ret['data']
            else:
                self.logger.error('账号：' + str(self.account) + str(ret))
                return False
        except:
            self.logger.error('账号：' + str(self.account) + str(ret))
            return False

    def login(self):
        try:
            ret = requests.get(self.__rest_root + sp_login + "{}".format(self.account), timeout=5).json()
            self.logger.info('账号：' + str(self.account) + str(self.__dispose_info(ret)))
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))

    def logout(self):
        try:
            ret = requests.get(self.__rest_root + sp_logout + '{}'.format(self.account), timeout=5).json()
            self.logger.info('账号：' + str(self.account) + str(self.__dispose_info(ret)))
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))

    def get_account(self):
        """
        获取账户信息
        :return:
        """
        try:
            self.check_all_position()
            if self.pos.keys():
                for proCode in self.pos.keys():
                    self.subscription(proCode)
            ret = requests.get(self.__rest_root + sp_account + "{}".format(self.account), timeout=5).json()
            print(ret,"1111")
            msg = self.__dispose_info(ret)
            # self.logger.info('账号：' + str(self.account) + msg)
            if msg:
                self.balance = msg['nav']
                self.margin = msg['iMargin']
                self.buy_power = msg['buyingPower']
                self.maintain_margin = msg['mmargin']  # 维持保证金
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))

    def get_orders(self):
        """
        查询未成交订单
        :return:
        """
        try:
            ret = requests.get(self.__rest_root + sp_get_order + '{}'.format(self.account), timeout=5).json()
            msg = self.__dispose_info(ret)
            if msg and type(msg) is list:
                order_list = [i for i in msg if i['status'] == 1]
                if order_list:
                    self.order_status = 1
                return order_list
            else:
                if msg == '没有订单':
                    self.order_status = 0
                self.logger.info('账号：' + str(self.account) + str(ret))
                return []
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def get_trade_order(self):
        """
        查询已成交订单
        :return:
        """
        try:
            ret = requests.get(self.__rest_root + sp_get_trade_order + '{}'.format(self.account), timeout=5).json()
            if ret == "系统没有成交单":
                ret = []
            return self.__dispose_info(ret)
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def get_order(self, order_id):
        """
        根据订单id查询
        :return:
        """
        try:
            ret = requests.get(self.__rest_root + sp_get_order_id + "{}/{}".format(self.account, order_id), timeout=5).json()
            return self.__dispose_info(ret)
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def add_order(self, order_type, prod_code, side, quantity, price=0, order_options=0, valid_type=0,
                  status=1):
        """
        下单
        :param order_type:0 限价 6市价
        :param prod_code: 商品编码
        :param side: 买卖类型
        :param quantity: 量
        :param price: 价格
        :param order_options: 默认0  有时1
        :param valid_type: 0当天有效 3直到有效期
        :param status: 1为有效单 2为无效单
        :return:
        """
        dec_dic = {'HO': 4, 'HSI': 0, 'SSI': 0, 'YM': 0}
        if prod_code[0:-2] in dec_dic.keys():
            dec_in_price = dec_dic[prod_code[0:-2]]
        else:
            self.logger.error('账号：' + str(self.account) + ' 没有此类商品的精度')
            raise ValueError
        if prod_code[0:-2] == "HSI":
            order_options = 1
            valid_type = 3
        params = {
            'prodCode': prod_code,
            'buySell': side,
            'qty': quantity,
            'orderOptions': order_options,
            'validType': valid_type,
            'orderType': order_type,
            'status': status,
            'userId': self.account,
            'decInPrice': dec_in_price,
        }
        if order_type == 0:
            params['price'] = price
        try:
            ret = requests.post(self.__rest_root + sp_add_order + '{}'.format(self.account), json=params, timeout=5).json()
            # return self.__dispose_info(ret)
            # 判断是否请求成功
            if self.__dispose_info(ret) is not False:
                old_pos = self.pos
                self.check_all_position()
                if prod_code not in self.pos.keys():
                    self.logger.error('账号：' + str(self.account) + ' prod_code: %s, 下单失败' % prod_code)
                    return False
                else:
                    if prod_code not in old_pos.keys():
                        # 下单成功
                        return prod_code, self.pos[prod_code]
                    else:
                        if old_pos[prod_code] == self.pos[prod_code]:
                            # 下单失败
                            return False
                        else:
                            return prod_code, self.pos[prod_code]
            else:
                return False
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def cancel_all(self):
        """
        撤销所有订单
        :return:
        """
        try:
            ret = requests.get(self.__rest_root + sp_cancel_all_order + '{}'.format(self.account), timeout=5).json()
            return self.__dispose_info(ret)
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def cancel_order(self, order_id, prod_code, cl_order_id=''):
        """
        撤单
        :param order_id: 订单号
        :param prod_code: 商品种类
        :param cl_order_id: 客户id
        :return:
        """
        try:
            ret = requests.get(
                self.__rest_root + sp_cancel_order_id + "{}/{}/{}/{}".format(self.account, order_id, prod_code, cl_order_id),
                timeout=5).json()
            return self.__dispose_info(ret)
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def check_position(self, prod_code):
        """
        查询持仓
        :param prod_code: 商品编码
        :return:
        """
        try:
            ret = requests.get(self.__rest_root + sp_check_pos + "{}/{}".format(self.account, prod_code), timeout=5).json()
            msg = self.__dispose_info(ret)
            if chr(msg['longShort']) == 'B':
                msg['q'] = msg['qty'] + msg['longQty'] - msg['shortQty']
            elif chr(msg['longShort']) == 'S':
                msg['q'] = -msg['qty'] + msg['longQty'] - msg['shortQty']
            return msg
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def check_all_position(self):
        """
        查询全部持仓
        :return:
        """
        try:
            ret = requests.get(self.__rest_root + sp_check_all_pos + '{}'.format(self.account), timeout=5).json()
            msg = self.__dispose_info(ret)
            if msg and type(msg) is list:
                for i in msg:
                    if chr(i['longShort']) == 'B':
                        i['q'] = i['qty'] + i['longQty'] - i['shortQty']
                    elif chr(i['longShort']) == 'S':
                        i['q'] = -i['qty'] + i['longQty'] - i['shortQty']
                    self.pos[i['prodCode']] = {"q": i["q"]}
                status_list = list()
                for symbol in self.pos.keys():
                    for B_S in self.pos[symbol].keys():
                        status_list.append(self.pos[symbol][B_S])
                if len(set(status_list)) == 1 and list(set(status_list))[0] == 0:
                    self.pos_status = 0
                else:
                    self.pos_status = 1
                return self.pos
            else:
                return self.pos
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def subscription(self, prod_code, subscribe='price', mode=1):
        """
        订阅或取消订阅
        :param subscribe: price ticker quoteRequest
        :param prod_code: 商品编号
        :param mode: 0取消订阅 1订阅市场数据
        :return:
        """
        try:
            ret = requests.get(self.__rest_root + "/{}/{}/{}/{}".format(subscribe, self.account, prod_code, mode),
                               timeout=5).json()
            msg = self.__dispose_info(ret)
            if msg == '订阅成功':
                # print(msg)
                self.logger.info('账号：' + str(self.account) + str(msg))
            else:
                # print(ret)
                self.logger.info('账号：' + str(self.account) + str(ret))
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def get_depth(self, prod_code):
        """
        查询市场价格深度
        :param prod_code: 商品编码
        :return:
        """
        try:
            ret = requests.get(self.__rest_root + sp_market_msg + "{}/{}".format(self.account, prod_code), timeout=5).json()
            msg = self.__dispose_info(ret)
            # print(ret)
            # print(msg)
            depth_dic = dict()
            depth_dic['bid'] = [b_price for b_price in msg['bid'] if b_price > 0]
            if not depth_dic['bid']:
                if self.count % 5 != 0:
                    self.subscription(prod_code)
                    self.count += 1
                    return self.get_depth(prod_code)
                else:
                    self.logger.error('连续出现五次， 获取不到深度的情况')
                    self.count = 1
                    return False
            depth_dic['bidQty'] = [bidqty for bidqty in msg['bidQty'] if bidqty > 0]
            depth_dic['ask'] = [a_price for a_price in msg['ask'] if a_price > 0]
            depth_dic['askQty'] = [askqty for askqty in msg['askQty'] if askqty > 0 or 0.0]
            depth_dic['timestamp'] = msg['timestamp']
            depth_dic["last"] = msg["last"]
            return depth_dic
        except Exception as e:
            self.logger.error('账号：' + str(self.account) + str(e))
            return False

    def write_data_for_file(self, prod_code):
        while True:
            try:
                ret = requests.get(self.__rest_root + sp_market_msg + "{}/{}".format(self.account, prod_code), timeout=5).json()
                # print(ret)
                if ret['data']['timestamp'] == '0':
                    self.subscription(prod_code)
                    continue
                price = ret['data']['last'][0]
                quantity = ret['data']['lastQty'][0]
                timestamp = ret['data']['timestamp']
                date = datetime.datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S')
                cur_path = os.getcwd() + os.path.sep + 'data'
                file_name = date.strftime(
                    '%Y-%m-%d') + '_' + prod_code + '.csv'
                if os.path.exists(cur_path):
                    if not os.path.exists(cur_path + os.path.sep + prod_code):
                        os.mkdir(cur_path + os.path.sep + prod_code)
                else:
                    os.makedirs(cur_path + os.path.sep + prod_code)
                data = [[prod_code, price, quantity, date]]
                # print(curPath + os.path.sep + prodCode + os.path.sep + fileName)
                try:
                    csv.reader(open(cur_path + os.path.sep + prod_code + os.path.sep + file_name, encoding='utf-8'))
                    save_data = pd.DataFrame(data)
                    save_data.to_csv(cur_path + os.path.sep + prod_code + os.path.sep + file_name, header=False,
                                     index=False, mode='a+',
                                     encoding='utf-8')
                except:
                    save_data = pd.DataFrame(data)
                    csv_headers = ['Type', 'Price', 'quantity', 'date']
                    save_data.to_csv(cur_path + os.path.sep + prod_code + os.path.sep + file_name, header=csv_headers,
                                     index=False,
                                     mode='a+', encoding='utf-8')
                # 存储数据
            except Exception as e:
                self.logger.error('prod_code:' + prod_code + 'error_msg:' + str(e))
                break
            time.sleep(1)

    def get_data(self, prod_code):
        th = threading.Thread(target=self.write_data_for_file, args=(prod_code,))
        th.start()

    def get_kline(self, prod_code):
        date = datetime.datetime.now()
        cur_path = os.getcwd() + os.path.sep + 'data' + os.path.sep + prod_code + os.path.sep + date.strftime(
            '%Y-%m-%d') + '_' + prod_code + '.csv'
        if not os.path.exists(cur_path):
            # print(prod_code + '找不到当天的数据，')
            self.logger.info('prod_code: ' + prod_code + ' error_msg: canot find the day data')
            return False
        else:
            df = pd.read_csv(cur_path)
            start_time = datetime.datetime(date.year, date.month, date.day, date.hour, date.minute)
            index_list = []
            count = 0
            for i in df['date']:
                if (start_time + datetime.timedelta(minutes=1)) > datetime.datetime.strptime(i,
                                                                                '%Y-%m-%d %H:%M:%S') >= start_time:
                    index_list.append(count)
                    count += 1
                else:
                    count += 1
            if index_list:
                kline_data = df[min(index_list):max(index_list) + 1]
                # print(kline_data)
                kline = [kline_data['Price'][min(index_list)], max(kline_data['Price']), min(kline_data['Price']),
                         kline_data['Price'][max(index_list)], sum(kline_data['quantity']),
                         start_time.strftime('%Y-%m-%d %H:%M:%S')]
                return kline
            else:
                # print(start_time.strftime('%Y-%m-%d %H:%M:%S') + '没有抓到这一分钟的数据')
                self.logger.warning('没有抓到这一分钟的数据')
                return False