from __future__ import annotations
from typing import AsyncIterable, Union, \
  TYPE_CHECKING, cast
from inspect import iscoroutinefunction
from pathlib import Path
import io

from aiofile import AIOFile, LineReader

from .types import Final

if TYPE_CHECKING:  # keep mypy quiet
  from .path import AsyncPath


BEGINNING: Final[int] = 0
CHUNK_SIZE: Final[int] = 4 * 1_024

SEP: Final[str] = '\n'
ENCODING: Final[str] = 'utf-8'
ERRORS: Final[str] = 'replace'


Paths = Union['AsyncPath', Path, str]


class IterableAIOFile(AIOFile):
  def __init__(
    self,
    *args,
    errors: str | None = ERRORS,
    newline: str | None = SEP,
    **kwargs
  ):
    super().__init__(*args, **kwargs)
    self._errors: str | None = errors
    self._newline: str | None = newline

  def __aiter__(self) -> AsyncIterable[str]:
    encoding, errors, line_sep = self._get_options()

    return read_lines(
      self.name,
      line_sep,
      encoding=encoding,
      errors=errors,
    )

  def _get_options(
    self,
    encoding: str | None = None,
    errors: str | None = None
  ) -> tuple[str, str, str]:
    encoding = encoding or self.encoding or ENCODING
    errors = errors or self._errors or ERRORS
    line_sep: str = self._newline or SEP

    return encoding, errors, line_sep

  async def read_text(
    self,
    encoding: str | None = None,
    errors: str | None = None
  ) -> str:
    encoding, errors, line_sep = self._get_options(encoding, errors)

    return await read_full_file(
      self.name,
      line_sep,
      encoding=encoding,
      errors=errors
    )


async def read_lines(
  path: Paths,
  line_sep: str = SEP,
  chunk_size: int = CHUNK_SIZE,
  offset: int = BEGINNING,
  encoding: str = ENCODING,
  errors: str = ERRORS,
  **kwargs
) -> AsyncIterable[str]: 
  if hasattr(path, 'resolve'):
    if iscoroutinefunction(path.resolve):
      path = str(await path.resolve())

    else:
      path = str(path.resolve())

  path = cast(str, path)

  async with AIOFile(path, 'rb') as handle:
    reader = LineReader(
      handle,
      line_sep=line_sep,
      chunk_size=chunk_size,
      offset=offset
    )

    while line := await reader.readline():
      yield line.decode(encoding, errors=errors)


async def read_full_file(
  path: Paths,
  line_sep: str = SEP,
  chunk_size: int = CHUNK_SIZE,
  offset: int = BEGINNING,
  encoding: str = ENCODING,
  errors: str = ERRORS,
  **kwargs
) -> str:
  lines_gen = read_lines(
    path,
    line_sep=line_sep,
    chunk_size=chunk_size,
    offset=offset,
    encoding=encoding,
    errors=errors
  )

  with io.StringIO() as string:
    async for line in lines_gen:
      string.write(line)

    return string.getvalue()
