import struct
import socket

from ppadb.protocol import Protocol
from ppadb.utils.logger import AdbLogging

logger = AdbLogging.get_logger(__name__)


class Connection:
    def __init__(self, host='localhost', port=5037, timeout=None):
        self.host = host
        self.port = port
        self.timeout = timeout
        self.socket = None

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.close()

    def connect(self):
        logger.debug("Connect to adb server - {}:{}".format(self.host, self.port))

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        l_onoff = 1
        l_linger = 0

        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', l_onoff, l_linger))
        if self.timeout:
            self.socket.settimeout(self.timeout)

        try:
            self.socket.connect((self.host, self.port))
        except socket.error as e:
            raise RuntimeError("ERROR: connecting to {}:{} {}.\nIs adb running on your computer?".format(
                self.host,
                self.port,
                e
            ))

        return self.socket

    def close(self):
        logger.debug("Connection closed...")
        self.socket.close()

    ##############################################################################################################
    #
    # Send command & Receive command result
    #
    ##############################################################################################################
    def _recv(self, length):
        return self.socket.recv(length)

    def _recv_into(self, length):
        recv = bytearray(length)
        view = memoryview(recv)
        self.socket.recv_into(view)
        return recv

    def _send(self, data):
        self.socket.send(data)

    def receive(self):
        nob = int(self._recv(4).decode('utf-8'), 16)
        recv = self._recv_into(nob)

        return recv.decode('utf-8')

    def send(self, msg):
        msg = Protocol.encode_data(msg)
        logger.debug(msg)
        self._send(msg)
        return self._check_status()

    def _check_status(self):
        recv = self._recv(4).decode('utf-8')
        if recv != Protocol.OKAY:
            error = self._recv(1024).decode('utf-8')
            raise RuntimeError("ERROR: {} {}".format(repr(recv), error))

        return True

    def check_status(self):
        return self._check_status()

    ##############################################################################################################
    #
    # Socket read/write
    #
    ##############################################################################################################
    def read_all(self):
        data = bytearray()

        while True:
            recv = self._recv(4096)
            if not recv:
                break
            data += recv

        return data

    def read(self, length=0):
        data = self._recv(length)
        return data

    def write(self, data):
        self._send(data)
