import logging
import sys
import threading
from lura.utils import ExcInfo
from typing import Any, Callable, Mapping, Optional, Sequence, Type

log = logging.getLogger(__name__)

class Thread(threading.Thread):
  '''
  Thread subclass with the following features:

 - Captures the result or excinfo generated by the target callable.
 - Optionally re-raise exceptions in threads.
 - spawn() static method for creating and starting threads.
 '''

  @classmethod
  def spawn(cls, *args: Any, **kwargs: Any) -> 'Thread':
    thread = cls(*args, **kwargs)
    thread.start()
    return thread

  _thread_target: Callable[[], Any]
  _thread_args: Sequence[Any]
  _thread_kwargs: Mapping[str, Any]
  _thread_reraise: bool
  _thread_result: Optional[Any]
  _thread_error: Optional[ExcInfo]

  def __init__(
    self,
    group: Optional[Any] = None,
    target: Optional[Callable] = None,
    name: Optional[str] = None,
    args: Sequence[Any] = (),
    kwargs: Mapping[str, Any] = {},
    *,
    daemon: Optional[bool] = None,
    reraise: bool = False
  ) -> None:
    super().__init__(group=group, name=name, daemon=daemon)
    self._thread_target = target
    self._thread_args = args
    self._thread_kwargs = kwargs
    self._thread_reraise = reraise
    self._thread_result = None
    self._thread_error = None
  
  @property
  def result(self) -> Optional[Any]:
    return self._thread_result

  @property
  def error(self) -> Optional[Type[Exception]]:
    return self._thread_error

  def _thread_work(self) -> None:
    try:
      self._thread_result = self._thread_target(
        *self._thread_args, **self._thread_kwargs)
    except Exception:
      self._thread_error = sys.exc_info()
      if self._thread_reraise:
        raise

  def run(self) -> None:
    raise NotImplementedError()

  def start(self) -> None:
    # FIXME !!!
    if not self._thread_target:
      self._thread_target = self.run
    self.run = self._thread_work
    super().start()
