#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4

from django.conf import settings
from accounts.models import flangioUser as User
from django.utils.datastructures import SortedDict
import os, json, sys, uuid, csv, pickle
from pymongo import Connection
from socialgraph.models import SocialGraph
from datetime import datetime, date, time
from bson.code import Code
from pymongo import Connection, DESCENDING
from bson.objectid import ObjectId

from pymongo import MongoClient

def to_json(results_dict):
    return json.dumps(results_dict, indent = 4)

def filter_social_graph(request, serial_result):
    result_list=[]
    try:
        grantee=User.objects.get(username=request.user)
    except(User.DoesNotExist):
        error = "The grantee could not be found."
        grantee=None

    for r in serial_result:
        try:
            grantr=r['subject']
            grantor=User.objects.get(username=grantr)
        except(User.DoesNotExist):
            try:
                grantor=User.objects.get(email=grantr)
            except(User.DoesNotExist):
                try:
                    gp=User.objects.get(anonymous_patient_id=grantr)
                    grantor = gp.user
                except(User.DoesNotExist):
                    error = "The grantor could not be found."

        try:
            sg=SocialGraph.objects.get(grantor=grantor, grantee=grantee)
            result_list.append(r)
        except(SocialGraph.DoesNotExist):
            """"Keep going"""
            pass
        except(User.DoesNotExist):
            print """The user does not exist.  Fatal Server Error!"""
            return ()

    return result_list

def normalize_results(results_dict):
    #Define some dummy/default values
    mydt=datetime.now()
    myd=date.today()
    myt=time(0,0)

    for r in results_dict['results']:
        for k,v in r.items():
            if type(r[k]) == type(mydt):
                r[k]= v.__str__()
                #print r[k]
    return results_dict

def normalize_list(results_list):
    #Define some dummy/default values
    mydt=datetime.now()
    myd=date.today()
    myt=time(0,0)

    for r in results_list:
        for k,v in r.items():
            if type(r[k]) == type(mydt):
                r[k]= v.__str__()

    return results_list

def query_mongo(query={}, database_name=settings.MONGO_DB_NAME,
                collection_name=settings.MONGO_MASTER_COLLECTION,
                skip=0, sort=None, limit=settings.MONGO_LIMIT, return_keys=()):
    """return a response_dict  with a list of search results"""

    l=[]
    response_dict={}

    try:
        mc =   MongoClient(host=settings.MONGO_HOST,
                           port=settings.MONGO_PORT)

        db          =   mc[str(database_name)]
        collection   = db[str(collection_name)]

        #Cast the query to integers
        if settings.CAST_STRINGS_TO_INTEGERS:
            query = cast_number_strings_to_integers(query)

        #print query
        if return_keys:
            return_dict={}
            for k in return_keys:
                return_dict[k]=1
            #print "returndict=",return_dict
            mysearchresult=collection.find(query, return_dict).skip(skip).limit(limit)
        else:
            mysearchresult=collection.find(query).skip(skip).limit(limit)

        if sort:
            mysearchresult.sort(sort)

        response_dict['num_results']=int(mysearchresult.count(with_limit_and_skip=False))
        response_dict['code']=200
        response_dict['type']="search-results"
        for d in mysearchresult:
            d['id'] = d['_id'].__str__()
            del d['_id']
            l.append(d)
        response_dict['results']=l

    except:
        print "Error reading from Mongo"
        print str(sys.exc_info())
        response_dict['num_results']=0
        response_dict['code']=500
        response_dict['type']="Error"
        response_dict['results']=[]
        response_dict['message']=str(sys.exc_info())

    return response_dict

def query_mongo_sort_decend(query={}, database_name=settings.MONGO_DB_NAME,
                collection_name=settings.MONGO_MASTER_COLLECTION,
                skip=0, limit=settings.MONGO_LIMIT, return_keys=(), sortkey=None):
    """return a response_dict  with a list of search results in decending
    order based on a sort key
    """

    l=[]
    response_dict={}

    try:
        mc =   MongoClient(host=settings.MONGO_HOST,
                           port=settings.MONGO_PORT)

        db          =   mc[str(database_name)]
        collection   = db[str(collection_name)]

        if return_keys:
            return_dict={}
            for k in return_keys:
                return_dict[k]=1
            #print "returndict=",return_dict
            mysearchresult=collection.find(query, return_dict).skip(skip).limit(limit).sort(sortkey,DESCENDING)
        else:
            mysearchresult=collection.find(query).skip(skip).limit(limit).sort(sortkey,DESCENDING)

        response_dict['num_results']=int(mysearchresult.count(with_limit_and_skip=False))
        response_dict['code']=200
        response_dict['type']="search-results"
        for d in mysearchresult:
            d['id'] = d['_id'].__str__()
            del d['_id']
            l.append(d)
        response_dict['results']=l

    except:
        print "Error reading from Mongo"
        print str(sys.exc_info())
        response_dict['num_results']=0
        response_dict['code']=500
        response_dict['type']="Error"
        response_dict['results']=[]
        response_dict['message']=str(sys.exc_info())
    return response_dict

def delete_mongo(query={}, database_name=settings.MONGO_DB_NAME,
                 collection_name=settings.MONGO_MASTER_COLLECTION,
                 just_one=False):
    """delete from mongo helper"""

    l=[]
    response_dict={}

    try:
        mc =   MongoClient(host=settings.MONGO_HOST,
                           port=settings.MONGO_PORT)
        db          =   mc[str(database_name)]
        collection   = db[str(collection_name)]

        mysearchresult=collection.remove(query, just_one)

        #response_dict['num_results']=int(mysearchresult.count())
        response_dict['code']=200
        response_dict['type']="remove-confirmation"

    except:
        #print "Error reading from Mongo"
        #print str(sys.exc_info())
        response_dict['num_results']=0
        response_dict['code']=500
        response_dict['type']="Error"
        response_dict['results']=[]
        response_dict['message']=str(sys.exc_info())
    return response_dict

def write_mongo(document, database_name=settings.MONGO_DB_NAME,
                 collection_name=settings.MONGO_MASTER_COLLECTION,
                 update = False):
    """Write a document to the collection. Return a response_dict containing
    the written record. Method functions as both insert or update based on update
    parameter"""

    l=[]
    response_dict={}
    try:
        mc =   MongoClient(host=settings.MONGO_HOST,
                           port=settings.MONGO_PORT)
        db          =   mc[str(database_name)]
        collection   = db[str(collection_name)]

        # Cast the query to integers
        if settings.CAST_STRINGS_TO_INTEGERS:
            query = cast_number_strings_to_integers(query)

        potential_key_found = False
        existing_transaction_id = None
        existing_mongo_id  = None

        #enforce non-repudiation constraint on create
        #if document.has_key("transaction_id"):
        #    existing_transaction_id = collection.find_one({'transaction_id':document['transaction_id']})
        #    if existing_transaction_id:
        #        potential_key_found = True

        if document.has_key("id"):
            document["_id"] = ObjectId(document["id"])
            del document["id"]

        if document.has_key("_id"):
            existing_mongo_id = collection.find_one({'_id':document['_id']})
            if existing_mongo_id:
                potential_key_found = True

        if update==False and potential_key_found==True:
                """409 conflict"""
                response_dict['num_results']=0
                response_dict['code']=409
                response_dict['type']="Error"
                response_dict['results']=[]
                response_dict['message']="Perhaps you meant to perform an update instead?"
                response_dict['errors']=["Conflict. This transaction_id has already been created.",]
                return response_dict

        elif update and potential_key_found: #this is an update
            #set kwargs _id to the existing_id to force to overwrite existing document

            #if existing_transaction_id:
            #
            #    document['_id'] = ObjectId(existing_transaction_id['_id'])
            #    document['history']=True
            #    history_collection_name = "%s_history" % str(collection_name)
            #    history_collection   = db[str(history_collection_name)]
            #
            #    history_object = existing_transaction_id
            #    history_object['historical_id'] = existing_transaction_id['_id']
            #    del history_object['_id']
            #    #now write the record to the historical collection
            #    written_object = history_collection.insert(history_object)

            if existing_mongo_id:
                document['_id'] =  ObjectId(existing_mongo_id['_id'])
                document['history']=True
                document['verified']=False
                history_collection_name = "%s_history" % str(collection_name)
                history_collection   = db[str(history_collection_name)]

                #print history_collection
                #print existing_mongo_id

                history_object = existing_mongo_id

                history_object['historical_id'] = existing_mongo_id['_id']
                del history_object['_id']
                #print history_object

                #now write the record to the historical collection
                written_object = history_collection.insert(history_object)

            #update the record
            myobjectid=collection.save(document)

        else:
            # this is new so perform an insert.
            myobjectid=collection.insert(document)

        #now fetch the record we just wrote so that we write it back to the DB.
        myobject=collection.find_one({'_id':myobjectid})
        response_dict['code']=200
        response_dict['type']="write-results"
        myobject['id'] = myobject['_id'].__str__()
        del myobject['_id']
        l.append(myobject)
        response_dict['results']=l

    except:
        #print "Error reading from Mongo"
        #print str(sys.exc_info())
        response_dict['num_results']=0
        response_dict['code']=400
        response_dict['type']="Error"
        response_dict['results']=[]
        response_dict['message']=str(sys.exc_info())
    return response_dict

def bulk_csv_import_mongo(csvfile, delete_collection_before_import=False,
                          database_name=settings.MONGO_DB_NAME,
                          collection_name=None):

    """return a response_dict  with a list of search results"""
    """method can be insert or update"""
    #print "writing ", csvfile._get_path(), "to the collection" , settings.MONGO_DB_NAME, settings.MONGO_MASTER_COLLECTION
    l=[]
    response_dict={}
    try:
        mconnection =   Connection(settings.MONGO_HOST, settings.MONGO_PORT)
        db = 	        mconnection[database_name]
        if not collection_name:
            collection = db[settings.MONGO_MASTER_COLLECTION]
        else:
            collection = db[collection_name]

        if delete_collection_before_import:
            myobjectid=collection.remove({})

        #open the csv file.
        csvhandle = csv.reader(open(csvfile._get_path(), 'rb'), delimiter=',')

        rowindex = 0
        errors=0
        error_list =[]
        success =0
        for row in csvhandle :

            if rowindex==0:
                 column_headers = row
                 cleaned_headers = []
                 for c in column_headers:
                    c= c.replace(".", "")
                    c =c.replace("$", "-")
                    c =c.replace(" ", "_")
                    cleaned_headers.append(c)
            else:

                record = dict(zip(cleaned_headers, row))
                #if there is no values, skip the key value pair
                kwargs ={}

                #Only populate fields that are not blank.
                for k,v in record.items():
                    if v:
                        if v.isdigit():
                            kwargs[k]=int(v)
                        else:
                            kwargs[k]=v

                try:

                    myobjectid=collection.insert(kwargs)
                    success+=1
                except:
                    error_message = "Error on row " + rowindex +  ". " + str(sys.exc_info())
                    error_list.append(str(sys.exc_info()))

            rowindex+=1

        if error_list:
            response_dict ={}
            response_dict['num_rows_imported']=rowindex
            response_dict['num_rows_errors']=len(error_list)
            response_dict['errors']=error_list
            response_dict['code']=400
            response_dict['message']="Completed with errors"
        else:

            response_dict ={}
            response_dict['num_rows_imported']=success
            response_dict['code']=200
            response_dict['message']="Completed."
        return response_dict

    except:
        #print "Error reading from Mongo"
        #print str(sys.exc_info())
        response_dict['num_results']=0
        response_dict['code']=400
        response_dict['type']="Error"
        response_dict['results']=[]
        response_dict['message']=str(sys.exc_info())
    return response_dict

