import collections
import xmltodict

from rdflib import Graph, Literal, BNode, RDF, RDFS, URIRef, Namespace
from rdflib.namespace import FOAF, DC

from vivotool.utils.models.user_model import User
from vivotool.utils.models.publication_model import Publication, Authorship

VIVO = Namespace('http://vivoweb.org/ontology/core#')

class RelationshipTranslator(object):

    def __add_bindings(self, graph):
        graph.bind('vivo', VIVO)

    def get_user_record(self, doc):
        user_doc = None
        for related in doc['entry']['api:relationship']['api:related']:
            if related['@category'] == 'user':
                user_doc = related
        return user_doc

    def get_user_email(self, user_doc):
        user_email = None
        records = []
        user_identifier_association = user_doc['api:object'][
            'api:user-identifier-associations']['api:user-identifier-association']

        if isinstance(user_identifier_association, collections.OrderedDict):
            records.append(user_identifier_association)
        else:
            records = user_identifier_association

        email_assoc = None
        for assoc in records:
            if assoc['@scheme'] == 'email-address':
                email_assoc = assoc
        if email_assoc is not None:
            user_email = email_assoc['#text']
        return user_email

    def get_user_id(self, user_doc):
        return user_doc['api:object']['@id']

    def get_username(self, user_doc):
        return user_doc['api:object']['@username']

    def get_publication_record(self, doc):
        pub_doc = None
        for related in doc['entry']['api:relationship']['api:related']:
            if related['@category'] == 'publication':
                pub_doc = related
        return pub_doc

    def get_publication_id(self, pub_doc):
        return pub_doc['api:object']['@id']

    def get_authorship_id(self, doc):
        return doc['entry']['api:relationship']['@id']

    def parse(self, input_file, target_dir=""):
        with open(input_file) as fd:
            doc = xmltodict.parse(fd.read())
            feed = doc['feed']

        vivo_user = User()
        user_record = self.get_user_record(feed)
        if user_record is not None:
            vivo_user.user_id = self.get_user_id(user_record)
            vivo_user.username = self.get_username(user_record)
            vivo_user.email = self.get_user_email(user_record)

        vivo_publication = Publication()
        publication_record = self.get_publication_record(feed)

        if publication_record and user_record:
            vivo_publication.id = self.get_publication_id(publication_record)

            vivo_authorship = Authorship(vivo_user, vivo_publication)
            vivo_authorship.id = self.get_authorship_id(feed)

            g = Graph()
            self.__add_bindings(g)
            vivo_authorship.add_to_graph(g)
            g.serialize(target_dir + vivo_authorship.id + ".rdf", format='nt')


# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (c) 2014 KIDDYS HOUSE SAC. (http://kiddyshouse.com).
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from osv import osv, fields
from tools.translate import _
from datetime import datetime
import time
import openerp.addons.decimal_precision as dp

from itertools import groupby
from operator import itemgetter

import logging
_logger = logging.getLogger(__name__)

class product_product(osv.osv):
    _inherit = 'product.product'

    def get_product_available(self, cr, uid, ids, context=None):
        """ Finds whether product is available or not in particular warehouse.
        @return: Dictionary of values
        """
        if context is None:
            context = {}

        location_obj = self.pool.get('stock.location')
        warehouse_obj = self.pool.get('stock.warehouse')
        shop_obj = self.pool.get('sale.shop')

        states = context.get('states',[])
        what = context.get('what',())
        if not ids:
            ids = self.search(cr, uid, [])
        res = {}.fromkeys(ids, 0.0)
        if not ids:
            return res

        if context.get('shop', False):
            warehouse_id = shop_obj.read(cr, uid, int(context['shop']), ['warehouse_id'])['warehouse_id'][0]
            if warehouse_id:
                context['warehouse'] = warehouse_id

        if context.get('warehouse', False):
            lot_id = warehouse_obj.read(cr, uid, int(context['warehouse']), ['lot_stock_id'])['lot_stock_id'][0]
            if lot_id:
                context['location'] = lot_id

        if context.get('location', False):
            if type(context['location']) == type(1):
                location_ids = [context['location']]
            elif type(context['location']) in (type(''), type(u'')):
                location_ids = location_obj.search(cr, uid, [('name','ilike',context['location'])], context=context)
            else:
                location_ids = context['location']
            #_logger.error("FIELDS NAMESSSSSSSSS 2: %r", where)
        else:
            location_ids = []
            #wids = warehouse_obj.search(cr, uid, [], context=context)
            #if not wids:
            #    return res
            #for w in warehouse_obj.browse(cr, uid, wids, context=context):
            #    location_ids.append(w.lot_stock_id.id)
            lids = location_obj.search(cr, uid, [], context=context)
            for l in location_obj.browse(cr, uid, lids, context=context):
                if l.usage=='internal' and l.chained_location_type=='none':
                    location_ids.append(l.id)

        # build the list of ids of children of the location given by id
        if context.get('compute_child',True):
            child_location_ids = location_obj.search(cr, uid, [('location_id', 'child_of', location_ids)])
            location_ids = child_location_ids or location_ids

        # this will be a dictionary of the product UoM by product id
        product2uom = {}
        uom_ids = []
        for product in self.read(cr, uid, ids, ['uom_id'], context=context):
            product2uom[product['id']] = product['uom_id'][0]
            uom_ids.append(product['uom_id'][0])
        # this will be a dictionary of the UoM resources we need for conversion purposes, by UoM id
        uoms_o = {}
        for uom in self.pool.get('product.uom').browse(cr, uid, uom_ids, context=context):
            uoms_o[uom.id] = uom

        results = []
        results2 = []

        from_date = context.get('from_date',False)
        to_date = context.get('to_date',False)
        date_str = False
        date_values = False
        where = [tuple(location_ids),tuple(location_ids),tuple(ids),tuple(states)]
        #_logger.error("FIELDS NAMESSSSSSSSS 2: %r", where)
        if from_date and to_date:
            date_str = "date>=%s and date<=%s"
            where.append(tuple([from_date]))
            where.append(tuple([to_date]))
        elif from_date:
            date_str = "date>=%s"
            date_values = [from_date]
        elif to_date:
            date_str = "date<=%s"
            date_values = [to_date]
        if date_values:
            where.append(tuple(date_values))

        prodlot_id = context.get('prodlot_id', False)
        prodlot_clause = ''
        if prodlot_id:
            prodlot_clause = ' and prodlot_id = %s '
            where += [prodlot_id]

        # TODO: perhaps merge in one query.
        if 'in' in what:
            # all moves from a location out of the set to a location in the set
            cr.execute(
                'select sum(product_qty), product_id, product_uom '\
                'from stock_move '\
                'where location_id NOT IN %s '\
                'and location_dest_id IN %s '\
                'and product_id IN %s '\
                'and state IN %s ' + (date_str and 'and '+date_str+' ' or '') +' '\
                + prodlot_clause +
                'group by product_id,product_uom',tuple(where))
            results = cr.fetchall()
        if 'out' in what:
            # all moves from a location in the set to a location out of the set
            cr.execute(
                'select sum(product_qty), product_id, product_uom '\
                'from stock_move '\
                'where location_id IN %s '\
                'and location_dest_id NOT IN %s '\
                'and product_id  IN %s '\
                'and state in %s ' + (date_str and 'and '+date_str+' ' or '') + ' '\
                + prodlot_clause +
                'group by product_id,product_uom',tuple(where))
            results2 = cr.fetchall()

        # Get the missing UoM resources
        uom_obj = self.pool.get('product.uom')
        uoms = map(lambda x: x[2], results) + map(lambda x: x[2], results2)
        if context.get('uom', False):
            uoms += [context['uom']]
        uoms = filter(lambda x: x not in uoms_o.keys(), uoms)
        if uoms:
            uoms = uom_obj.browse(cr, uid, list(set(uoms)), context=context)
            for o in uoms:
                uoms_o[o.id] = o

        #TOCHECK: before change uom of product, stock move line are in old uom.
        context.update({'raise-exception': False})
        # Count the incoming quantities
        for amount, prod_id, prod_uom in results:
            amount = uom_obj._compute_qty_obj(cr, uid, uoms_o[prod_uom], amount,
                     uoms_o[context.get('uom', False) or product2uom[prod_id]], context=context)
            res[prod_id] += amount
        # Count the outgoing quantities
        for amount, prod_id, prod_uom in results2:
            amount = uom_obj._compute_qty_obj(cr, uid, uoms_o[prod_uom], amount,
                    uoms_o[context.get('uom', False) or product2uom[prod_id]], context=context)
            res[prod_id] -= amount

        return res

    def _get_product_reservados_func():
        def _product_available(self, cr, uid, ids, name, arg, context=None):
            c = context.copy()
            res = {}
            c.update({ 'states': ('assigned',), 'what': ('out'),})
            #c.update({ 'states': ('assigned',), 'what': ('out'),'location':14 })
            stock = self.get_product_available(cr, uid, ids, context=c)
            for id in ids:
                res[id] = stock.get(id, 0.0)
            return res
        #_logger.error("RESERVADO 1: %r", _product_available)
        return _product_available

    def _get_product_disponible_func():
        def _product_available(self, cr, uid, ids, name, arg, context=None):
            fisico = context.copy()
            reservado = context.copy()
            fisic = {}
            reser = {}
            result = {}
            fisico.update({ 'states': ('done',), 'what': ('in', 'out') })
            #fisico.update({ 'states': ('done',), 'what': ('in', 'out'), 'location':14 })
            reservado.update({ 'states': ('assigned',), 'what': ('out') })
            #reservado.update({ 'states': ('assigned',), 'what': ('out'), 'location':14 })
            stock_fisico = self.get_product_available(cr, uid, ids, context=fisico)
            stock_reservado= self.get_product_available(cr, uid, ids, context=reservado)

            for id in ids:
                fisic[id] = stock_fisico.get(id, 0.0)
                reser[id] = stock_reservado.get(id, 0.0)
                result[id] = fisic[id] + reser[id]
            #_logger.error("CO0NTEXTO 1: %r", uid.location_ids)
            return result
        return _product_available

    _product_reservados = _get_product_reservados_func()
    _product_disponible = _get_product_disponible_func()

    _columns = {
        'qty_reservado': fields.function(_product_reservados, type='float', digits_compute=dp.get_precision('Product Unit of Measure'), string='Stock reservado', help="Stock reservado"),
        'qty_disponible': fields.function(_product_disponible, type='float', digits_compute=dp.get_precision('Product Unit of Measure'), string='Stock disponible', help="Stock disponible"),
    }

