#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

import time

import twisted.internet.protocol
from twisted.protocols import basic

from winswitch.consts import MAX_LINE_LENGTH, NOTIFY_INFO, NOTIFY_ERROR, NOTIFY_RETRY, DELIMITER
from winswitch.util.simple_logger import Logger
from winswitch.client.server_line_connection import ServerLineConnection
from winswitch.objects.server_config import ServerConfig
from winswitch.util.main_loop import callLater

MAX_RETRY = 3


class ServerReceiver(basic.LineReceiver):

	def __init__(self):
		Logger(self)
		self.MAX_LENGTH = MAX_LINE_LENGTH
		self.server = None
		self.closed = False
		self.line_connection = None

	def __str__(self):
		return	"ServerReceiver"

	def stop(self, retry=False, message=None):
		if self.closed:
			self.slog("already stopped!", retry, message)
			return
		self.slog(None, retry, message)
		if message:
			self.server_link.notify("Connection to %s closed" % self.server.name, message,
								notification_type=NOTIFY_INFO)
		self.closed = True
		self.factory.closed = True
		self.factory.do_retry = retry
		self.transport.loseConnection()

	def is_connected(self):
		return	not self.closed and self.server and self.server.is_connected()

	def connectionMade(self):
		self.closed = False
		self.delimiter = DELIMITER
		self.server_link = self.factory.server_link
		self.server = self.server_link.server
		self.slog("to %s" % self.server)
		#reset failure/timeouts
		self.factory.failures = 0
		self.factory.wait = 0
		self.line_connection = ServerLineConnection(self.factory.server_link, self.write, self.stop, self.is_connected)
		self.server_link.set_client(self.line_connection)
		self.server_link.set_server_status(self.factory, ServerConfig.STATUS_CONNECTED)
		self.server.touch()
		self.line_connection.connectionMade()

	def connectionLost(self, reason):
		if self.closed:
			self.slog(None, reason)
		else:
			self.serror(None, reason)
		self.closed = True
		self.line_connection = None
		self.server_link.set_server_status(self.factory, ServerConfig.STATUS_DISCONNECTED)
		self.server.touch()

	def write(self, data):
		self.transport.write("%s%s" % (data,DELIMITER))

	def lineReceived(self, line):
		self.line_connection.lineReceived(line)



class ServerReceiverConnectionFactory(twisted.internet.protocol.ClientFactory):
	"""
	A factory for Server Line Connections, creates a ServerReceiver which will
	send and receive data. The data is handled by ServerLineConnection.
	"""

	protocol = ServerReceiver

	def __init__ (self, server_link, endpoint):
		Logger(self)
		self.sdebug(None, server_link, endpoint)
		self.endpoint = endpoint
		self.server_link = server_link
		self.wait = 0
		self.failures = 0
		self.lost = 0
		self.do_retry = True
		self.closed = False
		self.abort = False

	def __str__(self):
		return	"ServerConnectionFactory(%s)" % str(self.endpoint)

	def clientConnectionLost(self, connector, reason):
		if self.abort:
			self.sdebug(None, connector, reason)
			return
		elif self.closed:
			self.slog(None, connector, reason)
		else:
			self.serror(None, connector, reason)
		self.server_link.set_server_status(self, ServerConfig.STATUS_DISCONNECTED)
		self.lost += 1
		if self.do_retry and not self.closed and self.lost < MAX_RETRY:
			if not self.server_link.warned:
				self.server_link.notify("Connection to server '%s' lost" % self.server_link.server.name,
									"Attempting to re-connect", notification_type=NOTIFY_RETRY)
				self.server_link.notify_embargo = time.time()+10		#hide other errors (ie: PortMonitor)
				self.server_link.warned = True
			self.server_link.server.touch()
			self.retry(connector)
		else:
			self.server_link.server.touch()
			self.server_link.stop_requested = False		#stop occured - reset flag
			self.server_link.client_factory = None

	def retry(self, connector):
		if self.wait < 100:
			self.wait += 1
		self.slog("waiting %d seconds before retrying connection to '%s' on %s" % (self.wait, self.server_link.server.name, self.endpoint), connector)
		def re_connect():
			self.server_link.set_server_status(self, ServerConfig.STATUS_CONNECTING)
			connector.connect()
		callLater(self.wait, re_connect)

	def clientConnectionFailed(self, connector, reason):
		if self.abort:
			self.sdebug(None, connector, reason)
			return
		self.serror(None, connector, reason)
		self.server_link.set_server_status(self, ServerConfig.STATUS_DISCONNECTED)
		self.failures += 1
		if self.do_retry and not self.closed and self.failures < MAX_RETRY:
			self.retry(connector)
		else:
			self.server_link.stop_requested = False		#stop occured - reset flag
			if not self.server_link.warned:
				server = self.server_link.server
				msg = "Please ensure that the server settings are correct\n"
				if self.server_link.server.platform.startswith("win"):
					msg += "Is a firewall blocking access?"
				else:
					msg += "You may want to enable SSH Tunneling to bypass a firewall"
				self.server_link.notify("Connection to server '%s' as '%s' failed" % (server.get_display_name(), server.username),
									msg, notification_type=NOTIFY_ERROR)
				self.server_link.warned = True
			self.server_link.client_factory = None