def verify_users_exist_and_sg(data):
    """
    Scan supplied user id's for exist valitity. All must exists.
    Compare ids against username, email, and anonymous_patient_id
    in that order.
    """

    data['errors']=[]
    try:
        receiver=User.objects.get(username=data['receiver'])
    except User.DoesNotExist:
        try:
            receiver=User.objects.get(email=data['receiver'])
        except User.DoesNotExist:
            try:
                receiver=UserProfile.objects.get(anonymous_patient_id=data['receiver'])
            except UserProfile.DoesNotExist:
                msg ="Receiver %s not found." %(data['receiver'])
                data['errors'].append({'field':'receiver','description':msg})

    try:
        sender=User.objects.get(username=data['sender'])
    except User.DoesNotExist:
        try:
            sender=User.objects.get(email=data['sender'])
        except User.DoesNotExist:
            try:
                sender=UserProfile.objects.get(anonymous_patient_id=data['sender'])
            except UserProfile.DoesNotExist:
                msg ="Sender %s not found." %(data['sender'])
                data['errors'].append({'field':'sender','description': msg})

    try:
        subject=User.objects.get(username=data['subject'])
    except User.DoesNotExist:
        try:
            subject=User.objects.get(email=data['subject'])
        except User.DoesNotExist:
            try:
                up = UserProfile.objects.get(anonymous_patient_id=data['subject'])
                subject = up.user
            except UserProfile.DoesNotExist:
                msg ="Subject %s not found." %(data['subject'])
                data['errors'].append({'field':'subject', 'description': msg })

    social_graph_errors = verify_social_graph(sender, receiver, subject)

    if social_graph_errors:
        data['errors'].append(social_graph_errors)

    #If all is good then this should be an empty list.
    if not data['errors']:
        del data['errors']
    return data

def verify_social_graph(sender, receiver, subject):
    """You must pass in 3 user objects"""
    errors = []
    if settings.RESPECT_SOCIAL_GRAPH==False:
        return errors
    #otherwise verify that a Social graph G exists between sender and subject.
    try:
        sg=SocialGraph.objects.get(grantor=subject, grantee=sender)
    except(SocialGraph.DoesNotExist):
        error = "A social graph does not exist between the grantor %s and the grantee %s." % (subject, sender)
        errors.append(error)

    return errors

def get_since_id(filename=settings.SINCE_ID_FILE):
    readfile            = open(filename, 'r')
    buffer              = readfile.read()
    readfile.close()
    last_since_id       = int(buffer)
    new_since_id        = int(last_since_id) + 1
    openfile            = open(filename, 'w')
    openfile.writelines(str(new_since_id))
    openfile.close()
    return new_since_id

def get_user_profile_info(identifier):
    """Get the user profile based on username, email, vid, or anon ID, else
       return None
    """
    up = None
    try:
        up = User.objects.get(username=identifier)
    except User.DoesNotExist:
        try:
           up = User.objects.get(email=identifier)
        except User.DoesNotExist:
            try:
                up = User.objects.get(vid=identifier)
            except User.DoesNotExist:
                try:
                    up = User.objects.get(anonymous_patient_id=identifier)
                except UserProfile.DoesNotExist:
                    up=None
    return up

def build_non_observational_key(k):

    if str(k).__contains__("__"):
        model_field_split = str(k).split("__")
        newlabel = "%s_" % (model_field_split[0])

        field_occurence_split =  str(model_field_split[1]).split("_")

        for i in  field_occurence_split[:-1]:
            newlabel = "%s_%s" % (newlabel,i)
        return newlabel
    return k

def delete_tx(attrs, collection=None):

    #Connect to the db or fail.
    try:
        mconnection     = Connection(settings.MONGO_HOST, settings.MONGO_PORT)
        db              = mconnection[settings.MONGO_DB_NAME]
        transactions    = db[settings.MONGO_MASTER_COLLECTION_NAME]
        history         = db[settings.MONGO_HISTORYDB_NAME]
    except:
        error=str(sys.exc_value)
        d={"code":'500',
           "message": "MongoDB Connection Error. MongoDB may not be running or flangio cannot access it.",
           "errors":(error,)}
        return d

    #Check to be sure this transaction_id exists
    mysearchresult=transactions.find_one({'transaction_id':attrs['transaction_id']})

    if mysearchresult:
        # This tx already exists (this uuid has been submitted before)
        if settings.ALLOW_DELETE_TX==True:
            attrs['history']=True
            responsedict=mysearchresult
            responsedict['_id'] = str(uuid.uuid4())
            hist_id=history.insert(responsedict)
            #print "saved to history!!!"
            r= transactions.remove({'_id': attrs['transaction_id']})
        #print "removed origional from main collection"
            return {"code": "200", "message": "Transaction deleted.",}
        else:
            #if settings.ALLOW_DELETE_TX==False, then we fail the delete attempt.
            error="%s could not be deleted because the server is not configured to allow deletes." % (attrs['transaction_id'])
            d={"code": '403',
               "message": "Fobidden.  Deletes are not allowed.",
               "errors": (error,)}
            return d
    else:
        error="%s does not exist." % (attrs['transaction_id'])
        d={"code": '404',
            "message": "Not found.  The transaction you are trying to delete does not exist.",
            "errors": (error,)}
        return d

def raw_query_mongo_db(kwargs, collection_name=None):
    #for key in kwargs:
    #    print "arg: %s: %s" % (key, kwargs[key])

    """return a result list or an empty list"""
    l=[]
    response_dict={}

    try:
        mconnection =   Connection(settings.MONGO_HOST, settings.MONGO_PORT)
        db =            mconnection[settings.MONGO_DB_NAME]
        if not collection_name:
            transactions = db[settings.MONGO_MASTER_COLLECTION]
        elif (collection_name=="history"):
            transactions = db[settings.MONGO_HISTORYDB_NAME]

        mysearchresult=transactions.find(kwargs)
        mysearchcount=mysearchresult.count()
        if mysearchcount>0:
            response_dict['code']=200
            for d in mysearchresult:
                l.append(d)
            response_dict['results']=l
    except:
        #print "Error reading from Mongo"
        #print str(sys.exc_info())
        response_dict['code']=400

        response_dict['type']="Error"
        response_dict['message']=str(sys.exc_info())
    return response_dict

def prepare_idTransaction_dict(model_instance):
    fields = SortedDict()

    for field in model_instance._meta.fields:
        try:
            if getattr(model_instance, field.name) not in (None, ''):
                newfieldname ="%s" % (field.name)

                value = getattr(model_instance, field.name)
                #if a datetime sting, then turn into a datetime
                try:
                    value = time.strptime(value, "%Y-%m-%d %H:%M:%S")
                except:
                    pass
                try:
                    value = time.strptime(value, "%Y-%m-%d")
                except:
                    pass
                try:
                    value = time.strptime(value, "%H:%M:%S")
                except:
                    pass

            fields[newfieldname] = str(value)
        except:
            pass

    fields.insert(0,'_id', model_instance.transaction_id)
    if fields.has_key('extra_fields'):
        ef =json.loads(fields['extra_fields'])
        fields.update(ef)
        del fields['extra_fields']

    if fields.has_key('tags'):
        fields['tags'] = json.loads(fields['tags'])

    #print "The tx type is ", fields['transaction_type']
    if fields['transaction_type']=="omhe":
        if fields['text'] != "":
            p = parseomhe()
            d = p.parse(fields['text'])
            del d['transaction_id'], d['transaction_datetime']
            fields.update(d)

    #for k in fields.keys():
    #    print k,"= ",fields[k]

    return fields

def get_collection_keys(collection_name=None):
    l=[]
    try:
        mconnection =   Connection(settings.MONGO_HOST, settings.MONGO_PORT)
        db =            mconnection[settings.MONGO_DB_NAME]
        if not collection_name:
            ckey_collection = "%s_keys" % (settings.MONGO_MASTER_COLLECTION)
            print settings.MONGO_DB_NAME, ckey_collection
            collection = db[ckey_collection]
        else:
            ckey_collection = "%s_keys" % (collection_name)
            collection = db[ckey_collection]
        result = collection.find({}).distinct("_id")
        for r in result:
            l.append(r)

        if settings.SORTCOLUMNS:
            nl=[] #new list list
            #sort the list according to our list

            for i in settings.SORTCOLUMNS:
                for j in l:
                    if j.__contains__(i):
                        nl.append(j)
            difflist = list(set(l) - set(nl))

            for i in difflist:
                nl.append(i)
            return nl

        else:
            return sorted(l)
    except:
        print "Error.", str(sys.exc_info())
        return []

def get_collection_labels():
    l=[]
    try:
        mconnection     = Connection(settings.MONGO_HOST, settings.MONGO_PORT)
        db              = mconnection[settings.MONGO_DB_NAME]
        collection      = db[settings.MONGO_MASTER_LABELS_COLLECTION]

        result = collection.find_one({})

        label_dict = dict((x, y) for x, y in result['labels'])

        return label_dict
    except:
        print "Error reading from Mongo!", str(sys.exc_info())
        return {}

def get_labels_tuple():
    l=[]
    try:
        mconnection     = Connection(settings.MONGO_HOST, settings.MONGO_PORT)
        db              = mconnection[settings.MONGO_DB_NAME]


# -*- coding: utf-8 -*-
try:
    import pygtk
    pygtk.require('2.0')
except ImportError, err:
    print ("PyGTK not found. Please make sure it is installed properly and referenced in your PYTHONPATH environment variable.")

import os.path
import threading

import gobject
import gtk
from gettext import gettext as _
from gettext import ngettext

import dialog_swallower
import game_selector
import gsudoku
import printing
import saver
import sudoku_generator_gui
import sudoku_maker
import timer
from defaults import (APPNAME, APPNAME_SHORT, AUTHORS, COPYRIGHT, DESCRIPTION, DOMAIN,
        IMAGE_DIR, LICENSE, MIN_NEW_PUZZLES, UI_DIR, VERSION, WEBSITE, WEBSITE_LABEL)
from gtk_goodies import mateconf_wrapper, Undo, dialog_extras
from simple_debug import simple_debug, options

