"""Sourcetrail server"""

import socket
import errno
import threading
import os.path
import encodings.idna
import subprocess
import time
import vim

try:
    # Python 3
    import socketserver
except ImportError:
    # Python 2
    import SocketServer as socketserver

MESSAGE_SPLIT_STRING = ">>"

class Options:
    """Option class for sourcetrail"""
    instance = None
    def __init__(self):
        self.port_vim_to_sourcetrail = int(vim.eval('sourcetrail#get("vim_to_sourcetrail_port")'))
        self.port_sourcetrail_to_vim = int(vim.eval('sourcetrail#get("sourcetrail_to_vim_port")'))
        self.ip_addr = vim.eval('sourcetrail#get("sourcetrail_ip")')

    @classmethod
    def reload(cls):
        """Reload the options"""
        cls.instance = Options()

    @classmethod
    def inst(cls):
        """ Get the instance of Options"""
        if cls.instance is None:
            cls.reload()
        return cls.instance

    @classmethod
    def get_port_vim_to_sourcetrail(cls):
        """Returns the port sourcetrail listens to"""
        return cls.inst().port_vim_to_sourcetrail

    @classmethod
    def get_port_sourcetrail_to_vim(cls):
        """Returns the port vim listens to"""
        return cls.inst().port_sourcetrail_to_vim

    @classmethod
    def get_ip(cls):
        """Return the ip address"""
        return cls.inst().ip_addr

    @classmethod
    def print_settings(cls):
        """Prints the options"""
        print("Sourcetrail Settings: \n--------------------")
        print("Ports: ")
        print("g:sourcetrail_to_vim_port: " + str(cls.inst().port_sourcetrail_to_vim))
        print("g:vim_to_sourcetrail_port: " + str(cls.inst().port_vim_to_sourcetrail))
        print("Ip: ")
        print("g:sourcetrail_ip         : " + cls.inst().ip_addr)
        print("--------------------")

class ConnectionHandler(socketserver.BaseRequestHandler):
    # This class is instantiated once per connection to the server
    """Handler for incomming messages"""
    timeout = 5
    def handle(self):
        data = self.request.recv(1024).strip()
        text = data.decode('utf-8')
        eom_index = text.find("<EOM>")
        if not eom_index == 0:
            message_string = text[0:eom_index]
            message_fields = message_string.split(MESSAGE_SPLIT_STRING)
            if message_fields[0] == "moveCursor":
                Sourcetrail.set_new_buffer(message_fields[1], \
                                     int(message_fields[2]), int(message_fields[3]))
            if message_fields[0] == "ping":
                Sourcetrail.send_message("ping>>Vim<EOM>".encode())
        else:
            print("asdfasfd")

class Sourcetrail:
    """Sourcetrail class for structur"""
    _instance = None
    def __init__(self):
        self.__col = 0
        self.__row = 0
        self.__file = ""
        self.__update = False
        self.__server = None

    def __del__(self):
        self.stop_server()

    @classmethod
    def inst(cls):
        """get a instance of Sourcetrail"""
        if cls._instance is None:
            cls._instance = Sourcetrail()
        return cls._instance

    @classmethod
    def row(cls):
        """returns the current row"""
        return cls.inst().__row

    @classmethod
    def col(cls):
        """returns the current column"""
        return cls.inst().__col


    @classmethod
    def file(cls):
        """returns the current file"""
        return cls.inst().__file

    @classmethod
    def start_server(cls):
        """starting the server to listen"""
        if cls.inst().__server is None:
            try:
                socketserver.ThreadingTCPServer.allow_reuse_address = True
                address = (Options.get_ip(), Options.get_port_sourcetrail_to_vim())
                cls.inst().__server = socketserver.ThreadingTCPServer(address, ConnectionHandler)
                server_thread = threading.Thread(target=cls.inst().__server.serve_forever)
                server_thread.daemon = True
                server_thread.start()
            except socket.error:
                print("Socket needed for Sourcetrail plugin already in use")

    @classmethod
    def stop_server(cls):
        """stop listening to the port"""
        if cls.inst().__server is not None:
            cls.inst().__server.shutdown()
            cls.inst().__server.server_close()

    @classmethod
    def restart_server(cls):
        """restart the server"""
        cls.inst().stop_server()
        Options.reload()
        cls.inst().start_server()

    @classmethod
    def send_activate_token(cls):
        """send activate token to sourcetrail"""
        cls.inst().start_server()
        filepath = vim.current.buffer.name
        (row, col) = vim.current.window.cursor

        col += 1 # cols returned by rowcol() are 0-based.

        text = "setActiveToken" + MESSAGE_SPLIT_STRING \
               + filepath + MESSAGE_SPLIT_STRING + str(row) \
               + MESSAGE_SPLIT_STRING + str(col) + "<EOM>"
        data = text.encode()
        try:
            cls.inst().send_message(data)
            print("Current Position sent to Sourcetrail")
        except socket.error:
            print("Counld not send to Sourcetrail")


    @classmethod
    def send_message(cls, message):
        """sends a message to sourcetrail"""
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            sock.connect((Options.get_ip(), Options.get_port_vim_to_sourcetrail()))
            sock.send(message)
            sock.close()
        except socket.error:
            raise socket.error

    @classmethod
    def set_new_buffer(cls, filepath, row, col):
        """set currents data"""
        cls.inst().__col = col
        cls.inst().__row = row
        cls.inst().__file = filepath
        cls.inst().__update = True

    @classmethod
    def update_buffer(cls):
        """update"""
        if cls.inst().__server is None:
            cls.inst().start_server()
            print("Vim was not listening to Sourcetrail. Vim is listening now.")
            print("Try to send again from Sourcetrail.")
        else:
            if cls.inst().__update:
                # Must clear the __update flag before "e!" due to autocmd nesting
                cls.inst().__update = False
                vim.command("e! " + cls.inst().__file)
                vim.current.window.cursor = (cls.inst().__row, cls.inst().__col)

    @classmethod
    def print_settings(cls):
        """ Prints Settings """
        Options.inst().print_settings()