#
# Copyright 2019 aiohomekit team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import logging
from typing import Union

from aiohomekit.exceptions import HttpException

logger = logging.getLogger(__name__)


class HttpResponse:
    STATE_PRE_STATUS = 0
    STATE_HEADERS = 1
    STATE_BODY = 2
    STATE_DONE = 3

    def __init__(self) -> None:
        self._state = HttpResponse.STATE_PRE_STATUS
        self._raw_response = bytearray()
        self._is_ready = False
        self._is_chunked = False
        self._had_empty_chunk = False
        self._content_length = -1
        self.version = None
        self.code = None
        self.reason = None
        self.headers = []
        self.body = bytearray()

    def parse(self, part: Union[bytearray, bytes]) -> bytearray:
        self._raw_response += part
        pos = self._raw_response.find(b"\r\n")

        while pos != -1 and self._state < HttpResponse.STATE_BODY:
            line = self._raw_response[:pos]
            self._raw_response = self._raw_response[pos + 2 :]
            if self._state == HttpResponse.STATE_PRE_STATUS:
                # parse status line
                line = line.split(b" ", 2)
                if len(line) != 3:
                    raise HttpException("Malformed status line.")
                self.version = line[0].decode()
                self.code = int(line[1])
                self.reason = line[2].decode()
                self._state = HttpResponse.STATE_HEADERS

            elif self._state == HttpResponse.STATE_HEADERS and line == b"":
                # this is the empty line after the headers
                self._state = HttpResponse.STATE_BODY

            elif self._state == HttpResponse.STATE_HEADERS:
                # parse a header line
                line = line.split(b":", 1)
                name = line[0].decode().strip().title()
                value = line[1].decode().strip()
                if name == "Transfer-Encoding":
                    if value == "chunked":
                        self._is_chunked = True
                elif name == "Content-Length":
                    self._content_length = int(value)
                self.headers.append((name, value))
            else:
                raise HttpException("Unknown parser state")

            pos = self._raw_response.find(b"\r\n")

        if self._state == HttpResponse.STATE_BODY and self._is_chunked:
            pos = self._raw_response.find(b"\r\n")
            while pos > -1:
                line = self._raw_response[:pos]
                self._raw_response = self._raw_response[pos + 2 :]
                length = int(line, 16)
                if length + 2 > len(self._raw_response):
                    self._raw_response = line + b"\r\n" + self._raw_response
                    # the remaining bytes in raw response are not sufficient. bail out and wait for an other call.
                    break

                if length == 0:
                    self._had_empty_chunk = True
                    self._state = HttpResponse.STATE_DONE
                    self._raw_response = self._raw_response[length + 2 :]
                    break

                line = self._raw_response[:length]
                self.body += line
                self._raw_response = self._raw_response[length + 2 :]

                pos = self._raw_response.find(b"\r\n")

        if self._state == HttpResponse.STATE_BODY and self._content_length > 0:
            remaining = self._content_length - len(self.body)
            self.body += self._raw_response[:remaining]
            self._raw_response = self._raw_response[remaining:]

        if self.is_read_completely():
            # Whatever is left in the buffer is part of the next request
            if len(self._raw_response) > 0:
                logger.debug(
                    "Bytes left in buffer after parsing packet: %r", self._raw_response
                )
            return self._raw_response

        return bytearray()

    def read(self):
        """
        Returns the body of the response.

        :return: The read body or None if no body content was read yet
        """
        return self.body

    def is_read_completely(self) -> bool:
        if self._is_chunked:
            return self._had_empty_chunk

        if self._state < HttpResponse.STATE_BODY:
            return False

        if self._content_length != -1:
            return len(self.body) == self._content_length

        return True

    def get_http_name(self) -> str:
        """
        Returns the HTTP name (e.g. HTTP or EVENT).

        :return: The name or None if the status line was not yet read
        """
        if self.version is not None:
            return self.version.split("/")[0]
        return None
