import datetime
import json
import os
import psutil
import asyncio
import asyncio.streams
import pam
import discord
from concurrent.futures import CancelledError


class IpcClient:
    def __init__(self, bot):
        self.bot = bot
        self.loop = asyncio.get_event_loop()
        self.task = None
        self.writer = None
        self.reader = None
        self.queue = asyncio.Queue(loop=self.loop)

    @property
    def bot(self):
        return self._bot

    @bot.setter
    def bot(self, value):
        self._bot = value

    @property
    def loop(self):
        return self._loop

    @loop.setter
    def loop(self, value):
        self._loop = value

    @property
    def task(self):
        return self._task

    @task.setter
    def task(self, value):
        self._task = value

    @property
    def reader(self):
        return self._reader

    @reader.setter
    def reader(self, value):
        self._reader = value

    @property
    def writer(self):
        return self._writer

    @writer.setter
    def writer(self, value):
        self._writer = value

    @property
    def queue(self):
        return self._queue

    @queue.setter
    def queue(self, value):
        self._queue = value

    def _prepare_data(self, data):
        return "{}\n".format(data).encode("utf-8")

    def start(self):
        self.task = self.loop.create_task(self._client())

    def stop(self):
        if not self.task is None:
            self.task.cancel()
            self.task = None

    async def _client(self):
        reader, writer = await asyncio.streams.open_connection("127.0.0.1", 64001, loop=self.loop)
        self.reader = reader
        self.writer = writer
        pam.log("Shard {} IPC connected".format(self.bot.shard_id), tag="PAM IPC")

        async def send(data):
            dat = self._prepare_data(data)
            writer.write(dat)
            await writer.drain()

        async def recv():
            dat = await reader.readline()
            dat = dat.decode("utf-8")
            try:
                dat = json.loads(dat)
                return dat

            except:
                pam.log("Received malformed IPC packet from master: {}".format(dat), tag="PAM IPC")

        await send(json.dumps({"shard_id": self.bot.shard_id}))

        try:
            while True:
                data = await recv()

                if not "cmd" in data:
                    await self.queue.put(data)
                    continue

                cmd = data["cmd"]

                if cmd == "msg":
                    gldid = data["guild_id"]
                    chnid = data["channel_id"]

                    gld = self.bot.get_server(gldid)
                    if gld is None:
                        await send(json.dumps({"status": "error", "reason": "Guild not found (IPC)"}))
                        continue

                    chn = gld.get_channel(chnid)
                    if chn is None:
                        await send(json.dumps({"status": "error", "reason": "Channel not found (IPC)"}))
                        continue

                    await self.bot.send_message(chn, data["content"])
                    await send(json.dumps({"status": "success", "reason": "Message sent"}))

                elif cmd == "stats":
                    ccount = 0
                    tcount = 0
                    vcount = 0
                    mcount = 0
                    for gld in self.bot.servers:
                        ccount += len(gld.channels)
                        mcount += gld.member_count
                        for chn in gld.channels:
                            if chn.type == discord.ChannelType.text:
                                tcount += 1
                            else:
                                vcount += 1

                    p = psutil.Process()
                    ct = p.create_time()
                    ct = datetime.datetime.fromtimestamp(ct, tz=datetime.timezone.utc)
                    ut = datetime.datetime.now(datetime.timezone.utc) - ct
                    ut = ut.total_seconds()

                    await send(json.dumps(
                        {"guild_count": len(self.bot.servers), "channel_count": {"total": ccount, "text": tcount, "voice": vcount}, "member_count": {"total": mcount}, "cog_count": len(self.bot.cogs),
                         "uptime": ut, "pid": os.getpid(), "total_ram": p.memory_info().rss}))

        except CancelledError:
            pass

        finally:
            writer.close()
            self.reader = None
            self.writer = None

    async def send_guild_available(self, guild_id, guild_name):
        dat = {"cmd": "guild_available", "id": guild_id, "name": guild_name}
        dat = json.dumps(dat)
        dat = self._prepare_data(dat)

        self.writer.write(dat)
        await self.writer.drain()

    async def send_guild_unavailable(self, guild_id):
        dat = {"cmd": "guild_unavailable", "id": guild_id}
        dat = json.dumps(dat)
        dat = self._prepare_data(dat)

        self.writer.write(dat)
        await self.writer.drain()

    async def send_message(self, guild_id, channel_id, content):
        dat = {"cmd": "msg", "guild_id": str(guild_id), "channel_id": str(channel_id), "content": content}
        dat = json.dumps(dat)
        dat = self._prepare_data(dat)

        self.writer.write(dat)
        await self.writer.drain()

        data = None
        while data is None or not "status" in data:
            data = await self.queue.get()

        return data

    async def get_statistics(self):
        dat = {"cmd": "stats"}
        dat = json.dumps(dat)
        dat = self._prepare_data(dat)

        self.writer.write(dat)
        await self.writer.drain()

        data = None
        while data is None:
            data = await self.queue.get()

        return data