ICON_FACTORY = gtk.IconFactory()
STOCK_PIXBUFS = {}
for filename, stock_id in [('footprints.png', 'tracks'), ]:
    try:
        pb = gtk.gdk.pixbuf_new_from_file(os.path.join(IMAGE_DIR, filename))
    except gobject.GError, e:
        print 'Failed to load pixbuf: %s' % e
        continue
    STOCK_PIXBUFS[stock_id] = pb
    iconset = gtk.IconSet(pb)
    ICON_FACTORY.add(stock_id, iconset)
    ICON_FACTORY.add_default()

gtk.stock_add([('tracks',
                _('Track moves'),
                0, 0, ""), ])

def inactivate_new_game_etc (fun):
    def inactivate_new_game_etc_ (ui, *args, **kwargs):
        paths = [
            '/MenuBar/Game/New',
            '/MenuBar/Game/Print',
            # undo/redo is handled elsewhere as it can't simply be turned on/off.
            '/MenuBar/Edit/Clear',
            '/MenuBar/Edit/ClearNotes',
            '/MenuBar/View/ToggleToolbar',
            '/MenuBar/Tools/ShowPossible',
            '/MenuBar/Tools/AutofillCurrentSquare',
            '/MenuBar/Tools/Autofill',
            '/MenuBar/Tools/AlwaysShowPossible',
            '/MenuBar/Tools/ShowImpossibleImplications',
            '/MenuBar/Tools/Tracker',
            '/MenuBar/Game/PuzzleInfo',
            ]
        for p in paths:
            action = ui.uimanager.get_action(p)
            if not action:
                action = ui.uimanager.get_widget(p)
            if not action:
                print 'No action at path', p
            else:
                action.set_sensitive(False)
        ret = fun(ui, *args, **kwargs)
        for p in paths:
            action = ui.uimanager.get_action(p)
            if not action:
                action = ui.uimanager.get_widget(p)
            if not action:
                print 'No action at path', p
            else:
                action.set_sensitive(True)
        return ret
    return inactivate_new_game_etc_