class stock_location(osv.osv):
    _name = "stock.location"
    _inherit = "stock.location"

    def _product_value_2d(self, cr, uid, ids, field_names, arg, context=None):
        """Computes stock value (real and virtual) for a product, as well as stock qty (real and virtual).
        @param field_names: Name of field
        @return: Dictionary of values
        """
        prod_id = context and context.get('product_id', False)

        #_logger.error("PRODUCT_ ID S: %r", field_names)

        if not prod_id:

            return dict([(i, {}.fromkeys(field_names, 0.0)) for i in ids])

        product_product_obj = self.pool.get('product.product')

        cr.execute('select distinct product_id, location_id from stock_move where location_id in %s', (tuple(ids), ))
        dict1 = cr.dictfetchall()
        cr.execute('select distinct product_id, location_dest_id as location_id from stock_move where location_dest_id in %s', (tuple(ids), ))
        dict2 = cr.dictfetchall()
        res_products_by_location = sorted(dict1+dict2, key=itemgetter('location_id'))
        products_by_location = dict((k, [v['product_id'] for v in itr]) for k, itr in groupby(res_products_by_location, itemgetter('location_id')))

        result = dict([(i, {}.fromkeys(field_names, 0.0)) for i in ids])
        result.update(dict([(i, {}.fromkeys(field_names, 0.0)) for i in list(set([aaa['location_id'] for aaa in res_products_by_location]))]))

        currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
        currency_obj = self.pool.get('res.currency')
        currency = currency_obj.browse(cr, uid, currency_id, context=context)
        for loc_id, product_ids in products_by_location.items():
            if prod_id:
                product_ids = [prod_id]
            c = (context or {}).copy()
            c['location'] = loc_id
            for prod in product_product_obj.browse(cr, uid, product_ids, context=c):
                for f in field_names:
                    if f == 'qty_disponible':
                        result[loc_id][f] += prod.qty_disponible
        return result

    _columns = {
        'qty_disponible': fields.function(_product_value_2d, type='float', string='Stock disponible', multi="product_qty_disponible"),
        # multi="product_qty_disponible" --> indica que se va a ejecutar desde el modulo "product_qty_disponible"
    }

stock_location()


class node:
    def __init__(self, data, left=None, right=None):
        self.data = data
        self.left = self.right = None

def inorder_traversal(root):
    if not root:
        return

    stack = []
    current = root

    while stack or current:
        if current:
            stack += current,
            current = current.left
        else:
            node = stack.pop()
            print (node.data, end=' ')
            current = node.right
    print()
    return

def preorder_traversal(root):
    if not root:
        return

    stack = [root]
    while stack:
        node = stack.pop()
        print (node.data, end=' ')
        if node.right:
            stack += node.right,
        if node.left:
            stack += node.left,
    print ()
    return

def postorder_traversal(root):
    if not root:
        return

    stack, stack_reverse = [root], []

    while stack:
        node = stack.pop()
        stack_reverse += node,

        if node.right:
            stack += node.right,
        if node.left:
            stack += node.left,

    while stack_reverse:
        node = stack_reverse.pop()
        print (node.data, end=' ')
    print ()
    return

def main():
    root = None
    root = node('-')
    root.left = node('+')
    root.left.left = node('*')
    root.left.left.left = node('4')
    root.left.left.right = node('5')
    root.left.right = node('/')
    root.left.right.left = node('9')
    root.left.right.right = node('3')
    root.right = node('-')
    root.right.left = node('/')
    root.right.left.left = node('12')
    root.right.left.right = node('6')
    root.right.right = node('*')
    root.right.right.left = node('4')
    root.right.right.right = node('6')
    inorder_traversal(root)
    preorder_traversal(root)
    postorder_traversal(root)

main()


#CPSC475 Dr.DePalma Fall 2016 asgn2
#Minimum Edit Distance
#Sebastian Vargas
#id: avargas
#
#To run on linux, go to your terminal then go to the directory in which
# this program is stored. Then type "python asgn2.py" in the command line
#This should execute the program

#This function calculatese the minimum edit distance as well as
#creates and initializes the arrays to form the matrix.
#Also, the backtracing and alignment are called through here
def minEditDistance():
    #gets the users input for target and source
    sourceString = raw_input("Input the source string \n")
    targetString = raw_input("Input the target string \n")

    #Lines of the matrix
    sourceArray = ["#"]
    targetArray = ["#"]

    #the Distance matrix
    nestedArray = []

    #List of the tuples that form the back traced path
    pathArray = []

    backTracedSource = []
    backTracedTarget = []

    #the identifying rows and columns of the distance matrix are formed
    n = len(sourceString)
    m = len(targetString)
    for i in range(0, n):
        sourceArray.append(sourceString[i])
    for i in range(0, m):
        targetArray.append(targetString[i])

    #Distance Matrix is made of lists of lists
    for i in range(0,n+1):
        nestedArray.append([])
        for j in range(0,m+1):
            nestedArray[i].append([])

    #fill the first row and first column with the initial values
    for i in range(0,m+1):
        nestedArray[0][i] = i
    for i in range(0,n+1):
        nestedArray[i][0] = i

    #Recurrence relation
    for i in range(1,n+1):
        for j in range(1,m+1):
            nestedArray[i][j] = min(nestedArray[i-1][j] + delCost(sourceArray[i]),
                                   nestedArray[i-1][j-1] + subCost(sourceArray[i],targetArray[j]),
                                   nestedArray[i][j-1] + insCost(targetArray[j]))

    printArray(nestedArray, sourceArray)
    print("Minimum Edit distance: " + str(nestedArray[n][m]))
    computeAlignment(nestedArray,pathArray,sourceString,targetString)
    backTracePrint(nestedArray,pathArray,sourceString,
                   targetString,sourceArray,targetArray)
    return

#This function computes the letter alignment of the source and target
def computeAlignment(nestedArray,pathArray,sourceString,targetString):

    n = len(sourceString)
    m = len(targetString)

    #loc is a variable that keeps track of the current tuple the path would be on
    loc = [n,m] #starts at the location of the minimum edit distance
    pathArray.append(loc)
    i = n
    j = m

    #pathval simply represents the values in the matrix
    pathVal = nestedArray[n][m]

    #Finds the minimum value in relation to the neighbours of the current path
    while loc != [0,0]:
        pathVal = min(nestedArray[i-1][j],nestedArray[i-1][j-1],nestedArray[i][j-1])
        if (pathVal == nestedArray[i-1][j]):
            loc = [i-1,j]
            i = i - 1
        elif (pathVal == nestedArray[i-1][j-1]):
            loc = [i-1,j-1]
            i = i - 1
            j = j - 1
        elif (pathVal == nestedArray[i][j-1]):
            loc = [i,j-1]
            j = j - 1
        pathArray.append(loc)
        #print("loc: " + str(loc)) #Use to debug
    return

    #prints a distance matrix
def printArray(array, sourceArray):
    print("Distance Matrix: ")

    for i in range(0, len(sourceArray)):
        print(array[i])
    return

#delete cost for M.E.D (Minimum Edit Distance)
def delCost(letter):
    if (letter != ' '):
        return 1
    return 0

#ins cost for M.E.D (Minimum Edit Distance)
def insCost(letter):
    if (letter != ' '):
        return 1
    return 0

#substitute cost for M.E.D (Minimum Edit Distance)
def subCost(srcLetter,tarLetter):
    if (srcLetter == tarLetter):
        return 0
    return 2

#This function traces the path back and finds the tuples the
#Minimum Edit Distance traverses to align the letters
def backTracePrint(nestedArray,pathArray,sourceString,
                   targetString, sourceArray, targetArray):

    #Arrays to hold characters for the source and target
    backTracedSource = []
    backTracedTarget = []

    n = len(sourceString)
    m = len(targetString)

    sourceCounter = n
    targetCounter = m

    print("Back Traced Path: " + str(pathArray))

    #This loop generates the arrays for
    #source and the target to be aligned
    # '*' characters indicate deletion or insertion
    for i in range (0,len(pathArray)-1):
        #if substitution is done, add letters to both strings
        if (pathArray[i][0] > pathArray[i + 1][0] and pathArray[i][1] > pathArray[i + 1][1]):
            backTracedSource.append(sourceArray[sourceCounter])
            backTracedTarget.append(targetArray[targetCounter])
            sourceCounter = sourceCounter - 1
            targetCounter = targetCounter - 1
        #Insertion adds * to the source
        elif (pathArray[i][1] > pathArray[i + 1][1]):
            backTracedSource.append("*")
            backTracedTarget.append(targetArray[targetCounter])
            targetCounter = targetCounter - 1
        #Deletion adds * to the target
        elif (pathArray[i][0] > pathArray[i + 1][0]):
            backTracedTarget.append("*")
            backTracedSource.append(sourceArray[sourceCounter])
            sourceCounter = sourceCounter - 1

    #reverse the arrays because they are done from the last letter to the first
    backTracedSource.reverse()
    backTracedTarget.reverse()
    print("source: " + str(backTracedSource))
    print("target: " + str(backTracedTarget))
    return

def main():
    minEditDistance()
    return

main()


#--------------------------------------------
# If this code does not execute, uncomment
# the two lines below to change your backend.
#
#import matplotlib
#matplotlib.use("TkAgg")
#
#--------------------------------------------

import tkinter as tk
from tkinter import ttk
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import __version__ as pltVersion
from matplotlib.patches import Rectangle
from astropy.io import fits
import warnings
import time
import os
import csv

