"""Node (User) Class for Paxos Calendar."""

import os
import sys
import time
import thread
import pickle
import socket
import logging
from Bully import bully_algorithm
from Appointment import Appointment
from Calendar import Calendar
from Proposer import Proposer
from Acceptor import Acceptor

class Node(object):
    """
    Node class.

    node_id:        Unique ID used for Node identification as well as for
                    unique proposal number generation; int.
    calendar:       Calendar object which contains Appointment objects.
    proposer:       Proposer object used in Synod Algorithm; passed node_id so
                    it can create unique proposal numbers.
    acceptor:       Acceptor object used in Synod Algorithm.
    log:            Dictionary of Calendar objects used in Paxos Algorithm;
                    intially empty, Synod Algorithm is used to fill each entry
                    of log where integer keys represents slots and the values
                    being the Calendar agreed upon via conscensus.
    leader:         The current leader elected via the bully algorithm;
                    initially None and updated every ~6 seconds.
    """

    _ip_filename = "./IP_translations.txt"

    def __init__(self, node_id):
        """Construct a Node object."""
        if type(node_id) != int:
            raise TypeError("node_id must be an int")
        if node_id < 0:
            raise ValueError("node id must be a nonnegative integer")

        try:
            Node._ip_table = Node._make_ip_table()
        except IOError:
            raise IOError("Node-to-IP translation file: " + ip_filename + " not found.")

        self._node_id = node_id
        self._calendar = Calendar()
        self._proposer = Proposer(node_id,self._ip_table)
        self._acceptor = Acceptor(self._ip_table)
        self._log = {}
        self._leader = None
        self._terminate = False
        self._is_Node = True

    def insert(self, appointment):
        """Insert an Appointment into this Node's Calendar."""
        #First create new Calendar with new appointment
        from copy import deepcopy
        new_calendar = deepcopy(self._calendar)
        new_calendar += appointment

        if self._log.keys():
            next_log_slot = max(self._log.keys()) + 1
        else:
            next_log_slot = 0

        #Then ask leader to propose the new Calendar
        try:
            leader_IP, leader_TCP, leader_UDP = self._ip_table[self._leader]
            proposal_message = pickle.dumps(
                ("propose", Calendar.serialize(new_calendar), next_log_slot))
            udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            udp_socket.sendto(proposal_message, (leader_IP, leader_UDP))
            udp_socket.close()
        except KeyError as excinfo:
            print "Unable to find leader, waiting until one is selected..."
            while self._leader == None:
                pass
            print "Found leader, continuing...\n"
            self.insert(appointment)

    def delete(self, appointment):
        """Delete an Appointment in this Node's Calendar."""
        #First create new Calendar without appointment
        from copy import deepcopy
        new_calendar = Calendar()
        for self_appointment in self._calendar:
            if self_appointment != appointment:
                new_calendar += deepcopy(self_appointment)

        if self._log.keys():
            next_log_slot = max(self._log.keys()) + 1
        else:
            next_log_slot = 0

        #Then ask leader to propose the new Calendar
        try:
            leader_IP, leader_TCP, leader_UDP = self._ip_table[self._leader]
            proposal_message = pickle.dumps(