class UI (mateconf_wrapper.MateConfWrapper):
    ui = '''<ui>
    <menubar name="MenuBar">
      <menu name="Game" action="Game">
        <menuitem action="New"/>
        <separator/>
        <menuitem action="PuzzleInfo"/>
        <separator/>
        <menuitem action="Print"/>
        <menuitem action="PrintMany"/>
        <separator/>
        <menuitem action="Close"/>
      </menu>
      <menu action="Edit">
        <menuitem action="Undo"/>
        <menuitem action="Redo"/>
        <separator/>
        <menuitem action="Clear"/>
        <menuitem action="ClearNotes"/>
      </menu>
      <menu action="View">
        <menuitem action="FullScreen"/>
        <separator/>
        <menuitem action="ToggleToolbar"/>
        <menuitem action="ToggleHighlight"/>
      </menu>
      <menu action="Tools">
        <menuitem action="ShowPossible"/>
        <menuitem action="AutofillCurrentSquare"/>
        <menuitem action="Autofill"/>
        <separator/>
        <menuitem action="AlwaysShowPossible"/>
        <menuitem action="ShowImpossibleImplications"/>
        <separator/>
        <menuitem action="Generator"/>
        <menuitem action="BackgroundGenerator"/>
        <separator/>
        <menuitem action="Tracker"/>
        </menu>
      <menu action="Help">
        <menuitem action="ShowHelp"/>
        <menuitem action="About"/>
      </menu>
    </menubar>
    <toolbar name="Toolbar">
      <toolitem action="New"/>
      <toolitem action="Print"/>
      <separator/>
      <toolitem action="Undo"/>
      <toolitem action="Redo"/>
      <separator/>
      <toolitem action="ShowPossible"/>
      <toolitem action="AutofillCurrentSquare"/>
      <separator/>
      <toolitem action="ToggleHighlight"/>
      <toolitem action="Tracker"/>
    </toolbar>
    </ui>'''

    initial_prefs = {'group_size':9,
                     'always_show_hints':False,
                     'difficulty':0.0,
                     'minimum_number_of_new_puzzles':MIN_NEW_PUZZLES,
                     'highlight':False,
                     'bg_color':'black',
                     'show_tracker':False,
                     'width': 700,
                     'height': 675,
                     'auto_save_interval':60 # auto-save interval in seconds...
                     }

    @simple_debug
    def __init__ (self, run_selector = True):
        """run_selector means that we start our regular game.

        For testing purposes, it will be convenient to hand a
        run_selector=False to this method to avoid running the dialog
        and allow a tester to set up a game programmatically.
        """
        mateconf_wrapper.MateConfWrapper.__init__(self,
                                            mateconf_wrapper.MateConf('mate-sudoku')
                                            )
        self.setup_gui()
        self.timer = timer.ActiveTimer(self.w)
        self.won = False
        # add the accelerator group to our toplevel window
        self.worker_connections = []
        self.is_fullscreen = False

        # setup sudoku maker...
        self.sudoku_maker = sudoku_maker.SudokuMaker()
        self.sudoku_tracker = saver.SudokuTracker()
        # generate puzzles while our use is working...
        self.show()
        if run_selector:
            self.do_stop()
            if self.select_game():
                # If this return True, the user closed...
                self.quit = True
            else:
                self.quit = False
                # Generate puzzles in background...
                if self.mateconf['generate_puzzles_in_background']:
                    gobject.timeout_add_seconds(1, lambda *args: self.start_worker_thread() and True)

    @inactivate_new_game_etc
    def select_game (self):
        self.tb.hide()
        self.update_statusbar()
        choice = game_selector.NewOrSavedGameSelector().run_swallowed_dialog(self.swallower)
        if not choice:
            return True
        self.timer.start_timing()
        if choice[0] == game_selector.NewOrSavedGameSelector.NEW_GAME:
            self.gsd.change_grid(choice[1], 9)
            self.update_statusbar()
        if choice[0] == game_selector.NewOrSavedGameSelector.SAVED_GAME:
            saver.open_game(self, choice[1])
            self.update_statusbar()
        if self.mateconf['show_toolbar']:
            self.tb.show()
        if self.mateconf['always_show_hints']:
            self.gsd.update_all_hints()
        if self.mateconf['highlight']:
            self.gsd.toggle_highlight(True)

    def show (self):
        self.gsd.show()
        self.w.show()

    def setup_gui (self):
        self.initialize_prefs()
        self.setup_main_window()
        self.gsd = gsudoku.SudokuGameDisplay()
        self.gsd.connect('puzzle-finished', self.you_win_callback)
        self.setup_color()
        self.setup_actions()
        self.setup_undo()
        self.setup_autosave()
        self.w.add_accel_group(self.uimanager.get_accel_group())
        self.setup_main_boxes()
        self.setup_tracker_interface()
        self.setup_toggles()

    def setup_main_window (self):
        gtk.window_set_default_icon_name('mate-sudoku')
        self.w = gtk.Window()
        self.w.set_default_size(self.mateconf['width'], self.mateconf['height'])
        self.w.set_title(APPNAME_SHORT)
        self.w.connect('configure-event', self.resize_cb)
        self.w.connect('delete-event', self.quit_cb)
        self.uimanager = gtk.UIManager()

    def setup_actions (self):
        self.main_actions = gtk.ActionGroup('MainActions')
        self.main_actions.add_actions([
            ('Game', None, _('_Game')),
            ('New', gtk.STOCK_NEW, None,
             '<Control>n', _('New game'), self.new_cb),
            ('Print', gtk.STOCK_PRINT, None,
             None, _('Print current game'), self.print_game),
            ('PrintMany', gtk.STOCK_PRINT, _('Print _Multiple Sudokus'),
             None, _('Print more than one sudoku at a time.'), self.print_multiple_games),
            ('Close', gtk.STOCK_CLOSE, None, '<Control>w',
             _('Close Sudoku'), self.quit_cb),
            ('Tools', None, _('_Tools')),
            ('View', None, _('_View')),
            ('ShowPossible', gtk.STOCK_DIALOG_INFO, _('_Hint'),
             '<Control>h',
             _('Show which numbers could go in the current square.'),
             self.show_hint_cb),
            ('AutofillCurrentSquare', gtk.STOCK_APPLY, _('_Fill'), '<Control>f',
             _('Automatically fill in the current square if possible.'),
             self.auto_fill_current_square_cb),
            ('Autofill', gtk.STOCK_REFRESH, _('Fill _all squares'), '<Control>a',
             _('Automatically fill in all squares for which there is only one valid value.'),
             self.auto_fill_cb),
            ('FullScreen', gtk.STOCK_FULLSCREEN, None,
             'F11', None, self.full_screen_cb),
            ('Generator', None, _('_Generate new puzzles'), None, _('Generate new puzzles.'),
              self.generate_puzzle_gui, ),
            ('PuzzleInfo', gtk.STOCK_ABOUT, _('Puzzle _Statistics'),
             None, _('Show statistics about current puzzle'),
             self.show_info_cb),
            ('Help', None, _('_Help'),
             None, None, None),
            ('About', gtk.STOCK_ABOUT, None,
             None, None, self.show_about),
            ('ShowHelp', gtk.STOCK_HELP, _('_Contents'),
             'F1', None, self.show_help),
            ])
        self.main_actions.add_toggle_actions([
            ('AlwaysShowPossible',
             None,
             _('_Always show hint'),
             None,
             _('Always show possible numbers in a square'),
             self.auto_hint_cb),
            ('ShowImpossibleImplications',
             None,
             _('Warn about _unfillable squares'),
             None,
             _('Warn about squares made unfillable by a move'),
             self.impossible_implication_cb),
            ('Tracker', 'tracks', _('_Track additions'),
             '<Control>T',
             _('Mark new additions in a separate color so you can keep track of them.'),
             self.tracker_toggle_cb, False),
            ('ToggleToolbar', None, _('Show _Toolbar'), None, None, self.toggle_toolbar_cb, True),
            ('ToggleHighlight', gtk.STOCK_SELECT_COLOR, _('_Highlighter'),
             None, _('Highlight the current row, column and box'), self.toggle_highlight_cb, False),
            ('BackgroundGenerator', None, _('Generate new puzzles _while you play'),
             None,
             _('Generate new puzzles in the background while you play. This will automatically pause when the game goes into the background.'),
             self.toggle_generator_cb, True),
            ])

        self.edit_actions = gtk.ActionGroup('EditActions')
        self.edit_actions.add_actions(
            [('Edit', None, _('_Edit')),
             ('Undo', gtk.STOCK_UNDO, _('_Undo'), '<Control>z', _('Undo last action'), self.stop_dancer),
             ('Redo', gtk.STOCK_REDO, _('_Redo'), '<Shift><Control>z', _('Redo last action')),
             ('Clear', gtk.STOCK_CLEAR, _('_Clear'), '<Control>b', _("Clear entries you've filled in"), self.clear_cb),
             ('ClearNotes', None, _('Clear _Notes'), None, _("Clear notes and hints"), self.clear_notes_cb),
             ])
        self.uimanager.insert_action_group(self.main_actions, 0)
        self.uimanager.insert_action_group(self.edit_actions, 0)
        self.uimanager.add_ui_from_string(self.ui)

    def setup_undo (self):
        self.cleared = [] # used for Undo memory
        self.cleared_notes = [] # used for Undo memory
        # Set up our UNDO stuff
        undo_widg = self.edit_actions.get_action('Undo')
        redo_widg = self.edit_actions.get_action('Redo')
        self.history = Undo.UndoHistoryList(undo_widg, redo_widg)
        for entry in self.gsd.__entries__.values():
            Undo.UndoableGenericWidget(entry, self.history,
                                       set_method = 'set_value_from_undo',
                                       pre_change_signal = 'value-about-to-change'
                                       )
            Undo.UndoableGenericWidget(entry, self.history,
                                       set_method = 'set_notes',
                                       get_method = 'get_note_text',
                                       signal = 'notes-changed',
                                       pre_change_signal = 'value-about-to-change',
                                       )

    def setup_color (self):
        # setup background colors
        bgcol = self.mateconf['bg_color']
        if bgcol != '':
            self.gsd.set_bg_color(bgcol)

    def setup_autosave (self):
        gobject.timeout_add_seconds(self.mateconf['auto_save_interval'] or 60, # in seconds...
                            self.autosave)

    def setup_main_boxes (self):
        self.vb = gtk.VBox()
        # Add menu bar and toolbar...
        mb = self.uimanager.get_widget('/MenuBar')
        mb.show()
        self.vb.pack_start(mb, fill = False, expand = False)
        self.tb = self.uimanager.get_widget('/Toolbar')
        self.vb.pack_start(self.tb, fill = False, expand = False)
        self.main_area = gtk.HBox()
        self.swallower = dialog_swallower.SwappableArea(self.main_area)
        self.swallower.show()
        self.vb.pack_start(self.swallower, True, padding = 12)
        self.main_area.pack_start(self.gsd, padding = 6)
        self.main_actions.set_visible(True)
        self.game_box = gtk.VBox()
        self.main_area.show()
        self.vb.show()
        self.game_box.show()
        self.main_area.pack_start(self.game_box, False, padding = 12)
        self.statusbar = gtk.Statusbar()
        self.statusbar.show()
        self.vb.pack_end(self.statusbar, fill = False, expand = False)
        self.w.add(self.vb)

    def setup_toggles (self):
        # sync up toggles with mateconf values...
        map(lambda tpl: self.mateconf_wrap_toggle(*tpl),
            [('always_show_hints',
              self.main_actions.get_action('AlwaysShowPossible')),
             ('show_impossible_implications',
              self.main_actions.get_action('ShowImpossibleImplications')),
             ('generate_puzzles_in_background',
              self.main_actions.get_action('BackgroundGenerator')),
             ('show_toolbar',
              self.main_actions.get_action('ToggleToolbar')),
             ('highlight',
              self.main_actions.get_action('ToggleHighlight')),
             ('show_tracker',
              self.main_actions.get_action('Tracker')),
             ])

    @simple_debug
    def start_worker_thread (self, *args):
        n_new_puzzles = self.sudoku_maker.n_puzzles(new = True)
        try:
            if n_new_puzzles < self.mateconf['minimum_number_of_new_puzzles']:
                self.worker = threading.Thread(target = lambda *args: self.sudoku_maker.work(limit = 5))
                self.worker_connections = [
                    self.timer.connect('timing-started', self.sudoku_maker.resume),
                    self.timer.connect('timing-stopped', self.sudoku_maker.pause)
                    ]
                self.worker.start()
        except mateconf_wrapper.MateConfError:
            pass # assume we have enough new puzzles
        return True

    @simple_debug
    def stop_worker_thread (self, *args):
        if hasattr(self, 'worker'):
            self.sudoku_maker.stop()
            for c in self.worker_connections:
                self.timer.disconnect(c)

    def stop_dancer (self, *args):
        if hasattr(self, 'dancer'):
            self.dancer.stop_dancing()
            delattr(self, 'dancer')

    @simple_debug
    def you_win_callback (self, grid):
        if hasattr(self, 'dancer'):
            return
        self.won = True
        # increase difficulty for next time.
        self.mateconf['difficulty'] = self.mateconf['difficulty'] + 0.1
        self.timer.finish_timing()
        self.sudoku_tracker.finish_game(self)
        sublabel = _("You completed the puzzle in %(totalTime)s (%(activeTime)s active)") % {'totalTime': self.timer.total_time_string(),
        'activeTime': self.timer.active_time_string()
                }
        sublabel += "\n"
        sublabel += ngettext("You got %(n)s hint", "You got %(n)s hints", self.gsd.hints) % {'n':self.gsd.hints}
        sublabel += "\n"
        if self.gsd.impossible_hints:
            sublabel += ngettext("You had %(n)s impossibility pointed out.",
                                 "You had %(n)s impossibilities pointed out.",
                                 self.gsd.impossible_hints) % {'n':self.gsd.impossible_hints}
            sublabel += "\n"
        if self.gsd.auto_fills:
            sublabel += ngettext("You used the auto-fill %(n)s time",
                                 "You used the auto-fill %(n)s times",
                                 self.gsd.auto_fills) % {'n':self.gsd.auto_fills}
        import dancer
        self.dancer = dancer.GridDancer(self.gsd)
        self.dancer.start_dancing()
        dialog_extras.show_message(_("You win!"), label = _("You win!"),
                                   sublabel = sublabel
                                   )

    @simple_debug
    def initialize_prefs (self):
        for k, v in self.initial_prefs.items():
            try:
                self.mateconf[k]
            except:
                self.mateconf[k] = v

    @simple_debug
    @inactivate_new_game_etc
    def new_cb (self, *args):
        if (self.gsd.grid and self.gsd.grid.is_changed() and not self.won):
            try:
                if dialog_extras.getBoolean(
                    label = _("Save this game before starting new one?"),
                    custom_yes = _("_Save game for later"),
                    custom_no = _("_Abandon game"),
                    ):
                    self.save_game()
                else:
                    self.sudoku_tracker.abandon_game(self)
            except dialog_extras.UserCancelledError:
                # User cancelled new game
                return
        self.do_stop()
        self.select_game()

    @simple_debug
    def stop_game (self):
        if (self.gsd.grid
            and self.gsd.grid.is_changed()
            and (not self.won)):
            try:
                if dialog_extras.getBoolean(label = _("Save game before closing?")):
                    self.save_game(self)
            except dialog_extras.UserCancelledError:
                return
            self.do_stop()

    def do_stop (self):
        self.stop_dancer()
        self.gsd.grid = None
        self.tracker_ui.reset()
        self.history.clear()
        self.won = False

    @simple_debug
    def resize_cb (self, widget, event):
        self.mateconf['width'] = event.width
        self.mateconf['height'] = event.height

    @simple_debug
    def quit_cb (self, *args):
        self.w.hide()
        if (self.gsd.grid
            and self.gsd.grid.is_changed()
            and (not self.won)):
            self.save_game(self)
        if gtk.main_level() > 1:
            # If we are in an embedded mainloop, that means that one
            # of our "swallowed" dialogs is active, in which case we
            # have to quit that mainloop before we can quit
            # properly.
            if self.swallower.running:
                d = self.swallower.running
                d.response(gtk.RESPONSE_DELETE_EVENT)
            gtk.main_quit() # Quit the embedded mainloop
            gobject.idle_add(self.quit_cb, 100) # Call ourselves again
                                               # to quit the main
                                               # mainloop
            return
        # make sure we really go away before doing our saving --
        # otherwise we appear sluggish.
        while gtk.events_pending():
            gtk.main_iteration()
        self.stop_worker_thread()
        # allow KeyboardInterrupts, which calls quit_cb outside the main loop
        try:
            gtk.main_quit()
        except RuntimeError:
            pass

    @simple_debug
    def save_game (self, *args):
        self.sudoku_tracker.save_game(self)

    def full_screen_cb (self, *args):
        if self.is_fullscreen:
            self.w.unfullscreen()
            self.is_fullscreen = False
        else:
            self.w.fullscreen()
            self.is_fullscreen = True

    @simple_debug
    def clear_cb (self, *args):
        clearer = Undo.UndoableObject(
            self.do_clear, #action
            self.undo_clear, #inverse
            self.history #history
            )
        clearer.perform()

    # add a check to stop the dancer if she is dancing
    def do_clear (self, *args):
        self.cleared.append(self.gsd.reset_grid())
        self.stop_dancer()

    # add a check for finish in the undo to clear
    def undo_clear (self, *args):
        for entry in self.cleared.pop():
            self.gsd.add_value(*entry)
        if self.gsd.grid.check_for_completeness():
            self.gsd.emit('puzzle-finished')

    def clear_notes_cb (self, *args):
        clearer = Undo.UndoableObject(
            lambda *args: self.cleared_notes.append(self.gsd.clear_notes()), #action
            # clear_notes returns a list of tuples indicating the cleared notes...
            # (x,y,(top,bottom)) -- this is what we need for undoing
            lambda *args: [self.gsd.__entries__[t[0], t[1]].set_notes(t[2]) for t in self.cleared_notes.pop()], #inverse
            self.history
            )
        clearer.perform()

    @simple_debug
    def show_hint_cb (self, *args):
        self.gsd.show_hint()

    @simple_debug
    def auto_hint_cb (self, action):
        if action.get_active():
            self.gsd.always_show_hints = True
            self.gsd.update_all_hints()
        else:
            self.gsd.always_show_hints = False
            self.gsd.clear_hints()

    @simple_debug
    def impossible_implication_cb (self, action):
        if action.get_active():
            self.gsd.show_impossible_implications = True
        else:
            self.gsd.show_impossible_implications = False

    @simple_debug
    def auto_fill_cb (self, *args):
        if not hasattr(self, 'autofilled'):
            self.autofilled = []
        if not hasattr(self, 'autofiller'):
            self.autofiller = Undo.UndoableObject(
                self.do_auto_fill,
                self.undo_auto_fill,
                self.history
                )
        self.autofiller.perform()

    def do_auto_fill (self, *args):
        self.autofilled.append(self.gsd.auto_fill())
        if self.mateconf['always_show_hints']:
            self.gsd.update_all_hints()

    def undo_auto_fill (self, *args):
        for entry in self.autofilled.pop():
            self.gsd.remove(entry[0], entry[1], do_removal = True)
        if self.mateconf['always_show_hints']:
            self.gsd.update_all_hints()

    @simple_debug
    def auto_fill_current_square_cb (self, *args):
        self.gsd.auto_fill_current_entry()

    @simple_debug
    def setup_tracker_interface (self):
        self.trackers = {}
        self.tracker_ui = TrackerBox(self)
        self.tracker_ui.show_all()
        self.tracker_ui.hide()
        self.game_box.add(self.tracker_ui)

    @simple_debug
    def tracker_toggle_cb (self, widg):
        if widg.get_active():
            self.tracker_ui.show_all()
        else:
            self.tracker_ui.hide()

    @simple_debug
    def toggle_toolbar_cb (self, widg):
        if widg.get_active():
            self.tb.show()
        else:
            self.tb.hide()

    def set_statusbar_value (self, status):
        if not hasattr(self, 'sbid'):
            self.sbid = self.statusbar.get_context_id('game_info')
        self.statusbar.pop(self.sbid)
        self.statusbar.push(self.sbid, status)

    def update_statusbar (self, *args):
        if not self.gsd.grid:
            self.set_statusbar_value(" ")
            return True

        puzzle = self.gsd.grid.virgin.to_string()
        puzzle_diff = self.sudoku_maker.get_difficulty(puzzle)

        tot_string = _("Playing %(difficulty)s puzzle.") % {'difficulty':puzzle_diff.value_string()}
        tot_string += " " + "(%1.2f)" % puzzle_diff.value

        self.set_statusbar_value(tot_string)
        return True

    def toggle_highlight_cb (self, widg):
        if widg.get_active():
            self.gsd.toggle_highlight(True)
        else:
            self.gsd.toggle_highlight(False)

    @simple_debug
    def show_info_cb (self, *args):
        if not self.gsd.grid:
            dialog_extras.show_message(parent = self.w,
                                       title = _("Puzzle Information"),
                                       label = _("There is no current puzzle.")
                                       )
            return
        puzzle = self.gsd.grid.virgin.to_string()
        diff = self.sudoku_maker.get_difficulty(puzzle)
        information = _("Calculated difficulty: ")
        information += diff.value_string()
        information += " (%1.2f)" % diff.value
        information += "\n"
        information += _("Number of moves instantly fillable by elimination: ")
        information += str(int(diff.instant_elimination_fillable))
        information += "\n"
        information += _("Number of moves instantly fillable by filling: ")
        information += str(int(diff.instant_fill_fillable))
        information += "\n"
        information += _("Amount of trial-and-error required to solve: ")
        information += str(len(diff.guesses))
        dialog_extras.show_message(parent = self.w,
                                   title = _("Puzzle Statistics"),
                                   label = _("Puzzle Statistics"),
                                   sublabel = information)

    @simple_debug
    def toggle_generator_cb (self, toggle):
        if toggle.get_active():
            self.start_worker_thread()
        else:
            self.stop_worker_thread()

    @simple_debug
    def autosave (self):
        # this is called on a regular loop and will autosave if we
        # have reason to...
        if self.gsd.grid and self.gsd.grid.is_changed() and not self.won:
            self.sudoku_tracker.save_game(self)
        return True

    @simple_debug
    def show_about (self, *args):
        about = gtk.AboutDialog()
        about.set_transient_for(self.w)
        about.set_name(APPNAME)
        about.set_version(VERSION)
        about.set_copyright(COPYRIGHT)
        about.set_license(LICENSE[0] + '\n\n' + LICENSE[1] + '\n\n'  + LICENSE[2])
        about.set_wrap_license(True)
        about.set_comments(DESCRIPTION)
        about.set_authors(AUTHORS)
        about.set_website(WEBSITE)
        about.set_website_label(WEBSITE_LABEL)
        about.set_logo_icon_name("mate-sudoku")
        about.set_translator_credits(_("translator-credits"))
        about.connect("response", lambda d, r: d.destroy())
        about.show()

    @simple_debug
    def show_help (self, *args):
        try:
            gtk.show_uri(self.w.get_screen(), "ghelp:mate-sudoku", gtk.get_current_event_time())
        except gobject.GError, error:
            # FIXME: This should create a pop-up dialog
            print _('Unable to display help: %s') % str(error)

    @simple_debug
    def print_game (self, *args):
        printing.print_sudokus([self.gsd], self.w)

    @simple_debug
    def print_multiple_games (self, *args):
        gp = printing.GamePrinter(self.sudoku_maker, self.mateconf)
        gp.run_dialog()

    @simple_debug
    def generate_puzzle_gui (self, *args):
        sudoku_generator_gui.GameGenerator(self, self.mateconf)