class Eyecheck(object):

    def __init__(self, specObj, options):

        # *** Store input variables ***

        self.specObj = specObj  # The Spectrum object created in the pyhammer script
        self.options = options  # The list of options input by the user in the pyhammer script

        # *** Define useful information ***

        self.specType  = ['O', 'B', 'A', 'F', 'G', 'K', 'M', 'L']
        self.subType   = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
        self.metalType = ['-2.0', '-1.5', '-1.0', '-0.5', '+0.0', '+0.5', '+1.0']
        self.templateDir = os.path.join(os.path.split(__file__)[0], 'resources', 'templates')

        # *** Read the infile ***

        self.inData = []
        with open(self.options['infile'], 'r') as file:
            for line in file:
                self.inData.append(line.strip().rsplit(' ',1))

        # *** Read the outfile ***

        with open(self.options['outfile'], 'r') as file:
            reader = csv.reader(file)
            self.outData = list(reader)[1:] # Ignore the header line

        # *** Define user's spectrum ***

        self.specIndex = 0      # The index in the inData where we should start from
        # Loop through the outData and see if some classification has occured already
        for i in range(len(self.outData)):
            # If classification by the user has already occured for the
            # current spectrum, then move to the next one.
            if self.outData[self.specIndex][4] != 'nan' or self.outData[self.specIndex][5] != 'nan':
                self.specIndex += 1
            else:
                # Break out if we can get to a spectrum that hasn't been
                # classified by the user yet
                break
        # If the outfile already has eyecheck results, ask if they want
        # to start where they left off
        if self.specIndex != 0:
            # If every outfile spectra already has an eyecheck result
            # they've classified everything and ask them if they want
            # to start over instead.
            if self.specIndex == i+1:
                modal = ModalWindow('Every spectrum in the output file already has\nan eyecheck result. Do you want to start over?')
                if modal.choice == 'yes':
                    self.specIndex = 0
                else:
                    return
            else:
                modal = ModalWindow('The output file already has eyecheck results.\nDo you want to continue where you left off?')
                if modal.choice == 'no' or modal.choice == None:
                    self.specIndex = 0
        # Now use the Spectrum object to read in the user's appropriate starting spectrum
        self.specObj.readFile(self.options['spectraPath']+self.inData[self.specIndex][0],
                              self.inData[self.specIndex][1]) # Ignore returned values

        # *** Define GUI variables ***

        self.root = tk.Tk()             # Create the root GUI window
        # Define String and Int variables to keep track of widget states
        self.spectrumEntry = tk.StringVar(value = os.path.basename(self.inData[self.specIndex][0]))
        self.specState  = tk.IntVar(value = self.specType.index(self.outData[self.specIndex][2][0]))
        self.subState   = tk.IntVar(value = int(self.outData[self.specIndex][2][1]))
        self.metalState = tk.IntVar(value = self.metalType.index(self.outData[self.specIndex][3]))
        self.subButtons = []            # We keep track of these radio buttons so
        self.metalButtons = []          # they can be enabled and disabled if need be
        self.smoothButton = []          # Save the smooth button info also, so it can be updated
        self.smoothStr = tk.StringVar(value = 'Smooth')
        self.lockSmooth = tk.BooleanVar(value = False)

        # *** Define figure variables ***

        plt.ion()                       # Turn on plot interactivity
        plt.style.use('ggplot')         # Makes the plot look nice
        self.full_xlim = None           # +--
        self.full_ylim = None           # | Store these to keep track
        self.zoomed_xlim = None         # | of zoom states on the plot
        self.zoomed_ylim = None         # |
        self.zoomed = False             # +--
        self.updatePlot()               # Create the plot
        # Other variables
        self.pPressNum = 0
        self.pPressTime = 0

        # *** Initialize the GUI

        self.setupGUI()                 # Call the method for setting up the GUI layout
        self.root.mainloop()            # Run the GUI

    ###
    # Setup/Close Methods
    #

    def _exit(self):
        """
        Description:
            This method is called anytime the user wants to quit the program.
            It will be called if the "X" out of the GUI window, if they choose
            quit from the menu, or if they finish classifying and say they're
            done. This function will first write the self.outData variable
            contents to their outfile, then clean up the GUI and matplotlib
            window.
        """
        # Write the outData to the output file
        with open(self.options['outfile'], 'w') as outfile:
            outfile.write('#Filename,Radial Velocity (km/s),Guessed Spectral Type,Guessed Metallicity,User Spectral Type,User Metallicity\n')
            for i, spectra in enumerate(self.outData):
                for j, col in enumerate(spectra):
                    outfile.write(col)
                    if j < 5: outfile.write(',')
                if i < len(self.outData)-1: outfile.write('\n')
        # Close down the GUI and plot window
        self.root.destroy()
        plt.close('all')

    def setupGUI(self):
        """
        Description:
            This handles setting up all the widgets on the main GUI window and
            defining the initial state of the GUI.
        """

        # *** Set root window properties ***

        self.root.title('PyHammer Eyecheck')
        self.root.iconbitmap(os.path.join(os.path.split(__file__)[0],'resources','sun.ico'))
        self.root.resizable(False, False)
        self.root.geometry('+100+100')
        # Set the close protocol to call this class' personal exit function
        self.root.protocol('WM_DELETE_WINDOW', self._exit)

        # *** Define menubar ***

        menubar = tk.Menu() # Create the overall menubar

        # Define the options menu
        optionsMenu = tk.Menu(menubar, tearoff = 1)
        optionsMenu.add_checkbutton(label = 'Lock Smooth State', onvalue = True, offvalue = False, variable = self.lockSmooth)
        optionsMenu.add_separator()
        optionsMenu.add_command(label = 'Quit', command = self._exit)

        # Define the about menu
        aboutMenu = tk.Menu(menubar, tearoff = 0)
        aboutMenu.add_command(label = 'Help', command = self.callback_help)
        aboutMenu.add_command(label = 'About', command = self.callback_about)

        # Put all menus together
        menubar.add_cascade(label = 'Options', menu = optionsMenu)
        menubar.add_cascade(label = 'About', menu = aboutMenu)
        self.root.config(menu = menubar)

        # *** Define labels ***

        for i, name in enumerate(['Spectra', 'Type', 'Subtype', 'Metallicity', 'Change Type', 'Change Metallicity', 'Options']):
            ttk.Label(self.root, text = name).grid(row = i, column = 0, stick = 'e',
                                                   pady = (10*(i==4),10*(i==0)))

        # *** Define entry box ***

        # This defines the entry box and relevant widgets for indicating the spectrum
        ttk.Entry(self.root, textvariable = self.spectrumEntry).grid(row = 0, column = 1, columnspan = 9, pady = (0,10), sticky = 'nesw')
        ttk.Button(self.root, text = 'Go', width = 3, command = self.jumpToSpectrum).grid(row = 0, column = 10, pady = (0,10))

        # *** Define radio buttons ***

        # First the radio buttons for the spectral type
        for ind, spec in enumerate(self.specType):
            temp = ttk.Radiobutton(self.root, text = spec, variable = self.specState, value = ind,
                                   command = lambda: self.callback_specRadioChange(True))
            temp.grid(row = 1, column = ind+1, sticky = 'nesw')

        # Now the sub spectral type radio buttons
        for ind, sub in enumerate(self.subType):
            self.subButtons.append(ttk.Radiobutton(self.root, text = sub, variable = self.subState, value = ind,
                                                   command = lambda: self.callback_subRadioChange(True)))
            self.subButtons[-1].grid(row = 2, column = ind+1, sticky = 'nesw')

        # Finally the radio buttons for the metallicities
        for ind, metal in enumerate(self.metalType):
            self.metalButtons.append(ttk.Radiobutton(self.root, text = metal, variable = self.metalState, value = ind,
                                                     command = lambda: self.callback_metalRadioChange(True)))
            self.metalButtons[-1].grid(row = 3, column = ind+1, sticky = 'nesw')

        # *** Define buttons ***

        # These will be the buttons for interacting with the data (e.g., smooth it, next, back)
        # We must handle the smooth button specially so we can interact with it later.
        ttk.Button(self.root, text = 'Earlier', command = self.callback_earlier).grid(row = 4, column = 1, columnspan = 5, sticky = 'nesw', pady = (10,0))
        ttk.Button(self.root, text = 'Later', command = self.callback_later).grid(row = 4, column = 6, columnspan = 5, sticky = 'nesw', pady = (10,0))
        ttk.Button(self.root, text = 'Lower', command = self.callback_lower).grid(row = 5, column = 1, columnspan = 5, sticky = 'nesw')
        ttk.Button(self.root, text = 'Higher', command = self.callback_higher).grid(row = 5, column = 6, columnspan = 5, sticky = 'nesw')
        ttk.Button(self.root, text = 'Odd', underline = 0, command = self.callback_odd).grid(row = 6, column = 1, columnspan = 2, sticky = 'nesw')
        ttk.Button(self.root, text = 'Bad', underline = 0, command = self.callback_bad).grid(row = 6, column = 3, columnspan = 2, sticky = 'nesw')
        self.smoothButton = ttk.Button(self.root, textvariable = self.smoothStr, underline = 0, command = self.callback_smooth)
        self.smoothButton.grid(row = 6, column = 5, columnspan = 2, sticky = 'nesw')
        ttk.Button(self.root, text = 'Back', underline = 3, command = self.callback_back).grid(row = 6, column = 7, columnspan = 2, sticky = 'nesw')
        ttk.Button(self.root, text = 'Next', command = self.callback_next).grid(row = 6, column = 9, columnspan = 2, sticky = 'nesw')

        # *** Set key bindings ***

        self.root.bind('o', lambda event: self.callback_odd())
        self.root.bind('b', lambda event: self.callback_bad())
        self.root.bind('s', lambda event: self.callback_smooth())
        self.root.bind('<Return>', lambda event: self.callback_next())
        self.root.bind('k', lambda event: self.callback_back())
        self.root.bind('<Left>', lambda event: self.callback_earlier())
        self.root.bind('<Right>', lambda event: self.callback_later())
        self.root.bind('<Down>', lambda event: self.callback_lower())
        self.root.bind('<Up>', lambda event: self.callback_higher())
        self.root.bind('p', lambda event: self.callback_hammer_time())

        # Force the GUI to appear as a top level window, on top of all other windows
        self.root.lift()
        self.root.call('wm', 'attributes', '.', '-topmost', True)
        self.root.after_idle(self.root.call, 'wm', 'attributes', '.', '-topmost', False)

    def updatePlot(self):
        """
        Description:
            This is the method which handles all the plotting on the matplotlib
            window. It will plot the template (if it exists), the user's spectrum
            and do things like control the zoom level on the plot.
        """

        # Before updating the plot, check the current axis limits. If they're
        # set to the full limit values, then the plot wasn't zoomed in on when
        # they moved to a new plot. If the limits are different, they've zoomed
        # in and we should store the current plot limits so we can set them
        # to these limits at the end.
        if self.full_xlim is not None and self.full_ylim is not None:
            if (self.full_xlim == plt.gca().get_xlim() and
                self.full_ylim == plt.gca().get_ylim()):
                self.zoomed = False
            else:
                self.zoomed = True
                self.zoomed_xlim = plt.gca().get_xlim()
                self.zoomed_ylim = plt.gca().get_ylim()

        # *** Define Initial Figure ***

        fig = plt.figure('Pyhammer Spectrum Matching', figsize = (12,6))
        plt.cla()   # Clear the plot
        if plt.get_current_fig_manager().toolbar._active != 'ZOOM':
            # Make it so the zoom button is selected by default
            plt.get_current_fig_manager().toolbar.zoom()

        # *** Plot the template ***

        # Determine which, if any, template file to load
        templateFile = self.getTemplateFile()

        if templateFile is not None:
            # Load in template data
            with warnings.catch_warnings():
                # Ignore a very particular warning from some versions of astropy.io.fits
                # that is a known bug and causes no problems with loading fits data.
                warnings.filterwarnings('ignore', message = 'Could not find appropriate MS Visual C Runtime ')
                hdulist = fits.open(templateFile)
            loglam = hdulist[1].data['loglam'][::10]
            flux = hdulist[1].data['flux'][::10]
            std = hdulist[1].data['std'][::10]

            # Plot template error bars and spectrum line
            plt.fill_between(loglam, flux+std, flux-std, color = 'b', edgecolor = 'None', alpha = 0.1, label = 'Template Error')
            plt.plot(loglam, flux, '-k', label = 'Template')
            templateName = os.path.split(templateFile)[1][:-5].replace('_','\;')
        else:
            # No template exists, plot nothing
            templateName = 'N/A'

        # *** Plot the user's data ***

        if self.smoothStr.get() == 'Smooth':
            plt.plot(self.specObj.loglam, self.specObj.normFlux, '-r', alpha = 0.6, label = 'Your Spectrum')
        else:
            plt.plot(self.specObj.loglam, self.specObj.normSmoothFlux, '-r', alpha = 0.6, label = 'Your Spectrum')
        spectraName = os.path.basename(self.inData[self.specIndex][0])[:-5]

        # *** Set Plot Labels ***

        plt.xlabel(r'$\mathrm{log_{10}(wavelength / \mathring{A})}$', fontsize = 16)
        plt.ylabel(r'$\mathrm{Normalized\;Flux}$', fontsize = 16)
        plt.title(r'$\mathrm{Template:\;' + templateName + '}$\n$\mathrm{Spectrum:\;' + spectraName + '}$', fontsize = 16)

        # *** Set Legend Settings ***

        handles, labels = plt.gca().get_legend_handles_labels()
        # In matplotlib versions befoew 1.5, the fill_between plot command above
        # does not appear in the legend. In those cases, we will fake it out by
        # putting in a fake legend entry to match the fill_between plot.
        if pltVersion < '1.5':
            labels.append('Template Error')
            handles.append(Rectangle((0,0),0,0, color = 'b', ec = 'None', alpha = 0.1))
        labels, handles = zip(*sorted(zip(labels, handles), key=lambda t: t[0]))
        leg = plt.legend(handles, labels, loc = 0)
        leg.get_frame().set_alpha(0)
        # Set legend text colors to match plot line colors
        if templateFile is not None:
            plt.setp(leg.get_texts()[1], color = 'b', alpha = 0.5)  # alpha is higher, to make more readable
            plt.setp(leg.get_texts()[2], color = 'r', alpha = 0.6)
        else:
            plt.setp(leg.get_texts()[0], color = 'r', alpha = 0.6)

        # *** Set Plot Spacing ***

        plt.subplots_adjust(left = 0.075, right = 0.975, top = 0.9, bottom = 0.15)

        # *** Set Plot Limits ***

        plt.xlim([3.5,4.05])                    # Set x axis limits to constant value
        fig.canvas.toolbar.update()             # Clears out view stack
        self.full_xlim = plt.gca().get_xlim()   # Pull out default, current x-axis limit
        self.full_ylim = plt.gca().get_ylim()   # Pull out default, current y-axis limit
        fig.canvas.toolbar.push_current()       # Push the current full zoom level to the view stack
        if self.zoomed:                         # If the previous plot was zoomed in, we should zoom this too
            plt.xlim(self.zoomed_xlim)          # Set to the previous plot's zoom level
            plt.ylim(self.zoomed_ylim)          # Set to the previous plot's zoom level
            fig.canvas.toolbar.push_current()   # Push the current, zoomed level to the view stack so it shows up first

        # *** Draw the Plot ***

        plt.draw()

    ##
    # Menu Item Callbacks
    #

    def callback_help(self):
        mainStr = \
            'Welcome to the main GUI for spectral typing your spectra.\n\n' \
            'Each spectra in your spectra list file will be loaded in ' \
            'sequence and shown on top of the template it was matched ' \
            'to. From here, fine tune the spectral type by comparing ' \
            'your spectrum to the templates and choose "Next" when ' \
            "you've landed on the correct choice. Continue through each " \
            'spectrum until finished.'
        buttonStr = \
            'Upon opening the Eyecheck program, the first spectrum in your ' \
            'list will be loaded and displayed on top of the template determined ' \
            'by the spectral type guesser.\n\n' \
            'Use the "Earlier" and "Later" buttons to change the spectrum ' \
            'templates. Note that not all templates exist for all spectral ' \
            'types. This program specifically disallows choosing K8 and K9 ' \
            'spectral types as well.\n\n' \
            'The "Higher" and "Lower" buttons change the metallicity. Again, ' \
            'not all metallicities exist as templates.\n\n' \
            'The "Odd" button allows you to mark a spectrum as something other ' \
            'than a standard classification, such as a white dwarf or galaxy.\n\n' \
            'The "Bad" button simply marks the spectrum as BAD in the output ' \
            'file, indicating it is not able to be classified.\n\n' \
            'The Smooth/Unsmooth button will allow you to smooth or unsmooth ' \
            'your spectra in the event that it is noisy. This simply applies ' \
            'a boxcar convolution across your spectrum, leaving the edges unsmoothed.\n\n' \
            'You can cycle between your spectra using the "Back" and "Next" buttons. ' \
            'Note that hitting "Next" will save the currently selected state as ' \
            'the classification for that spectra.'
        keyStr = \
            'The following keys are mapped to specific actions.\n\n' \
            '<Left>\tEarlier spectral type button\n' \
            '<Right>\tLater spectral type button\n' \
            '<Up>\tHigher metallicity button\n' \
            '<Down>\tLower metallicity button\n' \
            '<Enter>\tAccept spectral classification\n' \
            '<K>\tMove to previous spectrum\n' \
            '<O>\tMark spectrum as odd\n' \
            '<B>\tMark spectrum as bad\n' \
            '<S>\tSmooth/Unsmooth the spectrum\n' \
            '<P>'
        tipStr = \
            'The following are a set of tips for useful features of the ' \
            'program.\n\n' \
            'Any zoom applied to the plot is held constant between switching ' \
            'templates. This makes it easy to compare templates around specific ' \
            'features or spectral lines. Hit the home button on the plot ' \
            'to return to the original zoom level.\n\n' \
            'The entry field on the GUI will display the currently plotted ' \
            'spectrum. You can choose to enter one of the spectra in your ' \
            'and hit the "Go" button to automatically jump to that spectrum.\n\n' \
            'By default, every new, loaded spectrum will be unsmoothed and ' \
            'the smooth button state reset. You can choose to keep the smooth '\
            'button state between loading spectrum by selecting the menu option ' \
            '"Lock Smooth State".\n\n' \
            'Some keys may need to be hit rapidly.'
        contactStr = \
            'Aurora Kesseli\n' \
            'aurorak@bu.edu'
        InfoWindow(('Main',    mainStr),
                   ('Buttons', buttonStr),
                   ('Keys',    keyStr),
                   ('Tips',    tipStr),
                   ('Contact', contactStr), parent = self.root, title = 'PyHammer Help')

    def callback_about(self):
        aboutStr = \
            'This project was developed by a select group of graduate students ' \
            'at the Department of Astronomy at Boston University. The project ' \
            'was lead by Aurora Kesseli with development help and advice provided ' \
            'by Mark Veyette, Dylan Morgan, Andrew West, Brandon Harrison, and ' \
            'Dan Feldman. Contributions were further provided by Chris Theissan.\n\n' \
            'See the acompanying paper Kesseli et al. (2016) for further details.'
        InfoWindow(aboutStr, parent = self.root, title = 'PyHammer About')

    ##
    # Button and Key Press Callbacks
    #

    def callback_odd(self):
        # Open an OddWindow object and wait for a user response
        choice = OddWindow(self.root, ['Wd', 'Wdm', 'Carbon', 'Gal', 'Unknown'])
        self.root.wait_window(choice.oddWindow)
        if choice.name is not None:
            # Store the user's response in the outData
            self.outData[self.specIndex][4] = choice.name
            self.outData[self.specIndex][5] = 'nan'
            # Move to the next spectra
            self.moveToNextSpectrum()

    def callback_bad(self):
        # Store BAD as the user's choices
        self.outData[self.specIndex][4] = 'BAD'
        self.outData[self.specIndex][5] = 'BAD'
        # Move to the next spectra
        self.moveToNextSpectrum()

    def callback_smooth(self):
        # Toggle the button text
        self.smoothButton.config(underline = (0 if self.smoothStr.get() == 'Unsmooth' else 2))
        self.smoothStr.set('Smooth' if self.smoothStr.get() == 'Unsmooth' else 'Unsmooth')
        # Update the plot now
        self.updatePlot()

    def callback_next(self):
        # Store the choice for the current spectra
        self.outData[self.specIndex][4] = self.specType[self.specState.get()] + str(self.subState.get())
        self.outData[self.specIndex][5] = self.metalType[self.metalState.get()]
        # Move to the next spectra
        self.moveToNextSpectrum()

    def callback_back(self):
        self.moveToPreviousSpectrum()

    def callback_earlier(self):
        curSub  = self.subState.get()
        curSpec = self.specState.get()
        # If user hasn't selected "O" spectral type and they're
        # currently selected zero sub type, we need to loop around
        # to the previous spectral type
        if curSpec != 0 and curSub == 0:
            # Set the sub spectral type, skipping over K8 and K9
            # since they don't exist.
            self.subState.set(7 if curSpec == 6 else 9)
            # Decrease the spectral type
            self.specState.set(curSpec - 1)
        else:
            # Just decrease sub spectral type
            self.subState.set(curSub - 1)

        # Call the spectral and sub spectral type radio
        # button change callbacks as if the user changed
        # the buttons themselves
        self.callback_specRadioChange(True)
        self.callback_subRadioChange(False)

    def callback_later(self):
        curSub  = self.subState.get()
        curSpec = self.specState.get()
        # If the user hasn't selected "L" spectral type and
        # they're currently selecting "9" spectral sub type
        # (or 7 if spec type is "K"), we need to loop around
        # to the next spectral type
        if curSpec != 7 and (curSub == 9 or (curSpec == 5 and curSub == 7)):
            self.specState.set(curSpec + 1)
            self.subState.set(0)
        else:
            # Just increase the sub spectral type
            self.subState.set(curSub + 1)

        # Call the spectral and sub spectral type radio
        # button change callbacks as if the user changed
        # the buttons themselves.
        self.callback_specRadioChange(True)
        self.callback_subRadioChange(False)

    def callback_higher(self):
        curMetal = self.metalState.get()
        # If the user isn't at the max metallicity option,
        # then increase the metallicity
        if curMetal != 6:
            self.metalState.set(curMetal + 1)
        # Call the metallicity radio button change callback
        # as if the user changed the button themselves.
        self.callback_metalRadioChange(True)

    def callback_lower(self):
        curMetal = self.metalState.get()
        # If the user isn't at the min mellaticity option,
        # then decrease the metallicity
        if curMetal != 0:
            self.metalState.set(curMetal - 1)
        # Call the metallicity radio button change callback
        # as if the user changed the button themselves.
        self.callback_metalRadioChange(True)

    def callback_hammer_time(self):
        timeCalled = time.time()
        if self.pPressTime == 0 or timeCalled - self.pPressTime > 1.5:
            # Reset
            self.pPressTime = timeCalled
            self.pPressNum = 1
            return
        else:
            self.pPressNum += 1

        if self.pPressNum == 5:
            chrList = [(10,1),(32,18),(46,1),(39,1),(47,1),(32,26),(10,1),(32,1),(42,1),(32,3),(39,1),(42,1),(32,10),(47,1),(32,1),(40,1),(95,11),
                       (46,1),(45,12),(46,1),(32,2),(10,1),(32,9),(42,1),(32,7),(91,1),(32,1),(93,1),(95,11),(124,1),(47,2),(80,1),(121,1),(72,1),
                       (97,1),(109,2),(101,1),(114,1),(47,2),(124,1),(32,2),(10,1),(32,14),(42,1),(32,2),(41,1),(32,1),(40,1),(32,11),(39,1),(45,12),
                       (39,1),(32,2),(10,1),(32,17),(39,1),(45,1),(39,1),(32,1),(42,1),(32,25),(10,1),(32,13),(42,1),(32,33),(10,1),(32,19),(42,1),(32,27)]
            InfoWindow(''.join([chr(c[0])*c[1] for c in chrList]), parent = self.root, height = 9, font = 'Courier')

    ##
    # Radiobutton Selection Change Callbacks
    #

    def callback_specRadioChange(self, callUpdatePlot):
        # If the spectral type radio button has changed,
        # check to see if the user switched to a "K" type.
        # If they have, turn off the option to pick K8 and K9
        # Since those don't exist. Otherwise, just turn those
        # sub spectral type buttons on.
        if self.specState.get() == 5:
            self.subButtons[-1].configure(state = 'disabled')
            self.subButtons[-2].configure(state = 'disabled')
            if self.subState.get() == 8 or self.subState.get() == 9:
                self.subState.set(7)
        else:
            self.subButtons[-1].configure(state = 'normal')
            self.subButtons[-2].configure(state = 'normal')

        if callUpdatePlot: self.updatePlot()

    def callback_subRadioChange(self, callUpdatePlot):
        if callUpdatePlot: self.updatePlot()

    def callback_metalRadioChange(self, callUpdatePlot):
        if callUpdatePlot: self.updatePlot()

    ##
    # Utility Methods
    #

    def moveToNextSpectrum(self):
        """
        Description:
            This method handles moving to the next spectrum. All it really
            does is determines if the user is at the end of the list of
            spectrum, and, if so, asks if they're done. If they aren't at
            the end, it moves to the next spectrum (by incrementing self.specIndex)
            and calling self.getUserSpectrum.
        """
        if self.specIndex+1 >= len(self.inData):
            self.root.bell()
            modal = ModalWindow("You've classified all the spectra. Are you finished?", parent = self.root)
            self.root.wait_window(modal.modalWindow)
            if modal.choice == 'yes':
                self._exit()
        else:
            self.specIndex += 1
            self.getUserSpectrum()

    def moveToPreviousSpectrum(self):
        """
        Description:
            This method handles moving to the previous spectrum. It will simply
            decrement the self.specIndex variable if they're not already on
            the first index and call self.getUserSpectrum.
        """
        if self.specIndex > 0:
            self.specIndex -= 1
            self.getUserSpectrum()

    def jumpToSpectrum(self):
        """
        Description:
            This method handles moving to a different spectrum in the list which
            may or may not be before or after the current spectrum. This is handled
            by looking for a spectrum in the inData list which matches the spectrum
            in the Entry field in the GUI. If a match is found, that spectrum is
            loaded, otherwise they're informed that spectrum could not be found.
        """
        spectrumFound = False
        for i, spectrum in enumerate(self.inData):
            if os.path.basename(self.spectrumEntry.get()) == os.path.basename(spectrum[0]):
                self.specIndex = i
                spectrumFound = True
                break
        if not spectrumFound:
            message = 'The spectrum you input could not be matched' \
                      'to one of the spectrum in your list. Check' \
                      'your input and try again.'
            InfoWindow(message, parent = self.root, title = 'PyHammer Error')
        else:
            self.getUserSpectrum()

    def getUserSpectrum(self):
        """
        Description:
            This handles loading a new spectrum based on the self.specIndex variable
            and updates the GUI and plot window accordingly.
        """
        # Read in the next spectrum file indicated by self.specIndex
        self.specObj.readFile(self.options['spectraPath']+self.inData[self.specIndex][0],
                              self.inData[self.specIndex][1]) # Ignore returned values from readFile
        # Set the spectrum entry field to the new spectrum name
        self.spectrumEntry.set(os.path.basename(self.inData[self.specIndex][0]))
        # Set the radio button selections to the new spectrum's guessed classifcation
        self.specState.set(self.specType.index(self.outData[self.specIndex][2][0]))
        self.subState.set(int(self.outData[self.specIndex][2][1]))
        self.metalState.set(self.metalType.index(self.outData[self.specIndex][3]))
        # Reset the indicator for whether the plot is zoomed. It should only stay zoomed
        # between loading templates, not between switching spectra.
        self.full_xlim = None
        self.full_ylim = None
        self.zoomed = False
        # Reset the smooth state to be unsmoothed, unless the user chose to lock the state
        if not self.lockSmooth.get():
            self.smoothButton.config(underline = 0)
            self.smoothStr.set('Smooth')
        # Update the plot
        self.updatePlot()

    def getTemplateFile(self, specState = None, subState = None, metalState = None):
        """
        Description:
            This will determine the filename for the template which matches the
            current template selection. Either that selection will come from
            whichever radio buttons are selected, or else from input to this
            function. This will search for filenames matching a specific format.
            The first attempt will be to look for a filename of the format
            "XX.fits". The next format it will try (if the first doesn't exist)
            is "XX_+X.X.fits". After that it will try "XX_+X.X_Dwarf.fits".
        """
        # If values weren't passed in for certain states, assume we should
        # use what is chosen on the GUI
        if specState is None: specState = self.specState.get()
        if subState is None: subState = self.subState.get()
        if metalState is None: metalState = self.metalState.get()

        # Try to use just the spectral type and subtype for the name first
        filename = self.specType[specState] + str(subState)

        fullPath = os.path.join(self.templateDir, filename + '.fits')

        if os.path.isfile(fullPath):
            return fullPath

        # Try adding the current metallicity choice
        filename += '_' + self.metalType[metalState]

        fullPath = os.path.join(self.templateDir, filename + '.fits')

        if os.path.isfile(fullPath):
            return fullPath

        # Try adding the word Dwarf to the name
        filename += '_Dwarf'

        fullPath = os.path.join(self.templateDir, filename + '.fits')

        if os.path.isfile(fullPath):
            return fullPath

        # Return None if file could not be found
        return None

