import logging
import asyncio
import socket

import typing  #注意 typing 是 requirement.txt 里面的,需要额外安装

import sys
sys.path.append("..")
from module.cipher       import Cipher
from module.securesocket import SecureSocket
from utils               import net

Connection = socket.socket
logger = logging.getLogger(__name__)


class LsLocal(SecureSocket):
	# 新建一个本地端
	def __init__(self, loop: asyncio.AbstractEventLoop, password: bytearray, listenAddr: net.Address, remoteAddr: net.Address) -> None:
		super().__init__(loop=loop, cipher=Cipher.NewCipher(password))
		self.listenAddr = listenAddr
		self.remoteAddr = remoteAddr

	# 本地端启动监听,接收来自本机浏览器的连接
	async def listen(self, didListen: typing.Callable=None):
		with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener:
			listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
			listener.bind(self.listenAddr)
			listener.listen(socket.SOMAXCONN)
			listener.setblocking(False)

			logger.info('Listen to %s:%d' % self.listenAddr)
			if didListen:
				didListen(listener.getsockname())

			while True:
				connection, address = await self.loop.sock_accept(listener)
				logger.info('Receive %s:%d', *address)
				asyncio.ensure_future(self.handleConn(connection))

	async def handleConn(self, connection: Connection):
		remoteServer = await self.dialRemote()

		def cleanUp(task):
			"""
			Close the socket when they succeeded or had an exception.
			"""
			# 退出本次工作
			remoteServer.close()
			connection.close()

		# 从 localUser 读取数据发送到 dstServer
		local2remote = asyncio.ensure_future(self.decodeCopy(connection, remoteServer))
		# 从 localUser 发送数据发送到 proxyServer,这里因为处在翻墙阶段出现网络错误的概率更大
		remote2local = asyncio.ensure_future(self.encodeCopy(remoteServer, connection))
		task = asyncio.ensure_future(asyncio.gather(local2remote, remote2local, loop=self.loop, return_exceptions=True))
		task.add_done_callback(cleanUp)

	async def dialRemote(self):
		"""
		Create a socket that connects to the Remote Server.
		"""
		try:
			remoteConn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			remoteConn.setblocking(False)
			await self.loop.sock_connect(remoteConn, self.remoteAddr)
		except Exception as err:
			raise ConnectionError('链接到远程服务器 %s:%d 失败:\n%r' % (*self.remoteAddr, err))

		return remoteConn