class TrackerBox (gtk.VBox):

    @simple_debug
    def __init__ (self, main_ui):

        gtk.VBox.__init__(self)
        self.builder = gtk.Builder()
        self.builder.set_translation_domain(DOMAIN)
        self.builder.add_from_file(os.path.join(UI_DIR, 'tracker.ui'))
        self.main_ui = main_ui
        self.vb = self.builder.get_object('vbox1')
        self.vb.unparent()
        self.pack_start(self.vb, expand = True, fill = True)
        self.setup_actions()
        self.setup_tree()
        self.show_all()

    @simple_debug
    def reset (self):

        for tree in self.tracker_model:
            if tree[0] > -1:
                self.tracker_model.remove(tree.iter)

    @simple_debug
    def setup_tree (self):
        self.tracker_tree = self.builder.get_object('treeview1')
        self.tracker_model = gtk.ListStore(int, gtk.gdk.Pixbuf, str)
        self.tracker_tree.set_model(self.tracker_model)
        col1 = gtk.TreeViewColumn("", gtk.CellRendererPixbuf(), pixbuf = 1)
        col2 = gtk.TreeViewColumn("", gtk.CellRendererText(), text = 2)
        self.tracker_tree.append_column(col2)
        self.tracker_tree.append_column(col1)
        # Our initial row...
        self.tracker_model.append([-1, None, _('No Tracker')])
        self.tracker_tree.get_selection().connect('changed', self.selection_changed_cb)

    @simple_debug
    def setup_actions (self):
        self.tracker_actions = gtk.ActionGroup('tracker_actions')
        self.tracker_actions.add_actions(
            [('Clear',
              gtk.STOCK_CLEAR,
              _('_Clear Tracker'),
              None, _('Clear all moves tracked by selected tracker.'),
              self.clear_cb
              ),
             ('Keep', None,
              _('_Clear Others'),
              None,
              _('Clear all moves not tracked by selected tracker.'),
              self.keep_cb),
             ]
            )
        for action, widget_name in [('Clear', 'ClearTrackerButton'),
                                   ('Keep', 'KeepTrackerButton'),
                                   ]:
            a = self.tracker_actions.get_action(action)
            a.connect_proxy(self.builder.get_object(widget_name))
        self.builder.get_object('AddTrackerButton').connect('clicked',
                                                          self.add_tracker)
        # Default to insensitive (they only become sensitive once a tracker is added)
        self.tracker_actions.set_sensitive(False)

    @simple_debug
    def add_tracker (self, *args):
        tracker_id = self.main_ui.gsd.create_tracker()
        pixbuf = self.pixbuf_transform_color(
            STOCK_PIXBUFS['tracks'],
            self.main_ui.gsd.get_tracker_color(tracker_id),
            )
        # select our new tracker
        self.tracker_tree.get_selection().select_iter(
            self.tracker_model.append([tracker_id,
                                  pixbuf,
                                  _("Tracker %s") % (tracker_id + 1)]
                                  )
            )

    @simple_debug
    def pixbuf_transform_color (self, pixbuf, color):
        """Return new pixbuf with color changed to color"""
        pixbuf_str = pixbuf.get_pixels()
        pixbuf_str_new = ""

        for alpha in pixbuf_str[3::4]:
            pixbuf_str_new += chr(int(color[0]*255))
            pixbuf_str_new += chr(int(color[1]*255))
            pixbuf_str_new += chr(int(color[2]*255))
            pixbuf_str_new += alpha

        return gtk.gdk.pixbuf_new_from_data(pixbuf_str_new, gtk.gdk.COLORSPACE_RGB, True, 8,
                                            pixbuf.get_width(), pixbuf.get_height(), pixbuf.get_rowstride())

    @simple_debug
    def select_tracker (self, tracker_id):
        for row in self.tracker_model:
            if row[0] == tracker_id:
                self.tracker_tree.get_selection().select_iter(row.iter)

    @simple_debug
    def selection_changed_cb (self, selection):
        mod, itr = selection.get_selected()
        if itr:
            selected_tracker_id = mod.get_value(itr, 0)
        else:
            selected_tracker_id = -1
        # This should be cheap since we don't expect many trackers...
        # We cycle through each row and toggle it off if it's not
        # selected; on if it is selected
        for row in self.tracker_model:


#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 F5 Networks Inc.
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = r'''
---
module: bigip_policy
short_description: Manage general policy configuration on a BIG-IP
description:
  - Manages general policy configuration on a BIG-IP. This module is best
    used in conjunction with the C(bigip_policy_rule) module. This module
    can handle general configuration like setting the draft state of the policy,
    the description, and things unrelated to the policy rules themselves.
    It is also the first module that should be used when creating rules as
    the C(bigip_policy_rule) module requires a policy parameter.
version_added: "2.5"
options:
  description:
    description:
      - The description to attach to the policy.
      - This parameter is only supported on versions of BIG-IP >= 12.1.0. On earlier
        versions it will simply be ignored.
  name:
    description:
      - The name of the policy to create.
    required: True
  state:
    description:
      - When C(state) is C(present), ensures that the policy exists and is
        published. When C(state) is C(absent), ensures that the policy is removed,
        even if it is currently drafted.
      - When C(state) is C(draft), ensures that the policy exists and is drafted.
        When modifying rules, it is required that policies first be in a draft.
      - Drafting is only supported on versions of BIG-IP >= 12.1.0. On versions
        prior to that, specifying a C(state) of C(draft) will raise an error.
    choices:
      - present
      - absent
      - draft
  strategy:
    description:
      - Specifies the method to determine which actions get executed in the
        case where there are multiple rules that match. When creating new
        policies, the default is C(first).
      - This module does not allow you to specify the C(best) strategy to use.
        It will choose the system default (C(/Common/best-match)) for you instead.
    choices:
      - first
      - all
      - best
  rules:
    description:
      - Specifies a list of rules that you want associated with this policy.
        The order of this list is the order they will be evaluated by BIG-IP.
        If the specified rules do not exist (for example when creating a new
        policy) then they will be created.
      - The C(conditions) for a default rule are C(all).
      - The C(actions) for a default rule are C(ignore).
      - The C(bigip_policy_rule) module can be used to create and edit existing
        and new rules.
  partition:
    description:
      - Device partition to manage resources on.
    default: Common
extends_documentation_fragment: f5
author:
  - Tim Rupp (@caphrim007)
'''

EXAMPLES = r'''
- name: Create policy which is immediately published
  bigip_policy:
    name: Policy-Foo
    state: present
  delegate_to: localhost

- name: Add a rule to the new policy - Immediately published
  bigip_policy_rule:
    policy: Policy-Foo
    name: ABC
    conditions:
      - type: http_uri
        path_starts_with:
          - /ABC
          - foo
          - bar
        path_ends_with:
          - baz
    actions:
      - forward: yes
        select: yes
        pool: pool-svrs

- name: Add multiple rules to the new policy - Added in the order they are specified
  bigip_policy_rule:
    policy: Policy-Foo
    name: "{{ item.name }}"
    conditions: "{{ item.conditions }}"
    actions: "{{ item.actions }}"
  with_items:
    - name: rule1
      actions:
        - type: forward
          pool: pool-svrs
      conditions:
        - type: http_uri
          path_starts_with: /euro
    - name: HomePage
      actions:
        - type: forward
          pool: pool-svrs
      conditions:
        - type: http_uri
          path_starts_with: /HomePage/

- name: Create policy specify default rules - Immediately published
  bigip_policy:
    name: Policy-Bar
    state: present
    rules:
      - rule1
      - rule2
      - rule3

- name: Create policy specify default rules - Left in a draft
  bigip_policy:
    name: Policy-Baz
    state: draft
    rules:
      - rule1
      - rule2
      - rule3
'''