class InfoWindow(object):
    """
    Description:
        This brings up a new window derived from root
        that displays info ext and has a button to close
        the window when user is done. This optionally can
        display the multiple sets of text in multiple tabs
        so as to not have a huge, long window and to keep
        the information more organized.

    Input:
        args: This will be a set of arguments supplying what should be
            put into the info window. If the user wants a basic window
            with simple text, then just supply a string with that text.
            However, the user can also specify multiple arguments where
            each argument will be its own tab in a notebook on the window.
            Each argument in this case should be a tuple containing first
            the name that will appear on the tab, and second, the text to
            appear inside the tab.
        parent: The root window to potentially derive this one from. If
            nothing is provided, this will create a new window from scratch.
        title: The title to display on the window
        height: The height of the window, in lines. More height provides a
            taller window.
        font: A new font family to use, if desired.

    Example:
        This brings up a simple GUI with basic text in it. It derives from a
        top level root window named 'root'

        InfoWindow('This is an example\ninfo window.', parent = root, title = 'A GUI title')

        This brings up a GUI with multiple tabs and different
        text in each tab. This is not derived from a root.

        InfoWindow(('Tab 1', 'Text in tab 1'), ('Tab 2', 'Some more text'), title = 'Title')

    """

    def __init__(self, *args, parent = None, title = 'PyHammer', height = 6, font = None):
        # Setup the window
        if parent is None:
            # If no top level was provided, define the window as the top level
            self.infoWindow = tk.Tk()
        else:
            # If a top level was provided, derive a new window from it
            self.infoWindow = tk.Toplevel(parent)
            self.infoWindow.grab_set()  # Make the root window non-interactive
            self.infoWindow.geometry('+%i+%i' % (parent.winfo_rootx(), parent.winfo_rooty()))
        self.infoWindow.title(title)
        self.infoWindow.iconbitmap(os.path.join(os.path.split(__file__)[0],'resources','sun.ico'))
        self.infoWindow.resizable(False, False)

        # Define the window contents
        if len(args) == 1:
            self.defineTab(self.infoWindow, args[0], height, font)
        else:
            notebook = ttk.Notebook(self.infoWindow)
            for a in args:
                tab = tk.Frame(notebook)
                notebook.add(tab, text = a[0])
                self.defineTab(tab, a[1], height, font)
            notebook.pack()
        if parent is None: self.infoWindow.mainloop()

    def defineTab(self, parent, text, height, font):
        # Create the Text widget which displays the text
        content = tk.Text(parent, width = 50, height = height, background = parent.cget('background'),
                          relief = tk.FLAT, wrap = tk.WORD, font = '-size 10')
        if font is not None: content.configure(font = '-family ' + font +' -size 10')
        content.grid(row = 0, column = 0, padx = 2, pady = 2)
        content.insert(tk.END, text)
        # Create the Scrollbar for the Text widget
        scrollbar = ttk.Scrollbar(parent, command = content.yview)
        scrollbar.grid(row = 0, column = 1, sticky = 'ns')
        # Link the Text widget to the Scrollbar
        content.config(state = tk.DISABLED, yscrollcommand = scrollbar)
        # Add the OK button at the bottom for quitting out
        but = ttk.Button(parent, text = 'OK', command = self.infoWindow.destroy)
        but.grid(row = 1, column = 0, columnspan = 2, sticky = 'nsew', padx = 2, pady = 5)
        parent.rowconfigure(1, minsize = 40)

