#!/usr/bin/python

import Tkinter, Tkconstants, tkFileDialog, tkMessageBox, re, datetime
from Tkinter import *
from xml.dom import minidom


import ttk

def sortby(tree, col, descending):
    """Sort tree contents when a column is clicked on."""
    """From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21"""
    # grab values to sort
    data = [(tree.set(child, col), child) for child in tree.get_children('')]

    # reorder data
    data.sort(reverse=descending)
    for indx, item in enumerate(data):
        tree.move(item[1], '', indx)

    # switch the heading so that it will sort in the opposite direction
    tree.heading(col,
        command=lambda col=col: sortby(tree, col, int(not descending)))

  
class IDMapper_frame_gui(Frame):
    
    def __init__(self, parent):
        """Initialize the application"""
        self.parent = parent

        # Set up the dictionary map
        self.IDMap = {}

        # Initialize the frame
        self.initialize()


    def initialize(self):

        # initialize Frame
        self.frame = Frame(self.parent)
        self.frame.pack(expand=1, fill='both')
        self.frame.columnconfigure(0, weight=1)
        self.frame.columnconfigure(1, weight=1)
        self.frame.columnconfigure(2, weight=6)


        # select an existing candidate.xml file
        # Initialize default text that will be in self.entry
        self.entryVariable = Tkinter.StringVar()
        self.entryVariable.set("Open an XML file with candidate's key")

        # Create an entry with a default text that will be replaced by the path
        # to the XML file once directory selected
        self.entry = Entry(self.frame,
                           width=40,
                           textvariable=self.entryVariable
                          )
        self.entry.focus_set()
        self.entry.selection_range(0, Tkinter.END)

        # Create an open button to use to select an XML file with candidate's
        # key info
        self.buttonOpen = Button(self.frame,
                                 text=u"Open an existing file",
                                 command=self.openfilename
                                )

        self.buttonCreate = Button(self.frame,
                                   text=u"Create a new file",
                                   command=self.createfilename
                                  )

        self.buttonCreate.grid(row=0,
                               column=0,
                               padx=(0, 15),
                               pady=10,
                               sticky=E + W
                              )
        self.buttonOpen.grid(row=0,
                             column=1,
                             padx=(0, 15),
                             pady=10,
                             sticky=E + W
                            )
        self.entry.grid(row=0, column=2, padx=15, pady=10, sticky=E + W)

        self.InitUI()



    def InitUI(self):
        
        self.frame = Frame(self.parent)
        self.frame.pack(expand=1, fill='both')

        for i in range(0, 3):
            self.frame.columnconfigure(i, weight=6)
        self.frame.columnconfigure(3, weight=1)
        
        for i in range(3, 4):
            self.frame.rowconfigure(i, weight=1)

        self.labelID   = Label(self.frame, text=u'Identifier')
        self.labelName = Label(self.frame, text=u'Real Name')
        self.labelDoB  = Label(self.frame, text=u'Date of Birth (YYYY-MM-DD)')

        self.buttonAdd    = Button(self.frame, width=12,
                                   text=u'Add candidate',
                                   command=self.AddIdentifierEvent
                                  )
        self.buttonClear  = Button(self.frame, width=12,
                                   text=u'Clear fields',
                                   command=self.clear
                                  )
        self.buttonSearch = Button(self.frame, width=12,
                                   text=u'Search candidate',
                                   command=self.search
                                  )
        self.buttonEdit   = Button(self.frame, width=12,
                                   text=u'Edit candidate',
                                   command=self.edit
                                  )

        self.textCandId  = StringVar()
        self.candidateid = Entry(self.frame,
                                 textvariable=self.textCandId,
                                 width=20
                                )
        self.candidateid.focus_set()

        self.textCandName  = StringVar()
        self.candidatename = Entry(self.frame,
                                   textvariable=self.textCandName,
                                   width=20
                                  )

        self.textCandDoB  = StringVar()
        self.candidateDoB = Entry(self.frame,
                                  textvariable=self.textCandDoB,
                                  width=20
                                 )

        self.tableColumns = ("Identifier", "Real Name", "Date of Birth")
        self.datatable    = ttk.Treeview(self.frame,
                                         selectmode='browse',
                                         columns=self.tableColumns,
                                         show="headings")
        for col in self.tableColumns:
            self.datatable.heading(col, text=col.title(), 
                                   command=lambda c=col: sortby(self.datatable, c, 0))

        self.datatable.bind("<<TreeviewSelect>>", self.OnRowClick)
      
        self.ErrorMessage = StringVar()
        self.error = Label(self.frame, textvariable=self.ErrorMessage, fg='red')

        self.labelID.grid(row=0, column=0, padx=(0,4), sticky=E+W)
        self.labelName.grid(row=0, column=1, padx=(4,4), sticky=E+W)
        self.labelDoB.grid(row=0, column=2, padx=(4,4), sticky=E+W)

        self.candidateid.grid(row=1, column=0, padx=(0,4), pady=(0,10), sticky=E+W)
        self.candidatename.grid(row=1, column=1, padx=(4,4), pady=(0,10), sticky=E+W)
        self.candidateDoB.grid(row=1, column=2, padx=(4,4), pady=(0,10), sticky=E+W)
 
        self.buttonAdd.grid(row=2, column=1, padx=(4,0), sticky=E+W)
        self.buttonClear.grid(row=1, column=3, padx=(4,0), sticky=E+W)
        self.buttonSearch.grid(row=2, column=0, padx=(4,0), sticky=E+W)
        self.buttonEdit.grid(row=2, column=2, padx=(4,0), sticky=E+W)

        self.datatable.grid(row=3, column=0, columnspan=3, pady=10, sticky='nsew')
        self.error.grid(row=3, column=3)


    def LoadXML(self, file):
        global xmlitemlist
        global xmldoc

        # empty the datatable and data dictionary before loading new file
        self.datatable.delete(*self.datatable.get_children())
        self.IDMap = {}

        """Parses the XML file and loads the data into the current window"""
        try:
            xmldoc   = minidom.parse(file)
            xmlitemlist = xmldoc.getElementsByTagName('Candidate')
            for s in xmlitemlist:
                identifier = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue
                realname = s.getElementsByTagName("RealName")[0].firstChild.nodeValue
                dob = s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue
                self.AddIdentifierAction(identifier, realname, dob, False)
        except:
            pass


    def SaveMapAction(self):
        
        """Function which performs the action of writing the XML file"""
        f = open(self.filename, "w")
        f.write("<?xml version=\"1.0\"?>\n<data>\n")
        for key in self.IDMap:
            f.write("\t<Candidate>\n")
            f.write("\t\t<Identifier>%s</Identifier>\n" % key)
            f.write("\t\t<RealName>%s</RealName>\n" % self.IDMap[key][1])
            f.write("\t\t<DateOfBirth>%s</DateOfBirth>\n" % self.IDMap[key][2])
            f.write("\t</Candidate>\n")
        f.write("</data>")


    def SaveMapEvent(self, event):
        """Handles any wxPython event which should trigger a save action"""
        self.SaveMapAction()


    def AddIdentifierEvent(self):
        
        name = self.candidatename.get()
        candid = self.candidateid.get()
        dob = self.candidateDoB.get()
        self.AddIdentifierAction(candid, name, dob)


    def AddIdentifierAction(self, candid, realname, dob, save=True):
        """
        Adds the given identifier and real name to the mapping. If
        the "save" parameter is true, this also triggers the saving
        of the XML file. 
        This is set to False on initial load.
        """
        self.ErrorMessage.set("")

        # check that all fields are set
        if not candid or not realname or not dob:
            message = "ERROR:\nAll fields are\nrequired to add\na candidate"
            self.ErrorMessage.set(message)
            return

        # check candid does not already exist
        if candid in self.IDMap:
            message = "ERROR:\nCandidate ID\nalready exists"
            self.ErrorMessage.set(message)
            return

        # check dob is in format YYYY-MM-DD
        try:
            datetime.datetime.strptime(dob,"%Y-%m-%d")
        except ValueError:
            message = "ERROR:\nDate of birth's\nformat should be\n'YYYY-MM-DD'"
            self.ErrorMessage.set(message)
            return

        mapList = [candid, realname, dob]
        self.IDMap[candid] = mapList
                        
        insertedList = [(candid, realname, dob)]
        for item in insertedList:
            self.datatable.insert('', 'end', values=item)
        
        if(save):
            self.SaveMapAction()


    def OnRowClick(self, event):
        
        """Update the text boxes' data on row click"""
        item_id = str(self.datatable.focus())
        item = self.datatable.item(item_id)['values']
        
        self.textCandId.set(item[0])
        self.textCandName.set(item[1])
        self.textCandDoB.set(item[2])


    def clear(self):
        
        self.textCandId.set("")
        self.textCandName.set("")
        self.textCandDoB.set("")
        self.candidateid.focus_set()


    def search(self):
        #  Find a candidate based on its ID if it is set in text box
        if self.textCandId.get():
            (candid, name, dob) = self.FindCandidate("candid",
                                                     self.textCandId.get()
                                                    )
        # or based on its name if it is set in text box
        elif self.textCandName.get():
            (candid, name, dob) = self.FindCandidate("name",
                                                     self.textCandName.get()
                                                    )
        # print the values in the text box
        self.textCandId.set(candid)
        self.textCandName.set(name)
        self.textCandDoB.set(dob)


    def FindCandidate(self, key, value):
        global xmlitemlist

        # Loop through the candidate tree and return the candid, name and dob
        # that matches a given value
        for s in xmlitemlist:
            candid = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue
            name = s.getElementsByTagName("RealName")[0].firstChild.nodeValue
            dob = s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue
            if (key == "candid" and value == candid):
                return (candid, name, dob)
            elif (key == "name" and value == name):
                return (candid, name, dob)
            elif (key == "dob" and value == dob):
                return (candid, name, dob)
            else:
                continue
        # if candidate was not found, return empty strings
        return ("", "", "")


    def edit(self):
        self.EditIdentifierAction(self.textCandId.get(),
                                  self.textCandName.get(),
                                  self.textCandDoB.get()
                                 )

    def EditIdentifierAction(self, identifier, realname, realdob, edit=True):
        global xmlitemlist
        # Loop through the candidate tree, find a candidate based on its ID
        # and check if name or DoB needs to be updated
        for s in xmlitemlist:
            # initialize update variable
            update = False

            # get the candid, name and dob stored in the XML file
            candid = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue
            name = s.getElementsByTagName("RealName")[0].firstChild.nodeValue
            dob = s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue

            # if name of candidate is changed
            if (candid == identifier) and not (realname == name):
                # update in the XML file
                s.getElementsByTagName("RealName")[0].firstChild.nodeValue = realname
                update = True   # set update to True

            # if candidate's date of birth is changed
            if (candid == identifier) and not (realdob == dob):
                # update in the XML file
                s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue = realdob
                update = True   # set update to True

            if (update):
                # update the XML file
                f = open(self.filename, "w")
                xmldoc.writexml(f)

                # update IDMap dictionary
                mapList = [candid, realname, realdob]
                self.IDMap[candid] = mapList

                # update datatable
                item = self.datatable.selection()
                updatedList = (candid, realname, realdob)
                self.datatable.item(item, values=updatedList)

    def openfilename(self):

        """Returns a selected file name."""
        self.filename = tkFileDialog.askopenfilename(
            filetypes=[("XML files", "*.xml")]
        )
        self.entryVariable.set(self.filename)

        if self.filename:
            # Load the data
            self.LoadXML(self.filename)

        return self.filename

    def createfilename(self):

        self.filename = tkFileDialog.asksaveasfilename(
            defaultextension=[("*.xml")],
            filetypes=[("XML files", "*.xml")]
        )
        self.entryVariable.set(self.filename)

        if self.filename:
            open(self.filename, 'w')

            # Load the data
            self.LoadXML(self.filename)

        return self.filename

def main():
       
    root = Tk()
    app = IDMapper_frame_gui(root)
    root.mainloop()
 

if __name__ == "__main__":
    main()