RETURN = r'''
strategy:
  description: The new strategy set on the policy.
  returned: changed and success
  type: int
  sample: first-match
description:
  description:
    - The new description of the policy.
    - This value is only returned for BIG-IP devices >= 12.1.0.
  returned: changed and success
  type: string
  sample: This is my description
rules:
  description: List of the rules, and their order, applied to the policy.
  returned: changed and success
  type: list
  sample: ['/Common/rule1', '/Common/rule2']
'''

import re

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from distutils.version import LooseVersion

HAS_DEVEL_IMPORTS = False

try:
    # Sideband repository used for dev
    from library.module_utils.network.f5.bigip import HAS_F5SDK
    from library.module_utils.network.f5.bigip import F5Client
    from library.module_utils.network.f5.common import F5ModuleError
    from library.module_utils.network.f5.common import AnsibleF5Parameters
    from library.module_utils.network.f5.common import cleanup_tokens
    from library.module_utils.network.f5.common import fqdn_name
    from library.module_utils.network.f5.common import f5_argument_spec
    try:
        from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
    except ImportError:
        HAS_F5SDK = False
    HAS_DEVEL_IMPORTS = True
except ImportError:
    # Upstream Ansible
    from ansible.module_utils.network.f5.bigip import HAS_F5SDK
    from ansible.module_utils.network.f5.bigip import F5Client
    from ansible.module_utils.network.f5.common import F5ModuleError
    from ansible.module_utils.network.f5.common import AnsibleF5Parameters
    from ansible.module_utils.network.f5.common import cleanup_tokens
    from ansible.module_utils.network.f5.common import fqdn_name
    from ansible.module_utils.network.f5.common import f5_argument_spec
    try:
        from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
    except ImportError:
        HAS_F5SDK = False

try:
    from f5.sdk_exception import NonExtantPolicyRule
except ImportError:
    HAS_F5SDK = False

class Parameters(AnsibleF5Parameters):
    def to_return(self):
        result = {}
        for returnable in self.returnables:
            result[returnable] = getattr(self, returnable)
        result = self._filter_params(result)
        return result

    @property
    def strategy(self):
        if self._values['strategy'] is None:
            return None

        # Look for 'first' from Ansible or REST
        elif self._values['strategy'] == 'first':
            return self._get_builtin_strategy('first')
        elif 'first-match' in self._values['strategy']:
            return str(self._values['strategy'])

        # Look for 'all' from Ansible or REST
        elif self._values['strategy'] == 'all':
            return self._get_builtin_strategy('all')
        elif 'all-match' in self._values['strategy']:
            return str(self._values['strategy'])

        else:
            # Look for 'best' from Ansible or REST
            if self._values['strategy'] == 'best':
                return self._get_builtin_strategy('best')
            elif 'best-match' in self._values['strategy']:
                return str(self._values['strategy'])
            else:
                # These are custom strategies. The strategy may include the
                # partition, but if it does not, then we add the partition
                # that is provided to the module.
                return self._get_custom_strategy_name()

    def _get_builtin_strategy(self, strategy):
        return '/Common/{0}-match'.format(strategy)

    def _get_custom_strategy_name(self):
        strategy = self._values['strategy']
        if re.match(r'(\/[a-zA-Z_0-9.-]+){2}', strategy):
            return strategy
        elif re.match(r'[a-zA-Z_0-9.-]+', strategy):
            return '/{0}/{1}'.format(self.partition, strategy)
        else:
            raise F5ModuleError(
                "The provided strategy name is invalid!"
            )

    @property
    def rules(self):
        if self._values['rules'] is None:
            return None
        # In case rule values are unicode (as they may be coming from the API
        result = [str(x) for x in self._values['rules']]
        return result

class SimpleParameters(Parameters):
    api_attributes = [
        'strategy'
    ]

    updatables = [
        'strategy', 'rules'
    ]

    returnables = [
        'strategy', 'rules'
    ]

class ComplexParameters(Parameters):
    api_attributes = [
        'strategy', 'description'
    ]

    updatables = [
        'strategy', 'description', 'rules'
    ]

    returnables = [
        'strategy', 'description', 'rules'
    ]

class BaseManager(object):
    def __init__(self, *args, **kwargs):
        self.module = kwargs.get('module', None)
        self.client = kwargs.get('client', None)
        self.have = None
        self.want = Parameters(params=self.module.params)

    def _announce_deprecations(self):
        warnings = []
        if self.want:
            warnings += self.want._values.get('__deprecated', [])
        if self.have:
            warnings += self.have._values.get('__deprecated', [])
        for warning in warnings:
            self.module.deprecate(
                msg=warning['msg'],
                version=warning['version']
            )

    def _announce_warnings(self):
        warnings = []
        if self.want:
            warnings += self.want._values.get('__warning', [])
        if self.have:
            warnings += self.have._values.get('__warning', [])
        for warning in warnings:
            self.module.warn(warning['msg'])

    def present(self):
        if self.exists():
            return self.update()
        else:
            return self.create()

    def should_update(self):
        result = self._update_changed_options()
        if result:
            return True
        return False

    def _validate_creation_parameters(self):
        if self.want.strategy is None:
            self.want.update(dict(strategy='first'))

    def _get_rule_names(self, resource):
        rules = resource.rules_s.get_collection()
        rules.sort(key=lambda x: x.ordinal)
        result = [x.name for x in rules]
        return result

    def _upsert_policy_rules_on_device(self, policy):
        rules = self.changes.rules
        if rules is None:
            rules = []
        for idx, rule in enumerate(rules):
            try:
                resource = policy.rules_s.rules.load(name=rule)
                if int(resource.ordinal) != idx:
                    resource.modify(ordinal=idx)
            except NonExtantPolicyRule:
                policy.rules_s.rules.create(name=rule, ordinal=idx)
        self._remove_rule_difference(rules, policy)

    def _remove_rule_difference(self, rules, policy=None):
        if not rules or not self.have.rules:
            return
        have_rules = set(self.have.rules)
        want_rules = set(rules)
        removable = have_rules.difference(want_rules)
        for remove in removable:
            resource = policy.rules_s.rules.load(name=remove)
            resource.delete()

class SimpleManager(BaseManager):
    def __init__(self, *args, **kwargs):
        super(SimpleManager, self).__init__(**kwargs)
        self.want = SimpleParameters(params=self.module.params)
        self.have = SimpleParameters()
        self.changes = SimpleChanges()

    def _set_changed_options(self):
        changed = {}
        for key in SimpleParameters.returnables:
            if getattr(self.want, key) is not None:
                changed[key] = getattr(self.want, key)
        if changed:
            self.changes = SimpleChanges(params=changed)

    def _update_changed_options(self):


# Copyright 2015 Matthew Rogge and Michael Uttmark
#
# This file is part of Retr3d.
#
# Retr3d is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Retr3d 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Retr3d.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import division  # allows floating point division from integers
import math

import os
import sys
import platform
import subprocess

import globalVars as gv

try:
    import utilityFunctions as uf
except StandardError as e:
    msg = "Unable to import FreeCAD, please check your configuration."
    if not platform.system() == 'Windows' and os.getcwd() == os.path.dirname(os.path.abspath(__file__)):
        print '\033[1m\033[31m' + msg + '\x1b[0m'
    else:
        print msg
    print str(e)

source = os.path.basename(__file__)

class versionError(Exception):
    def __init__(self):
        raise StandardError