class ModalWindow(object):
    """
    Description:
        This brings up a new window, potentially derived
        from a top level root window that displays a question
        and asks the user to choose yes or no. At the termination
        of this window, one can look at the attribute choice
        to determine which button was pressed by the user. If the
        user X's out without choosing an option, then the choice
        will be equal to None.

    Input:
        text: The question to display to the user.
        parent: The root window to potentially derive this one from. If
            nothing is provided, this will create a new window from scratch.
        title: The title to display on the window. Default is "PyHammer".

    Example:
        To properly use this window, you must create a new object and
        assign it to a variable. This will bring up the window. You
        must then wait for the window to be closed before moving on in
        your code. This can be acheived in a manner similar to the following:

        modal = ModalWindow('Is the answer to this question no?', parent = self.root)
        self.root.wait_window(modal.modalWindow)

        After this, one can inspect modal.choice.

        If you are not deriving this window from a top level window, then
        you do not need the wait_window call and you can simply use:

        modal = ModalWindow('Is the answer to this question no?')
    """

    def __init__(self, text, parent = None, title = 'PyHammer'):
        self.choice = None

        # Setup the window
        if parent is None:
            # If no top level was provided, define the window as the top level
            self.modalWindow = tk.Tk()
        else:
            # If a top level was provided, derive a new window from it
            self.modalWindow = tk.Toplevel(parent)
            self.modalWindow.grab_set() # Make the root window non-interactive
            self.modalWindow.geometry('+%i+%i' % (parent.winfo_rootx(), parent.winfo_rooty()))
        self.modalWindow.title(title)
        self.modalWindow.iconbitmap(os.path.join(os.path.split(__file__)[0],'resources','sun.ico'))
        self.modalWindow.resizable(False, False)

        # Setup the widgets in the window
        label = ttk.Label(self.modalWindow, text = text, font = '-size 10')
        label.grid(row = 0, column = 0, columnspan = 2, padx = 2, pady = 2)

        but = ttk.Button(self.modalWindow, text = 'Yes', command = self.choiceYes)
        but.grid(row = 1, column = 0, sticky = 'nsew', padx = 2, pady = 5)

        but = ttk.Button(self.modalWindow, text = 'No', command = self.choiceNo)
        but.grid(row = 1, column = 1, sticky = 'nsew', padx = 2, pady = 5)

        self.modalWindow.rowconfigure(1, minsize = 40)

        if parent is None: self.modalWindow.mainloop()

    def choiceYes(self):
        self.choice = 'yes'
        self.modalWindow.destroy()

    def choiceNo(self):
        self.choice = 'no'
        self.modalWindow.destroy()

class OddWindow(object):
    """
    Description:
        This brings up a window that allows the user to choose between
        a list of radio button choices, or else enter their own choice
        in an Entry field. Once their choice is made, they hit okay.
        The specific purpose of this window is to allow the user to
        mark their spectra as "odd" in that it doesn't fit into the
        standard classifications and should be recorded as some other choice.
    """

    def __init__(self, parent, choices):
        self.name = None
        self.choices = choices
        self.radioChoice = tk.IntVar(value = 0)
        self.customName = tk.StringVar()
        self.label = tk.StringVar(value = 'Choose Odd Type')

        # Setup the window
        self.oddWindow = tk.Toplevel(parent)
        self.oddWindow.grab_set()   # Make the root window non-interactive
        self.oddWindow.title('')
        self.oddWindow.iconbitmap(os.path.join(os.path.split(__file__)[0],'resources','sun.ico'))
        self.oddWindow.resizable(False, False)
        self.oddWindow.geometry('+%i+%i' % (parent.winfo_rootx(), parent.winfo_rooty()))

        # Setup the widgets in the window
        tk.Label(self.oddWindow, textvariable = self.label, justify = 'center').grid(row = 0, column = 0, columnspan = 2)

        # Create the radio buttons for the input choices
        for i, c in enumerate(choices):
            temp = ttk.Radiobutton(self.oddWindow, text = '', variable = self.radioChoice, value = i)
            temp.grid(row = i+1, column = 0, padx = (10,0), sticky = 'nesw')

            temp = ttk.Label(self.oddWindow, text = c)
            temp.grid(row = i+1, column = 1, sticky = 'w')

        # Create the radio button and entry field for the user's choice
        temp = ttk.Radiobutton(self.oddWindow, text = '', variable = self.radioChoice, value = i+1)
        temp.grid(row = i+2, column = 0, padx = (10,0), sticky = 'nesw')

        temp = ttk.Entry(self.oddWindow, textvariable = self.customName, width = 10)
        temp.grid(row = i+2, column = 1, sticky = 'w')

        # Define the button
        but = ttk.Button(self.oddWindow, text = 'OK', command = self._exit)
        but.grid(row = i+3, column = 0, columnspan = 2, sticky = 'nsew', padx = 2, pady = 5)

        # Configure grid sizes
        self.oddWindow.rowconfigure(i+3, minsize = 40)
        self.oddWindow.columnconfigure(1, minsize = 175)

    def _exit(self):
        """
        Description:
            This is called when the user X's out of the window or
            clicks the OK button in the window. It will check
            what choice they chose and set it as the name then
            destroy the window. It will make sure though, that the
            user has entered text into the Entry field if they
            pick that option.
        """
        if self.radioChoice.get() < len(self.choices):
            self.name = self.choices[self.radioChoice.get()]
        else:
            if self.customName.get() == '':
                # Temporarily change label to inform user of the problem.
                self.label.set('Enter Text for Custom Name')
                self.oddWindow.bell()   # Chime a bell to indicate a problem
                self.oddWindow.after(1500, lambda: self.label.set('Choose Odd Type'))
                return
            else:
                self.name = self.customName.get()
        self.oddWindow.destroy()


