#!/usr/bin/env python
#
# Copyright (C) 2016 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import re
import uuid
import asyncio


import logging
log = logging.getLogger(__name__)


class Link:
    """
    Base class for links.
    """

    def __init__(self, project):

        self._id = str(uuid.uuid4())
        self._nodes = []
        self._project = project
        self._capturing = False
        self._capture_file_name = None
        self._streaming_pcap = None

    @asyncio.coroutine
    def add_node(self, node, adapter_number, port_number):
        """
        Add a node to the link
        """
        self._nodes.append({
            "node": node,
            "adapter_number": adapter_number,
            "port_number": port_number
        })

        if len(self._nodes) == 2:
            self._project.controller.notification.emit("link.created", self.__json__())

    @asyncio.coroutine
    def create(self):
        """
        Create the link
        """

        raise NotImplementedError

    @asyncio.coroutine
    def delete(self):
        """
        Delete the link
        """

        raise NotImplementedError

    @asyncio.coroutine
    def start_capture(self, data_link_type="DLT_EN10MB", capture_file_name=None):
        """
        Start capture on the link

        :returns: Capture object
        """

        self._capturing = True
        self._capture_file_name = capture_file_name
        self._streaming_pcap = asyncio.async(self._start_streaming_pcap())
        self._project.controller.notification.emit("link.updated", self.__json__())

    @asyncio.coroutine
    def _start_streaming_pcap(self):
        """
        Dump a pcap file on disk
        """

        stream = yield from self.read_pcap_from_source()
        with open(self.capture_file_path, "wb+") as f:
            while self._capturing:
                # We read 1 bytes by 1 otherwise the remaining data is not read if the traffic stops
                data = yield from stream.read(1)
                if data:
                    f.write(data)
                    # Flush to disk otherwise the live is not really live
                    f.flush()
                else:
                    break

    @asyncio.coroutine
    def stop_capture(self):
        """
        Stop capture on the link
        """

        self._capturing = False
        self._project.controller.notification.emit("link.updated", self.__json__())

    @asyncio.coroutine
    def _read_pcap_from_source(self):
        """
        Return a FileStream of the Pcap from the compute server
        """

        raise NotImplementedError

    def default_capture_file_name(self):
        """
        :returns: File name for a capture on this link
        """

        capture_file_name = "{}_{}-{}_to_{}_{}-{}".format(self._nodes[0]["node"].name,
                                                          self._nodes[0]["adapter_number"],
                                                          self._nodes[0]["port_number"],
                                                          self._nodes[1]["node"].name,
                                                          self._nodes[1]["adapter_number"],
                                                          self._nodes[1]["port_number"])
        return re.sub("[^0-9A-Za-z_-]", "", capture_file_name) + ".pcap"

    @property
    def id(self):
        return self._id

    @property
    def capturing(self):
        return self._capturing

    @property
    def capture_file_path(self):
        """
        Get the path of the capture
        """

        if self._capture_file_name:
            return os.path.join(self._project.captures_directory, self._capture_file_name)
        else:
            return None

    def __json__(self):
        res = []
        for side in self._nodes:
            res.append({
                "node_id": side["node"].id,
                "adapter_number": side["adapter_number"],
                "port_number": side["port_number"]
            })
        return {
            "nodes": res,
            "link_id": self._id,
            "project_id": self._project.id,
            "capturing": self._capturing,
            "capture_file_name": self._capture_file_name,
            "capture_file_path": self.capture_file_path
        }