def makePrinter():
    if platform.system() == 'Windows':
        subprocess.call('cls', shell=True)
    else:
        subprocess.call('clear', shell=True)
    uf.header("______     _       _____     _           _____     __      _____ ", False)
    uf.header("| ___ \   | |     |____ |   | |         |  _  |   /  |    |  _  |", False)
    uf.header("| |_/ /___| |_ _ __   / / __| |  __   __| |/' |   `| |    | |/' |", False)
    uf.header("|    // _ \ __| '__|  \ \/ _` |  \ \ / /|  /| |    | |    |  /| |", False)
    uf.header("| |\ \  __/ |_| | .___/ / (_| |   \ V / \ |_/ / _ _| |_ _ \ |_/ /", False)
    uf.header("\_| \_\___|\__|_| \____/ \__,_|    \_/   \___/ (_)\___/(_) \___/ ", False)
    uf.bold("Version: 0.1.0 ", False)
    uf.bold("If you encounter any issues, please let us know at https://github.com/maaphoo/retr3d/issues", False)
    print ""
    # import FreeCAD modules
    try:
        import FreeCAD as App
    except ImportError as e:
        uf.critical("Failure to import FreeCAD, check your configuration file.", 'Failure to import FreeCAD: ' + str(e),
                    gv.level, source)

    import FreeCADGui as Gui

    if platform.system() == 'Windows':
        if (App.Version()[1] > '14'):
            try:
                import FreeCAD#
            except AttributeError:
                pass
            finally:
                uf.info("FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2],
                        "FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2], gv.level, source)

    if platform.system() == 'Darwin':  # OSX
        if (App.Version()[1] >= '14'):
            try:
                import FreeCAD#
                FreeCAD#.showMainWindow()
            except AttributeError:
                pass
            finally:
                uf.info("FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2],
                        "FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2], gv.level, source)

    if platform.system() == 'Linux':
        if (App.Version()[1] < '14'):
            uf.warning(
                "Retr3d has not been tested on FreeCAD version .13 and below. You are using version 0." + App.Version()[
                    1] + ". If problems ensue, please upgrade to versions .14 or .16.",
                "Untested FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2], gv.level, source)
        if (App.Version()[1] == '14'):
            if not (os.getcwd() == os.path.dirname(os.path.abspath(__file__))):
                uf.info("FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2],
                        "FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2], gv.level, source)
            else:
                uf.critical(
                    "Retr3d on Linux is not compatible with FreeCAD version .14 for command line usage. Please upgrade to .16 to continue",
                    "VersionError: FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2], gv.level,
                    source)
                raise versionError
        if (App.Version()[1] == '15'):
            uf.critical(
                "Retr3d on Linux is not compatible with FreeCAD version .15. Please upgrade to .16 or downgrade to .14 to continue.",
                "VersionError: FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2], gv.level,
                source)
            raise versionError
        if (App.Version()[1] == '16'):
            try:
                import FreeCAD#
                FreeCAD#.showMainWindow()
            except AttributeError:
                pass
            finally:
                uf.info("FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2],
                        "FreeCAD Version 0." + App.Version()[1] + " Revision " + App.Version()[2], gv.level, source)

    # import Part modules
    import xRodBottom
    import xRodTop
    import yRodL
    import yRodR
    import zRodL
    import zRodR
    import xCarriage
    import xRodClamp
    import xEndZRodHolder
    import xEndMotorPlate
    import xEndIdlerPlate
    import zRodSupport
    import zMotorMount
    import printBedBushingHolder
    import printBedSupport
    import printBed
    import yBeltAnchor
    import yMotorMount
    import yRodSupport
    import extruderMountAngle
    import extruderMountPlate
    import verticalBars
    import crossBarTop
    import crossBarFrontTop
    import crossBarFrontBottom
    import crossBarBackTop
    import crossBarBackBottom
    import sideBarTopL
    import sideBarBottomL
    import sideBarBottomR
    import sideBarTopR
    import frameSpacers
    import extruderBarrel
    import nozzle
    import xEndstop
    import zEndstop
    import feet
    import leadScrewCoupler
    import plate
    import slic3r
    import zipup
    import draw
    import checklist
    import heatedbed
    import marlin

    # If any of the parameters have been changed, the includes must be reloaded
    # Normally, this would just be globalVariables because that is what would be changed,
    # But while the rest of the code is in development, the other modules will be reloaded too.
    # This will make testing easier.

    if gv.reloadClasses:
        reload(gv)
        reload(uf)
        reload(xCarriage)
        reload(xRodClamp)
        reload(xRodBottom)
        reload(xRodTop)
        reload(xEndMotorPlate)
        reload(xEndIdlerPlate)
        reload(xEndZRodHolder)
        reload(yRodL)
        reload(yRodR)
        reload(zRodL)
        reload(zRodR)
        reload(zRodSupport)
        reload(zMotorMount)
        reload(printBedBushingHolder)
        reload(printBedSupport)
        reload(printBed)
        reload(yBeltAnchor)
        reload(yMotorMount)
        reload(yRodSupport)
        reload(extruderMountAngle)
        reload(extruderMountPlate)
        reload(verticalBars)
        reload(crossBarTop)
        reload(crossBarFrontTop)
        reload(crossBarFrontBottom)
        reload(crossBarBackTop)
        reload(crossBarBackBottom)
        reload(sideBarTopL)
        reload(sideBarBottomL)
        reload(sideBarBottomR)
        reload(sideBarTopR)
        reload(frameSpacers)
        reload(extruderBarrel)
        reload(nozzle)
        reload(xEndstop)
        reload(zEndstop)
        reload(feet)
        reload(leadScrewCoupler)
        reload(plate)
        reload(slic3r)
        reload(zipup)
        reload(draw)
        reload(checklist)
        reload(heatedbed)
        reload(marlin)

    gv.reloadClasses = True

    # instantiate part objects
    xRodBottom = xRodBottom.XRodBottom()
    xRodTop = xRodTop.XRodTop()
    yRodL = yRodL.YRodL()
    yRodR = yRodR.YRodR()
    zRodL = zRodL.ZRodL()
    zRodR = zRodR.ZRodR()
    xCarriage = xCarriage.XCarriage()
    xRodClampL = xRodClamp.XRodClamp("xRodClampL", "Left")
    xRodClampR = xRodClamp.XRodClamp("xRodClampR", "Right")
    xEndZRodHolderL = xEndZRodHolder.XEndZRodHolder("xEndZRodHolderL", "Left")
    xEndZRodHolderR = xEndZRodHolder.XEndZRodHolder("xEndZRodHolderR", "Right")
    xEndMotorPlate = xEndMotorPlate.XEndMotorPlate()
    xEndIdlerPlate = xEndIdlerPlate.XEndIdlerPlate()
    zRodSupportR = zRodSupport.ZRodSupport(name="zRodSupportR", side="Right")
    zRodSupportL = zRodSupport.ZRodSupport(name="zRodSupportL", side="Left")
    zMotorMount = zMotorMount.ZMotorMount()
    PBBHR = printBedBushingHolder.PrintBedBushingHolder("printBedBushingHolderR", "Right")
    PBBHL = printBedBushingHolder.PrintBedBushingHolder("printBedBushingHolderL", "Left")
    printBedSupport = printBedSupport.PrintBedSupport()
    printBed = printBed.PrintBed()
    yBeltAnchor = yBeltAnchor.YBeltAnchor()
    yMotorMount = yMotorMount.YMotorMount()
    yRodSupportR = yRodSupport.YRodSupport("yRodSupportR", "Right")
    yRodSupportL = yRodSupport.YRodSupport("yRodSupportL", "Left")
    extruderMountAngle = extruderMountAngle.ExtruderMountAngle()
    extruderMountPlate = extruderMountPlate.ExtruderMountPlate()
    verticalBars = verticalBars.VerticalBars()
    crossBarTop = crossBarTop.CrossBarTop()
    crossBarFrontTop = crossBarFrontTop.CrossBarFrontTop()
    crossBarFrontBottom = crossBarFrontBottom.CrossBarFrontBottom()
    crossBarBackTop = crossBarBackTop.CrossBarBackTop()
    crossBarBackBottom = crossBarBackBottom.CrossBarBackBottom()
    sideBarTopL = sideBarTopL.SideBarTopL()
    sideBarBottomL = sideBarBottomL.SideBarBottomL()
    sideBarBottomR = sideBarBottomR.SideBarBottomR()
    sideBarTopR = sideBarTopR.SideBarTopR()
    frameSpacers = frameSpacers.FrameSpacers()
    extruderBarrel = extruderBarrel.ExtruderBarrel()
    nozzle = nozzle.Nozzle()
    xEndstop = xEndstop.XEndstop()
    zEndstop = zEndstop.ZEndstop()
    feet = feet.Feet()
    leadScrewCoupler = leadScrewCoupler.LeadScrewCoupler()

    # convert standard nut sizes to mm
    for i in range(len(gv.standardNuts)):
        for j in range(len(gv.standardNuts[i])):
            gv.standardNuts[i][j] *= 25.4

    if gv.bushingNutSizesUsed == "Standard":
        gv.bushingNutTable = gv.standardNuts
    elif gv.bushingNutSizesUsed == "Metric":
        gv.bushingNutTable = gv.metricNuts
    elif gv.bushingNutSizesUsed == "StandardAndMetric":
        gv.bushingNutTable = gv.standardNuts + gv.metricNuts
        gv.bushingNutTable.sort(key=lambda x: x[0])

    # Determine printed mounting hole diameters
    gv.printedToFrameDia = uf.adjustHole(gv.mountToFrameDia)
    gv.printedToFrameHeadDia = uf.adjustHole(gv.mountToFrameHeadDia)
    gv.printedToPrintedDia = uf.adjustHole(gv.mountToPrintedDia)
    gv.printedToPrintedNutFaceToFace = uf.adjustHole(gv.mountToPrintedNutFaceToFace)

    # Select Bushing and Lead Screw nuts
    gv.xBushingNutBottom = uf.pickBushingNut(gv.xRodDiaBottom)
    gv.xBushingNutTop = uf.pickBushingNut(gv.xRodDiaTop)
    gv.yBushingNutR = uf.pickBushingNut(gv.yRodDiaR)
    gv.yBushingNutL = uf.pickBushingNut(gv.yRodDiaL)
    gv.zBushingNutR = uf.pickBushingNut(gv.zRodDiaR)
    gv.zBushingNutL = uf.pickBushingNut(gv.zRodDiaL)
    gv.leadScrewNut = uf.pickLeadScrewNut(gv.leadScrewDia)

    # Determine max rod diameters
    gv.xRodDiaMax = (gv.xRodDiaTop
                     if gv.xRodDiaTop
                        > gv.xRodDiaBottom
                     else gv.xRodDiaBottom)

    # Determine the max yRod diameter
    gv.yRodDiaMax = (gv.yRodDiaL
                     if gv.yRodDiaL
                        > gv.yRodDiaR
                     else gv.yRodDiaR)

    # Determine xBushingNutMaxThickness
    gv.xBushingNutMaxThickness = (gv.xBushingNutTop[3] if gv.xBushingNutTop[3] >
                                                          gv.xBushingNutBottom[3] else gv.xBushingNutBottom[3])

    # Determine zBushingNutMaxThickness
    gv.zBushingNutMaxThickness = (gv.zBushingNutR[3] if gv.zBushingNutR[3] >
                                                        gv.zBushingNutL[3] else gv.zBushingNutL[3])

    # Determine xCarriageMaxNutFaceToFace
    gv.xBushingNutMaxFacetoFace = (gv.xBushingNutTop[2] if gv.xBushingNutTop[2] >
                                                           gv.xBushingNutBottom[2] else gv.xBushingNutBottom[2])

    # Determine xCarriageBushingHolderOR
    gv.xCarriageBushingHolderOR = gv.xBushingNutMaxFacetoFace / math.cos(math.pi / 6) / 2 + gv.bushingNutPadding

    # Check/Determind xRodSpacing
    gv.xRodSpacing = (gv.xMotorMountPlateWidth
                      if gv.xMotorMountPlateWidth
                         > gv.xCarriageBushingHolderOR * 4 + gv.xMotorPulleyDia + gv.xBeltAnchorThickness
                      else gv.xCarriageBushingHolderOR * 4 + gv.xMotorPulleyDia + gv.xBeltAnchorThickness)

    # Determine yRodStandoff
    gv.yRodStandoff = gv.yRodDiaMax / 2 + gv.rodSupportNutTrapDepthMin + gv.rodSupportClampThickness

    # The yRodSupportNutTrapDepth is always the minimum nut trap depth
    yRodSupportNutTrapDepth = gv.rodSupportNutTrapDepthMin

    # Check to see if the yRodClamps work with available bolt lengths
    # If needed adjust the yRodStandoff for the next closest bolt length available.
    gv.yRodSupportClampBoltLength = (gv.yRodStandoff
                                     + gv.yRodDiaMax / 2
                                     + gv.clampThickness
                                     - gv.rodSupportNutTrapDepthMin
                                     + gv.clampNutThickness)

    for i in range(len(gv.clampBoltLengths)):
        if gv.yRodSupportClampBoltLength <= gv.clampBoltLengths[i]:
            gv.yRodSupportClampBoltLength = gv.clampBoltLengths[i]
            break
        elif i == len(gv.clampBoltLengths):
            try:
                raise Exception("No clamp bolt available is long enough for the yRodSupport")
            except Exception as e:
                import traceback
                uf.critical("No available clamp bolt is long enough for the yRodSupport",
                            'Part error: No available clamp bolt is long enough for the yRodSupport: \n\n' + str(
                                e) + '\n' + traceback.format_exc(limit=1), gv.level, os.path.basename(__file__))
                raise StandardError

    # Now redetermine yRodStandoff based on chosen yRodClampBoltLength
    gv.yRodStandoff = (gv.yRodSupportClampBoltLength
                       + gv.rodSupportNutTrapDepthMin
                       - gv.clampNutThickness
                       - gv.yRodDiaMax / 2
                       - gv.clampThickness)

    # Determine the maximum bushing nut face to face for the y axis bushings
    gv.PBBHMaxFaceToFace = (gv.yBushingNutL[2]
                            if gv.yBushingNutL[2]
                               > gv.yBushingNutR[2]
                            else gv.yBushingNutR[2])

    # Determine the PBBHStandoff. Must be large enough to acomodate the bushing holder itself,
    # and the yrod support. So take which ever is larger.
    gv.PBBHStandoff = (gv.yRodDiaMax / 2 + gv.clampThickness + gv.clampBoltHeadThickness + gv.yRodSupportClearance
                       if gv.yRodDiaMax / 2 + gv.clampThickness + gv.clampBoltHeadThickness + gv.yRodSupportClearance
                          > gv.PBBHMaxFaceToFace / 2 + gv.tabThickness
                       else gv.PBBHMaxFaceToFace / 2 + gv.tabThickness)

    # Determine yBeltAnchorHeight. Set to allow for clamping down to the surface of the y motor mount plate
    gv.yBeltAnchorHeight = (gv.yBeltAnchorBridgeThickness
                            + gv.yRodStandoff
                            + gv.PBBHStandoff
                            - gv.yMotorMountPlateThickness)

    # Determine xRodClampOverallThickness
    gv.xRodClampOverallThickness = 2 * gv.xRodClampThickness + gv.xRodDiaMax

    # determine the horizontal or vertical component of the shaftToMountHoleDist for the x motor
    gv.xMotorShaftToMountHoleDistX = math.pow(math.pow(gv.xEndShaftToMountHoleDist, 2) / 2, 0.5)

    # determine x motor width/length
    gv.xMotorBodyWidth = gv.xMotorShaftToMountHoleDistX * 2 + gv.xEndMotorMountHoleDia + 2 * gv.xEndMotorMountHolePadding

    # Determine the yMotor x component of the shaft to mount hole distance
    gv.yMotorShaftToMountHoleDistX = math.pow(math.pow(gv.yShaftToMountHoleDist, 2) / 2, 0.5)

    # Determine the y motor width/length
    gv.yMotorBodyWidth = (2 * gv.yMotorMountHolePadding +
                          gv.yMotorMountHoleDia +
                          2 * gv.yMotorShaftToMountHoleDistX)

    # Determine zShaftToMountHoleDistX
    gv.zShaftToMountHoleDistX = math.pow(math.pow(gv.zShaftToMountHoleDist, 2) / 2, 0.5)

    # Determine zMotorBodyWidth
    gv.zMotorBodyWidth = (2 * gv.zMotorMountHolePadding +
                          gv.zMotorMountHoleDia +
                          2 * gv.zShaftToMountHoleDistX)

    # Determine zMotor mount variables and zRodStandoff
    if gv.zMotorMountLocation == "Top":
        gv.zRodStandoff = gv.zMotorBodyWidth / 2
        gv.zMotorMountMountingFaceWidth = gv.frameHeight
        gv.zMotorMountLength = gv.zMotorMountMountingFaceWidth + gv.zRodStandoff + gv.zMotorBodyWidth / 2
        gv.zMotorMountEdgeToShaftHole = gv.zMotorMountMountingFaceWidth + gv.zRodStandoff
        gv.zRodZScrewDist = (gv.zRodZScrewDist
                             if gv.zRodZScrewDist
                                > (gv.frameWidth + gv.zMotorMountPlateWidth) / 2
                             else (gv.frameWidth + gv.zMotorMountPlateWidth) / 2)
    if gv.zMotorMountLocation == "Bottom":
        gv.zRodStandoff = gv.zMotorMountPlateWidth / 2
        gv.zMotorMountMountingFaceWidth = gv.frameWidth
        gv.zRodZScrewDist = (gv.zRodZScrewDist
                             if gv.zRodZScrewDist
                                > (gv.frameWidth + gv.zMotorBodyWidth + gv.zMotorBodyToFrameSpacing) / 2
                             else (gv.frameWidth + gv.zMotorBodyWidth + gv.zMotorBodyToFrameSpacing) / 2)
        gv.zMotorMountLength = gv.zMotorMountMountingFaceWidth / 2 + gv.zRodZScrewDist + gv.zMotorBodyWidth / 2
        gv.zMotorMountEdgeToShaftHole = gv.zMotorMountMountingFaceWidth / 2 + gv.zRodZScrewDist

    # Determine zBushingNutMaxFaceToFace
    gv.zBushingNutMaxFaceToFace = (gv.zBushingNutL[2]
                                   if gv.zBushingNutL[2]
                                      > gv.zBushingNutR[2]
                                   else gv.zBushingNutR[2])

    # Determine the max Nut face to face for the xEndZRodHolder between zBushing nuts and lead Screw nuts
    gv.xEndZRodHolderMaxNutFaceToFace = (
    gv.zBushingNutMaxFaceToFace if gv.zBushingNutMaxFaceToFace > gv.leadScrewNut[2] else gv.leadScrewNut[2])

    # Determine xRodClampWidth
    gv.xRodClampWidth = (gv.zRodZScrewDist
                         + gv.xEndZRodHolderMaxNutFaceToFace / math.cos(math.pi / 6)
                         + 2 * gv.bushingNutPadding)
    # Determine zRod Spacing
    gv.zRodSpacing = (gv.xRodLength
                      + 2 * gv.xRodClampWidth
                      - 2 * gv.xRodClampPocketDepth
                      - 2 * gv.bushingNutPadding
                      - gv.xEndZRodHolderMaxNutFaceToFace / math.cos(math.pi / 6)
                      )

    # Determine the max Nut Thickness for the xEndZRodHolder between both zBushingNuts and LeadScrewNuts
    gv.xEndZRodHolderMaxNutThickness = (
    gv.zBushingNutMaxThickness if gv.zBushingNutMaxThickness > gv.leadScrewNut[3] else gv.leadScrewNut[3])

    # Determine the height of the xEndZRodHolder
    gv.xEndZRodHolderHeight = 2 * (
    gv.xEndZRodHolderMaxNutThickness + gv.bushingNutPadding) + gv.xRodSpacing - gv.xRodDiaMax

    # Determine printableWidth
    gv.printableWidth = (gv.xRodLength
                         - 2 * gv.xRodClampPocketDepth
                         - gv.xCarriageWidth / 2
                         - gv.xCarriageWingWidth)

    # Determine yRodSupportWidth
    gv.yRodSupportWidth = (gv.yRodDiaMax
                           + gv.clampHoleDia
                           + gv.clampNutFaceToFace
                           + 2 * gv.clampNutPadding
                           + 4 * gv.slotPadding
                           + 2 * gv.slotDia
                           + 2 * gv.slotWidth)

    # Determine printBedBusingSupportWidth
    gv.printBedBusingSupportWidth = (4 * gv.slotPadding +
                                     2 * gv.slotDia +
                                     2 * gv.slotWidth +
                                     2 * gv.bushingNutPadding +
                                     gv.PBBHMaxFaceToFace / math.cos(math.pi / 6))

    # Determine yRodSpacing
    gv.yRodSpacing = ((gv.printableWidth + 2 * gv.printBedPadding) / 2
                      if (gv.printableWidth + 2 * gv.printBedPadding) / 2
                         > gv.yMotorMountPlateWidth + gv.yRodSupportWidth
                      else gv.yMotorMountPlateWidth + gv.yRodSupportWidth)
    # Check yRodSpacing to make sure that the Bushing supports can be attached to the print bed support
    if gv.yRodSpacing + gv.printBedBusingSupportWidth > gv.printableWidth + 2 * gv.printBedPadding:
        try:
            raise Exception("The yRods can't be spaced far enough apart. Try longer xAxis rods.")
        except Exception as e:
            import traceback
            uf.critical("The yRods can't be spaced far enough apart. Try longer xAxis rods.",
                        "Part error: The yRods can't be spaced far enough apart.: \n\n" + str(
                            e) + '\n' + traceback.format_exc(limit=1), gv.level, os.path.basename(__file__))
            raise StandardError

    # Determine the maximum thickness of the y axis bushings
    gv.yBushingNutMaxThickness = (gv.yBushingNutL[3]
                                  if gv.yBushingNutL[3]
                                     > gv.yBushingNutR[3]
                                  else gv.yBushingNutR[3])

    # Determine the depth of the y bushing mounts
    gv.PBBHDepth = gv.yBushingNutMaxThickness + gv.bushingNutPadding

    # Determine center to center distance between the slots on the y bushing mounts
    # This is the spacing used to determine the spacing between mounting holes on the print bed support
    gv.yBushingMountSlotSpacing = (gv.PBBHMaxFaceToFace / math.cos(math.pi / 6)
                                   + 2 * gv.bushingNutPadding
                                   + 2 * gv.slotPadding
                                   + gv.slotDia
                                   + gv.slotWidth)

    # Determine the center to center distance between y bushing nuts along the y axis
    # Increase printable area by changing the divisor. The price is a less stable print bed.
    gv.yBushingNutSeparation = (gv.yRodLength - 2 * gv.frameWidth) / 3

    # Determine the printable length
    gv.printableLength = (gv.yRodLength
                          - 2 * gv.frameWidth
                          - gv.yBushingNutSeparation
                          - gv.PBBHDepth)

    # Determine the y belt anchor hole spacing
    gv.yBeltAnchorHoleSpacing = (gv.yBeltAnchorLength
                                 + 2 * gv.mountToPrintedPadding
                                 + uf.adjustHole(gv.mountToPrintedDia))

    # Determine the extruderMountPlateWidth
    gv.extruderMountPlateWidth = (gv.xCarriageWidth if gv.xCarriageWidth >
                                                       gv.extruderWidth else gv.extruderWidth)

    # Determine hotEndLength
    gv.hotEndLength = gv.nozzleLength + gv.extruderBarrelLength

    # Determine zEndStopClampLength
    gv.zEndStopClampLength = (2 * gv.mountToPrintedPadding
                              + gv.printedToPrintedDia
                              + 2 * gv.xEndstopChannelWidth
                              + gv.xEndstopContactSpacing
                              + gv.xEndstopPadding
                              + gv.zEndstopJogWidth)

    # Determine vertBarDistBelowZRod
    gv.vertBarDistBelowZRod = (gv.yRodStandoff
                               + gv.PBBHStandoff
                               + gv.printBedSupportThickness
                               + gv.printBedStandoff
                               + gv.printBedThickness
                               + gv.hotEndLength
                               - (gv.extruderMountAngleWidth + gv.extruderMountAngleThickness) / 2
                               - gv.xCarriageMountHoleVertOffset
                               + gv.xCarriageBushingHolderOR
                               - (gv.xEndZRodHolderHeight - gv.xRodSpacing) / 2
                               - gv.zEndStopClampLength  # May change to something fancier that incorporates the zOffset screw
                               - gv.zRodSupportLength)

    # Determine vertBarDistAboveZRod
    if gv.zMotorMountLocation == "Top":
        gv.vertBarDistAboveZRod = (gv.frameJointPadding + gv.frameWidth
                                   if gv.frameJointPadding + gv.frameWidth