#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
# Ozer Ozkahraman (ozero@kth.se)

import math
import rospy
import py_trees as pt
import tf
import numpy as np

import imc_enums
import bb_enums

class C_CheckWaypointType(pt.behaviour.Behaviour):
    """
    Checks if the type of the current WP corresponds to the given
    expected type and returns SUCCESS if they match

    Use the imc_enums.MANEUVER_XXX as the expected wp type
    """
    def __init__(self,
                 expected_wp_type,
                 bb_key = None):
        self.bb = pt.blackboard.Blackboard()
        self.expected_wp_type = expected_wp_type
        self.expected_wp_type_str = C_CheckWaypointType.imc_id_to_str(self.expected_wp_type)

        self.bb_key = bb_key

        super(C_CheckWaypointType, self).__init__(name="C_CheckWaypointType = {}".format(self.expected_wp_type_str))

    @staticmethod
    def imc_id_to_str(imc_id):
        if imc_id == imc_enums.MANEUVER_GOTO:
            return imc_enums.MANEUVER_GOTO_STR
        if imc_id == imc_enums.MANEUVER_SAMPLE:
            return imc_enums.MANEUVER_SAMPLE_STR

        return str(imc_id)

    def update(self):
        self.feedback_message = "Got None"

        if self.bb_key is None:
            mission = self.bb.get(bb_enums.MISSION_PLAN_OBJ)
            if mission is None:
                return pt.Status.FAILURE

            wp = mission.get_current_wp()
        else:
            wp = self.bb.get(self.bb_key)

        if wp is None:
            return pt.Status.FAILURE

        self.feedback_message = "Got:{}".format(C_CheckWaypointType.imc_id_to_str(wp.imc_man_id))
        if wp.imc_man_id != self.expected_wp_type:
            return pt.Status.FAILURE

        return pt.Status.SUCCESS

class C_AtDVLDepth(pt.behaviour.Behaviour):
    """
    Returns SUCCESS if at some specified depth
    """
    def __init__(self, dvl_depth):
        bb = pt.blackboard.Blackboard()
        self.vehicle = bb.get(bb_enums.VEHICLE_STATE)
        self.dvl_depth = dvl_depth
        super(C_AtDVLDepth, self).__init__(name="C_AtDVLDepth")

    def update(self):
        depth = self.vehicle.depth
        if depth is None or depth < self.dvl_depth:
            msg = "Not deep enough for DVL: {}".format(depth)
            rospy.loginfo_throttle(10, msg)
            self.feedback_message = msg
            return pt.Status.FAILURE

        return pt.Status.SUCCESS

class C_NoAbortReceived(pt.behaviour.Behaviour):
    """
    This condition returns FAILURE forever after it returns it once.
    Used as a one-time lock
    """
    def __init__(self):
        self.bb = pt.blackboard.Blackboard()
        self.vehicle = self.bb.get(bb_enums.VEHICLE_STATE)
        self.aborted = False
        super(C_NoAbortReceived, self).__init__(name="C_NoAbortReceived")

    def update(self):
        if self.bb.get(bb_enums.ABORT) or self.aborted or self.vehicle.aborted:
            self.aborted = True
            self.vehicle.abort()
            self.feedback_message = 'ABORTED'
            return pt.Status.FAILURE
        else:
            return pt.Status.SUCCESS

class C_LeakOK(pt.behaviour.Behaviour):
    def __init__(self):
        bb = pt.blackboard.Blackboard()
        self.vehicle = bb.get(bb_enums.VEHICLE_STATE)
        super(C_LeakOK, self).__init__(name="C_LeakOK")

    def update(self):
        if self.vehicle.leak == True:
            self.feedback_message = "\n\n\n!!!! LEAK !!!!\n\n\n"
            return pt.Status.FAILURE
        else:
            return pt.Status.SUCCESS

class C_DepthOK(pt.behaviour.Behaviour):
    def __init__(self):
        self.bb = pt.blackboard.Blackboard()
        self.vehicle = self.bb.get(bb_enums.VEHICLE_STATE)
        self.max_depth = self.bb.get(bb_enums.MAX_DEPTH)
        super(C_DepthOK, self).__init__(name="C_DepthOK")

    def update(self):
        self.max_depth = self.bb.get(bb_enums.MAX_DEPTH)
        depth = self.vehicle.depth

        if depth is None:
            rospy.logwarn_throttle(5, "NO DEPTH READ! Success anyways")
            self.feedback_message = "Last read:None, max:{m:.2f}".format(l=depth, m=self.max_depth)
            return pt.Status.SUCCESS
        else:
            self.feedback_message = "Last read:{l:.2f}, max:{m:.2f}".format(l=depth, m=self.max_depth)

        if depth < self.max_depth:
            return pt.Status.SUCCESS
        else:
            rospy.logwarn_throttle(5, "Too deep!"+str(depth))
            return pt.Status.FAILURE

class C_AltOK(pt.behaviour.Behaviour):
    def __init__(self):
        self.bb = pt.blackboard.Blackboard()
        self.min_alt = self.bb.get(bb_enums.MIN_ALTITUDE)
        self.vehicle = self.bb.get(bb_enums.VEHICLE_STATE)
        super(C_AltOK, self).__init__(name="C_AltOK")

    def update(self):
        self.min_alt = self.bb.get(bb_enums.MIN_ALTITUDE)
        alt = self.vehicle.altitude
        if alt is None:
            rospy.logwarn_throttle(10, "NO ALTITUDE READ! The tree will run anyways")
            self.feedback_message = "Last read:None, min:{m:.2f}".format(m=self.min_alt)
            return pt.Status.SUCCESS
        else:
            self.feedback_message = "Last read:{l:.2f}, min:{m:.2f}".format(l=alt, m=self.min_alt)

        if alt > self.min_alt:
            return pt.Status.SUCCESS
        else:
            rospy.loginfo_throttle(5, "Too close to the bottom! "+str(alt))
            return pt.Status.FAILURE

class C_StartPlanReceived(pt.behaviour.Behaviour):
    def __init__(self):
        """
        return SUCCESS if we the tree received a plan_control message that
        said 'run plan'.
        Currently being set bt A_UpdateNeptusPlanControl.
        FAILURE otherwise.
        """
        self.bb = pt.blackboard.Blackboard()
        super(C_StartPlanReceived, self).__init__(name="C_StartPlanReceived")

    def update(self):
        plan = self.bb.get(bb_enums.MISSION_PLAN_OBJ)
        if plan is None:
            self.feedback_message = "No plan"
            return pt.Status.FAILURE

        self.feedback_message = "Plan is go:{} for plan {}".format(plan.plan_is_go, plan.plan_id)
        if plan.plan_is_go:
            return pt.Status.SUCCESS
        else:
            rospy.loginfo_throttle_identical(5, "Waiting for start plan")
            return pt.Status.FAILURE

class C_PlanCompleted(pt.behaviour.Behaviour):
    def __init__(self):
        """
        If the currently know MissionPlan object in MISSION_PLAN_OBJ
        has no more actions left to do, return SUCCESS

        return FAILURE otherwise
        """
        self.bb = pt.blackboard.Blackboard()
        super(C_PlanCompleted, self).__init__(name="C_PlanCompleted")

    def update(self):
        mission_plan = self.bb.get(bb_enums.MISSION_PLAN_OBJ)
        if mission_plan is None:
            msg = "No plan received yet"
            self.feedback_message = msg
            rospy.loginfo_throttle(5, msg)
            return pt.Status.FAILURE
        elif not mission_plan.is_complete():
            msg = "Progress:{}/{} on plan {}".format(mission_plan.current_wp_index, len(mission_plan.waypoints), mission_plan.plan_id)
            self.feedback_message = msg
            rospy.loginfo_throttle_identical(5, msg)
            return pt.Status.FAILURE

        rospy.loginfo_throttle_identical(2, "Plan is complete!")
        self.feedback_message = "Current plan:{}".format(mission_plan.plan_id)
        return pt.Status.SUCCESS

class C_HaveCoarseMission(pt.behaviour.Behaviour):
    def __init__(self):
        self.bb = pt.blackboard.Blackboard()
        super(C_HaveCoarseMission, self).__init__(name="C_HaveCoarseMission")

    def update(self):
        mission_plan = self.bb.get(bb_enums.MISSION_PLAN_OBJ)
        if mission_plan is None or mission_plan.waypoints is None or len(mission_plan.waypoints) <= 0:
            return pt.Status.FAILURE

        self.feedback_message = "Current plan:{}".format(mission_plan.plan_id)
        return pt.Status.SUCCESS

class C_PlanIsNotChanged(pt.behaviour.Behaviour):
    """
    Use this condition to stop a running action when a plan with a different
    plan_id or bigger time stamp is seen in the tree.
    """
    def __init__(self):
        super(C_PlanIsNotChanged, self).__init__(name="C_PlanIsNotChanged")
        self.bb = pt.blackboard.Blackboard()
        self.last_known_id = None
        self.last_known_time = 0

    def update(self):
        current_plan = self.bb.get(bb_enums.MISSION_PLAN_OBJ)
        if current_plan is None:
            # there is no plan, it can not change
            self.last_known_id = None
            self.last_known_time = 0
            self.feedback_message = "There was no plan, plan is not changed"
            rospy.loginfo_throttle_identical(10, self.feedback_message)
            return pt.Status.SUCCESS

        if self.last_known_id is None:
            # this is the first plan we saw
            # record it, and let the tree tick again
            self.last_known_id = current_plan.plan_id
            self.last_known_time = current_plan.creation_time
            self.feedback_message = "First time seeing any plan, plan is changed"
            rospy.loginfo_throttle_identical(10, self.feedback_message)
            return pt.Status.FAILURE

        if self.last_known_id == current_plan.plan_id and self.last_known_time < current_plan.creation_time:
            # this is the same plan, but it was sent again, this means a restart
            # so the plan IS changed
            self.feedback_message = "Same plan_id, but the current plan is newer, plan is changed"
            rospy.loginfo_throttle_identical(10, self.feedback_message)
            self.last_known_id = current_plan.plan_id
            self.last_known_time = current_plan.creation_time
            return pt.Status.FAILURE

        if self.last_known_id != current_plan.plan_id:
            # the plan has been changed completely
            self.feedback_message = "A new plan is received!"
            rospy.loginfo_throttle_identical(10, self.feedback_message)
            self.last_known_id = current_plan.plan_id
            self.last_known_time = current_plan.creation_time
            current_plan.plan_is_go = False
            return pt.Status.FAILURE

        self.feedback_message = "last_id:{}, current_id:{}".format(self.last_known_id, current_plan.plan_id)
        rospy.loginfo_throttle_identical(10, self.feedback_message)
        return pt.Status.SUCCESS

class C_NoNewPOIDetected(pt.behaviour.Behaviour):
    """
    returns SUCCESS until there is a POI detected that is sufficiently further
    away than the last known one. or if its the first one.
    This distance is governed by new_pos_distance
    """
    def __init__(self, new_poi_distance):
        super(C_NoNewPOIDetected, self).__init__(name="C_NoNewPOIDetected")
        self.bb = pt.blackboard.Blackboard()
        self.new_poi_distance = new_poi_distance
        self._last_known_poi = None

    def update(self):
        poi = self.bb.get(bb_enums.POI_POINT_STAMPED)
        if poi is None:
            rospy.loginfo_throttle_identical(10,"No POI :(")
            return pt.Status.SUCCESS

        if self._last_known_poi is None:
            # a poi exists but we didnt know any beforehand, new poi!
            self._last_known_poi = poi
            rospy.logwarn_throttle_identical(10,"Our first POI!")
            return pt.Status.FAILURE

        # we knew a poi, there is a poi we see, far enough?
        xdiff = poi.point.x-self._last_known_poi.point.x
        ydiff = poi.point.y-self._last_known_poi.point.y
        zdiff = poi.point.z-self._last_known_poi.point.z
        dist = math.sqrt( xdiff**2 + ydiff**2 + zdiff**2 )

        # its far enough!
        if dist > self.new_poi_distance:
            self._last_known_poi = poi
            rospy.logwarn_throttle_identical(10,"A new POI that is far enough!"+str(dist))
            return pt.Status.FAILURE

        # aww, not far enough
        self._last_known_poi = poi
        rospy.loginfo_throttle_identical(10,"Probably the same POI as before...")
        return pt.Status.SUCCESS

class C_AutonomyDisabled(pt.behaviour.Behaviour):
    def __init__(self):
        super(C_AutonomyDisabled, self).__init__(name="C_AutonomyDisabled")
        self.bb = pt.blackboard.Blackboard()

    def update(self):
        enabled = self.bb.get(bb_enums.ENABLE_AUTONOMY)
        if enabled:
            return pt.Status.FAILURE

        return pt.Status.SUCCESS

class C_LeaderFollowerEnabled(pt.behaviour.Behaviour):
    def __init__(self, enable_leader_follower):
        super(C_LeaderFollowerEnabled, self).__init__(name="C_LeaderFollowerEnabled")
        self.bb = pt.blackboard.Blackboard()
        self.enable_leader_follower = enable_leader_follower

    def update(self):
        if self.enable_leader_follower:
            return pt.Status.SUCCESS

        return pt.Status.FAILURE

class C_LeaderExists(pt.behaviour.Behaviour):
    def __init__(self, base_link, leader_link):
        self.leader_link = leader_link
        self.base_link = base_link
        # strings might be ever so slightly different...
        if leader_link in base_link or base_link in leader_link:
            self.leader_is_self = True
        else:
            self.leader_is_self = False

        # assume not by default
        self.leader_exists = False

        self.bb = pt.blackboard.Blackboard()
        self.listener = tf.TransformListener()

        super(C_LeaderExists, self).__init__(name="C_LeaderExists")

    def setup(self, timeout):
        if self.leader_is_self:
            rospy.logwarn_throttle(3, "I am the leader!")
            return True

        try:
            rospy.loginfo_throttle(3, "Waiting for transform from {} to {}...".format(self.base_link, self.leader_link))
            self.listener.waitForTransform(self.base_link, self.leader_link, rospy.Time(), rospy.Duration(timeout))
            self.leader_exists = True
            rospy.loginfo_throttle(3, "...Got it, we got a leader to follow!")
        except:
            rospy.logwarn_throttle(5, "Could not find xform from {} to {}, assuming there is no leader!".format(self.base_link,self.leader_link))

        return True

    def update(self):
        if self.leader_is_self:
            rospy.logwarn_throttle(30, "I am the leader!")
            return pt.Status.FAILURE

        if not self.leader_exists:
            rospy.logwarn_throttle(60, "No leader!")
            return pt.Status.FAILURE

        return pt.Status.SUCCESS

class C_LeaderIsFarEnough(pt.behaviour.Behaviour):
    def __init__(self, base_link, leader_link, min_distance_to_leader):
        self.leader_link = leader_link
        self.base_link = base_link
        self.min_distance_to_leader = min_distance_to_leader
        self.bb = pt.blackboard.Blackboard()
        self.listener = tf.TransformListener()
        self.leader_exists = False
        super(C_LeaderIsFarEnough, self).__init__(name="C_LeaderIsFarEnough")

    def setup(self, timeout):
        try:
            rospy.loginfo_throttle(3, "Waiting for transform from {} to {}...".format(self.base_link, self.leader_link))
            self.listener.waitForTransform(self.base_link, self.leader_link, rospy.Time(), rospy.Duration(timeout))
            self.leader_exists = True
            rospy.loginfo_throttle(3, "...Got it, we got a leader to follow!")
        except:
            rospy.logwarn_throttle(5, "Could not find xform from {} to {}, assuming there is no leader!".format(self.base_link,self.leader_link))

        return True

    def update(self):
        if not self.leader_exists:
            return pt.Status.FAILURE

        trans, rot = self.listener.lookupTransform(self.base_link,
                                                   self.leader_link,
                                                   rospy.Time(0))
        dist = np.linalg.norm(trans)
        self.feedback_message = "Distance to leader:{}".format(dist)
        if dist > self.min_distance_to_leader:
            return pt.Status.SUCCESS

        rospy.loginfo_throttle(5, "Leader {} is too close to me {} !".format(self.leader_link, self.base_link))
        return pt.Status.FAILURE


#!/usr/bin/env python

import turbotutils.cluster
import requests
import urllib.parse
import json

def create_user_ssh_keys(turbot_api_access_key, turbot_api_secret_key, turbot_host_certificate_verification, turbot_host, turbot_user_id):
    """ Sets user access AKIA key pairs for a specified account

    NOTE: This requires a Cluster role Turbot/Owner or higher in order to work.
    """
    api_method = "POST"
    api_url = "/api/v1/users/%s/sshKeys" % (turbot_user_id)
    response = requests.request(
        api_method,
        urllib.parse.urljoin(turbot_host, api_url),
        auth=(turbot_api_access_key, turbot_api_secret_key),
        verify=turbot_host_certificate_verification,
        headers={
            'content-type': "application/json",
            'cache-control': "no-cache"
        }
    )

    responseObj = json.loads(response.text)
    return(responseObj['privateKey'])

if __name__ == '__main__':
    # Set to False if you do not have a valid certificate for your Turbot Host
    turbot_host_certificate_verification = True

    # Set to your Turbot Host URL
    turbot_host = turbotutils.get_turbot_host()
    turbot_user_id = turbotutils.get_turbot_user()
    # Get the access and secret key pairs
    (turbot_api_access_key,turbot_api_secret_key) = turbotutils.get_turbot_access_keys()

    turbot_user_id= 'dummy_user'
    cluster_id = turbotutils.cluster.get_cluster_id(turbot_host, turbot_api_access_key, turbot_api_secret_key, turbot_host_certificate_verification)
    new_key = create_user_ssh_keys(turbot_api_access_key, turbot_api_secret_key, turbot_host_certificate_verification, turbot_host, turbot_user_id)

    with open(turbot_user_id, 'w') as f:
        f.write(new_key)


from django.conf.urls.defaults import patterns, include, url

urlpatterns = patterns('spacescout_admin.views',
    #url(r'upload/$', 'upload.upload'),
    url(r'^$', 'page.home'),
    url(r'login/$', 'page.login'),
    url(r'space/$', 'page.space'),
    url(r'add/$', 'page.add'),
    url(r'upload/$', 'page.upload'),
)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
Créé le 14 Nov 2018

@author: SachaWanono
'''
import sys
import os
sys.path.insert(0, "/".join(os.path.dirname(os.path.abspath(__file__)).split("/")[:-2])+"/")
from src.InOutManager.inputmanager import InManager
from src.InOutManager.outputmanager import OutManager
from src.ScoringTools.calculScore import *
from src.projectDisplay import *
from src.projectListBuilder import projectListBuilder
from src.projectListBuilder import getBuildedNums
from src.project import *
from src.grid import Grid
from src.ScoringTools.calculWeight import *
import random
import time
from src.constants import Const

def placementUnitOpti(nbErreurs, choixExercice, nbSimu):
    """
    Fonction permettant de remplir la matrice "city plan" bâtiment par bâtiment en
    optimisant le score.

    :param nbErreurs: (int) nombre d'erreurs maximale autorisée
    :param choixExercice: (int) choisir parmi les "inputs" fournis par Google. 0->a_*, 5->f_*
    :param nbSimu: (int) nombre de simulation à effectuer pour obtenir la meilleur grille
    """

    print("Placement Unitaire Optimisé lancé !")
    print("Création de grille lancée à : " + time.strftime('%H:%M:%S'))
    print("chargement des projets du fichier " + Const.FILE_NAMES[choixExercice] + ".in ...")
    inputs = InManager(Const.FILE_NAMES[choixExercice])
    maximumDistance = inputs.walkDist

    # ici on charge dans projectList les projets présents dans le fichier
    projectList, trueNum = projectListBuilder(inputs)
    i = 0
    while isinstance(projectList[i], type(projectList[0])):
        i += 1
    # i est le dernier indice des résidentiels
    residList = projectList[:i]
    utilList = projectList[i:]

    print("fichiers chargés\n\nconstructions des batiments ...")

    listWeightsR = weight(residList)

    bestCity = None
    bestCityScore = 0
    for simuCourrante in range(nbSimu):
        # on effectue un certain nombre de simulations sur city et on garde la meilleure dans bestCity

        if inputs.rows == 1000:
            city = Grid(inputs.rows//10, inputs.columns//10)
        else:
            city = Grid(inputs.rows, inputs.columns)

        # on construit les deux premiers batiments au milieu de la map
        initialization = True
        while initialization:

            firstProjectResid = selectRandResid(residList, listWeightsR)
            firstProjectUtil = selectRandUtil(utilList, projectList, maximumDistance, city.buildedU, city.buildedR)

            residRandCo = (random.randint(0, city.grows - firstProjectResid.rows), random.randint(0, city.gcolumns - firstProjectResid.columns))
            utilRandCo = (random.randint(0, city.grows - firstProjectResid.rows), random.randint(0, city.gcolumns - firstProjectResid.columns))

            residBuildable = city.isBuildable(firstProjectResid, residRandCo)
            utilBuildable = city.isBuildable(firstProjectUtil, utilRandCo)

            # Si on arrive à placer les 2 bâtiments, on les place et on passe au placement unitaire
            if residBuildable and utilBuildable:
                city.buildProject(firstProjectResid, residRandCo)
                city.buildProject(firstProjectUtil, utilRandCo)

                initialization = False

        # on construit le plus de batiment possible avec le meilleur score possible
        constructing = True
        while constructing:

            bestCoords = (-1, -1)
            bestScore = 0

            constructR = (True if random.randint(0, 1) == 1 else False)
            proj = (selectRandResid(residList, listWeightsR) if constructR else utilList[random.randint(0, len(utilList) - 1)])

            # pour le projet choisi, on teste un certain nombre de coordonnées aléatoires et on garde celles qui rapportent le plus de score
            for nbTests in range(nbErreurs):
                curentCoords = (random.randint(0, city.grows - proj.rows), random.randint(0, city.gcolumns - proj.columns))
                if city.isBuildable(proj, curentCoords):
                    if constructR:
                        curentScore = getResidScore(projectList, (proj.numProject, curentCoords, proj.capacity), maximumDistance, city.buildedU)
                    else:
                        curentScore = getUtilScore(projectList, (proj.numProject, curentCoords, proj.utilType), maximumDistance, city.buildedR, city.buildedU)

                    if curentScore > bestScore:
                        bestScore = curentScore
                        bestCoords = curentCoords

            if bestCoords != (-1, -1):
                city.buildProject(proj, bestCoords)
            else:
                constructing = False

        scoreSimu = getGridScore(projectList, maximumDistance, city.buildedR, city.buildedU, False)
        if scoreSimu > bestCityScore:
            bestCity = copy.deepcopy(city)
            bestCityScore = scoreSimu

    finalCity = duplicate(bestCity, 10, 10, projectList)

    inputs.close()

    print("limite de {} simulations atteinte".format(nbSimu) + " : grille construite")

    out = OutManager("{} {}".format(Const.FILE_NAMES[choixExercice], time.strftime('%d-%m-%Y %H-%M-%S')))

    # city.showNumProject() # Prend beaucoup de ressources.

    print("nombre de batiments résidentiels : ", len(finalCity.buildedR), " / utilitaires : ", len(finalCity.buildedU), "\n")

    # on calcule le score et on l'écrit dans un fichier et dans le terminal
    score = getGridScore(projectList, maximumDistance, finalCity.buildedR, finalCity.buildedU, True)
    out.writeScore(score)
    out.writeProjects(getBuildedNums(finalCity.buildedR + finalCity.buildedU, trueNum))
    print("score = ", score)
    # printGrid(city, projectList) # Affichage avec TKinter
    Grid.show(city) # Affiche la grille dans la console

def duplicate(city, nbDupliX, nbDupliY, projectList):
    """
    Fonction permettant de dupliquer une grille le nombre de fois demandé

    :param city: (Grid) grille à dupliquer
    :param nbDupliX: (int) nombre de duplications en X
    :param nbDupliY: (int) nombre de duplications en Y
    :param projectList: (list[Project]) list des projets de l'input
    :return grid: (Grid) grille finale dupliquée
    """

    grid = Grid(city.grows*nbDupliX, city.gcolumns*nbDupliY)

    for i in range(nbDupliX):
        for j in range(nbDupliY):
            for k in range(len(city.buildedR)):
                proj = projectList[city.buildedR[k][Const.NUM_PROJ]]
                newBuildedR = copy.deepcopy(city.buildedR[k])
                newBuildedR[Const.COORD_PROJ] = (newBuildedR[Const.COORD_PROJ][0] + city.grows*i, newBuildedR[Const.COORD_PROJ][1] + city.gcolumns*j)
                grid.buildProject(proj, newBuildedR[Const.COORD_PROJ])

            for k in range(len(city.buildedU)):
                proj = projectList[city.buildedU[k][Const.NUM_PROJ]]
                newBuildedU = copy.deepcopy(city.buildedU[k])
                newBuildedU[Const.COORD_PROJ] = (newBuildedU[Const.COORD_PROJ][0] + city.grows*i, newBuildedU[Const.COORD_PROJ][1] + city.gcolumns*j)
                grid.buildProject(proj, newBuildedU[Const.COORD_PROJ])

    return grid


import os
import pandas as pd
import re
names = ["Nancy", "Vicky", "Tori", "Penny", "Jilly", "Cindy", "Laurie",
"Franny", "Brandy", "Betty", "Kylie", "Carrie", "Tracy", "Chloe",
"Annie", "Mindy", "Molly", "Sally", "Lacey", "Carlie", "Mandy",
"Hailey", "Lilly", "Jenny", "Judy", "Kerry", "Kelly", "Bonnie",
"Kimmy", "Freddy", "Ronnie", "Davey", "Jimmy", "Kenny", "Percy", "Sammy",
"Harvey", "Gary", "Ollie", "Danny", "Henry", "Teddy", "Wally",
"Johnny", "Jerry", "Timmy", "Benny", "Tommy", "Joey"]

verbs = ["Kissing", "Kicking", "Poking", "Lifting", "Pushing", "Pulling"]

status = []
words = names + verbs
WORDS = [x.upper() for x in words]
#with open(os.path.join('analysis', 'tmp', 'dict')) as f:
with open(os.path.join('analysis', 'model', 'dict')) as f:

    a = f.readlines()
    for iWord in WORDS:
        bFound = 0
        for iLine in a:
            b = re.search('^' + iWord + ' ', iLine)
#            print(b)
#            print(iLine)
#            if type(b.group(0)) is str:
            if not b is None:
                status = status + ["exist"]
                bFound = 1
                break
        if not bFound:

#            if not ' ' + iWord + ' ' in a:
                # print(iWord + ' is not in the dict')
            status = status + ["do not exist"]
#            else:

tWords_in_dict = pd.DataFrame(zip(WORDS, status), columns=['WORDS', 'status'])
tWords_in_dict.to_csv('tWords_in_dict.csv',  encoding='utf-8')


import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
import pygeoj
import pyproj
from allocation.Griding_TN import MyGrouping_Grid, Distance_Dict_Builder

from allocation.pmedianAllocator import pmedianAllocator

df_inrix=None
df_incident=None
Grid, Grid_center=MyGrouping_Grid(df_inrix,df_incident ,width = 0.1,height = 0.1 )

#Just for test
Grid_3310 =Grid_center.to_crs('EPSG:3310')
Grid_3310['center_3310']=Grid_3310.centroid
Grid_3310['area']=Grid.to_crs('EPSG:3310').area/1e6

All_seg_incident=None
Distant_Dic,All_seg_incident=Distance_Dict_Builder(All_seg_incident,Grid_center)

allocator = pmedianAllocator()

# inputs
num_resources = 5
possible_facility_locations = set(Grid_center['Grid_ID'])
demand_nodes = set(All_seg_incident['Grouping'])

weights_dict = dict()
for n_l in demand_nodes:
    weights_dict[n_l] = float(n_l) / 10000000.0

allocation = allocator.solve(number_of_resources_to_place=num_resources,
                             possible_facility_locations= possible_facility_locations,
                             demand_nodes=demand_nodes,
                             distance_dict=Distant_Dic,
                             demand_weights=weights_dict,
                             score_type='penalty',
                             alpha=1.0)

print(allocation)
#fig=Grid.plot()
All_seg_incident_4326=gpd.GeoDataFrame(All_seg_incident, geometry=All_seg_incident['line'], crs={'init': 'epsg:4326'} )
fig=All_seg_incident_4326.plot(All_seg_incident_4326['Grouping'])
Grid_center[Grid_center['Grid_ID'].isin(allocation)].plot(ax=fig)

All_seg_incident_4326=gpd.GeoDataFrame(All_seg_incident, geometry=All_seg_incident['line'], crs={'init': 'epsg:4326'} )
All_seg_incident_4326.plot(All_seg_incident_4326['Grouping'])

print('done!')


# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

import os

import numpy as np

from maro.simulator import Env
from maro.rl import AgentManagerMode, SimpleActor, ActorWorker
from maro.utils import convert_dottable

from components import CIMActionShaper, CIMStateShaper, POAgentManager, TruncatedExperienceShaper, create_po_agents

def launch(config):
    config = convert_dottable(config)
    env = Env(config.env.scenario, config.env.topology, durations=config.env.durations)
    agent_id_list = [str(agent_id) for agent_id in env.agent_idx_list]
    state_shaper = CIMStateShaper(**config.env.state_shaping)
    action_shaper = CIMActionShaper(action_space=list(np.linspace(-1.0, 1.0, config.agents.num_actions)))
    experience_shaper = TruncatedExperienceShaper(**config.env.experience_shaping)

    config["agents"]["input_dim"] = state_shaper.dim
    agent_manager = POAgentManager(
        name="cim_actor",
        mode=AgentManagerMode.INFERENCE,
        agent_dict=create_po_agents(agent_id_list, config.agents),
        state_shaper=state_shaper,
        action_shaper=action_shaper,
        experience_shaper=experience_shaper,
    )
    proxy_params = {
        "group_name": os.environ["GROUP"],
        "expected_peers": {"learner": 1},
        "redis_address": ("localhost", 6379)
    }
    actor_worker = ActorWorker(
        local_actor=SimpleActor(env=env, agent_manager=agent_manager),
        proxy_params=proxy_params
    )
    actor_worker.launch()

if __name__ == "__main__":
    from components.config import config
    launch(config)


import csv
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
from sklearn.cluster import KMeans
from sklearn import metrics
import datetime
import processing.ica.icaDecompHousing as ica
import processing.nmf.nmfDecompHousing as nmf
import processing.pca.pcaDecompHousing as pca
import processing.randomized.randomDecompHousing as randDe

def main():
    #kmeanProcessed()
    plotnCluster()

def kmeanProcessed():
    l = [ica,nmf,pca,randDe]
    module = ["ica","nmf","pca","randDe"]
    count=0
    for z in l:
        print('this is the ' +module[count])
        x, y = z.transformData()
        y_predict = KMeans(n_clusters=2).fit_predict(x)
        score = metrics.rand_score(y, y_predict)
        print(module[count]+ ' kmean rand score')
        print(score)
        print(module[count] +'  done')
        count+=1

def plotnCluster():
    l = [ica,nmf,pca,randDe]
    module = ["ica","nmf","pca","randDe"]
    count=0
    _, axes = plt.subplots(1, 2, figsize=(20, 5))

    for z in l:
        accuracy=[]
        times=[]
        print('this is the ' + module[count])
        x, y = z.transformData()
        for n in range(1,50):
            start = datetime.datetime.now()
            y_predict = KMeans(n_clusters=n).fit_predict(x)
            y_test_accuracy = metrics.rand_score(y, y_predict)
            stop = datetime.datetime.now()
            accuracy.append(y_test_accuracy*100)
            times.append(((stop - start).total_seconds()))
        param_range_a=np.linspace(1, 50, len(accuracy))
        param_range_t=np.linspace(1, 50, len(times))
        axes[0].plot(param_range_a, accuracy, label=module[count], lw=2)
        axes[1].plot(param_range_t, times, label=module[count], lw=2)
        count+=1

    axes[0].set_xlabel("Training examples")
    axes[0].set_ylabel("Score")
    axes[0].grid()
    axes[0].legend(loc="best")
    axes[0].set_xlabel("number of clusters")
    axes[0].set_ylabel("Accuracy score %")
    axes[1].grid()
    axes[1].legend(loc="best")
    axes[1].set_xlabel("number of clusters")
    axes[1].set_ylabel("Time in Seconds")
    plt.suptitle('K-means Housing')
    plt.show()
main()