import functools
import ipaddress
import re
import socket

from pathlib import Path, PurePath
from random import SystemRandom
from types import TracebackType
from typing import Any, AsyncContextManager, Awaitable, Callable, Dict
from typing import Generator, Generic, IO, Mapping, Optional, Sequence
from typing import Tuple, Type, TypeVar, Union, cast, overload
from typing_extensions import Literal, Protocol

from .constants import DEFAULT_LANG
from .constants import DISC_COMPRESSION_ERROR, DISC_CONNECTION_LOST
from .constants import DISC_HOST_KEY_NOT_VERIFIABLE, DISC_ILLEGAL_USER_NAME
from .constants import DISC_KEY_EXCHANGE_FAILED, DISC_MAC_ERROR
from .constants import DISC_NO_MORE_AUTH_METHODS_AVAILABLE
from .constants import DISC_PROTOCOL_ERROR, DISC_PROTOCOL_VERSION_NOT_SUPPORTED
from .constants import DISC_SERVICE_NOT_AVAILABLE

class _Hash(Protocol):
    """Protocol for hashing data"""

    @property
    def digest_size(self) -> int:
        """Return the hash digest size"""

    @property
    def block_size(self) -> int:
        """Return the hash block size"""

    @property
    def name(self) -> str:
        """Return the hash name"""

    def digest(self) -> bytes:
        """Return the digest value as a bytes object"""

    def hexdigest(self) -> str:
        """Return the digest value as a string of hexadecimal digits"""

    def update(self, __data: bytes) -> None:
        """Update this hash object's state with the provided bytes"""

class HashType(Protocol):
    """Protocol for returning the type of a hash function"""

    def __call__(self, __data: bytes = ...) -> _Hash:
        """Create a new hash object"""

class _SupportsWaitClosed(Protocol):
    """A class that supports async wait_closed"""

    async def wait_closed(self) -> None:
        """Wait for transport to close"""

_T = TypeVar('_T')
DefTuple = Union[Tuple[()], _T]
MaybeAwait = Union[_T, Awaitable[_T]]

ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
OptExcInfo = Union[ExcInfo, Tuple[None, None, None]]

BytesOrStr = Union[bytes, str]
FilePath = Union[str, PurePath]
HostPort = Tuple[str, int]
IPAddress = Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
IPNetwork = Union[ipaddress.IPv4Network, ipaddress.IPv6Network]
SockAddr = Union[Tuple[str, int], Tuple[str, int, int, int]]

# Define a version of randrange which is based on SystemRandom(), so that
# we get back numbers suitable for cryptographic use.
_random = SystemRandom()
randrange = _random.randrange

_unit_pattern = re.compile(r'([A-Za-z])')
_byte_units = {'': 1, 'k': 1024, 'm': 1024*1024, 'g': 1024*1024*1024}
_time_units = {'': 1, 's': 1, 'm': 60, 'h': 60*60,
               'd': 24*60*60, 'w': 7*24*60*60}

def hide_empty(value: object, prefix: str = ', ') -> str:
    """Return a string with optional prefix if value is non-empty"""

    value = str(value)
    return prefix + value if value else ''

def plural(length: int, label: str, suffix: str = 's') -> str:
    """Return a label with an optional plural suffix"""

    return '%d %s%s' % (length, label, suffix if length != 1 else '')

def all_ints(seq: Sequence[object]) -> bool:
    """Return if a sequence contains all integers"""

    return all(isinstance(i, int) for i in seq)

def get_symbol_names(symbols: Mapping[str, int], prefix: str,
                     strip_leading: int = 0) -> Mapping[int, str]:
    """Return a mapping from values to symbol names for logging"""

    return {value: name[strip_leading:] for name, value in symbols.items()
            if name.startswith(prefix)}

# Punctuation to map when creating handler names
_HANDLER_PUNCTUATION = (('@', '_at_'), ('.', '_dot_'), ('-', '_'))

def map_handler_name(name: str) -> str:
    """Map punctuation so a string can be used as a handler name"""

    for old, new in _HANDLER_PUNCTUATION:
        name = name.replace(old, new)

    return name

def _normalize_scoped_ip(addr: str) -> str:
    """Normalize scoped IP address

       The ipaddress module doesn't handle scoped addresses properly,
       so we normalize scoped IP addresses using socket.getaddrinfo
       before we pass them into ip_address/ip_network.

    """

    try:
        addrinfo = socket.getaddrinfo(addr, None, family=socket.AF_UNSPEC,
                                      type=socket.SOCK_STREAM,
                                      flags=socket.AI_NUMERICHOST)[0]
    except socket.gaierror:
        return addr

    if addrinfo[0] == socket.AF_INET6:
        sa = addrinfo[4]
        addr = sa[0]

        idx = addr.find('%')
        if idx >= 0: # pragma: no cover
            addr = addr[:idx]

        ip = ipaddress.ip_address(addr)

        if ip.is_link_local:
            scope_id = cast(Tuple[str, int, int, int], sa)[3]
            addr = str(ipaddress.ip_address(int(ip) | (scope_id << 96)))

    return addr

def ip_address(addr: str) -> IPAddress:
    """Wrapper for ipaddress.ip_address which supports scoped addresses"""

    return ipaddress.ip_address(_normalize_scoped_ip(addr))

def ip_network(addr: str) -> IPNetwork:
    """Wrapper for ipaddress.ip_network which supports scoped addresses"""

    idx = addr.find('/')
    if idx >= 0:
        addr, mask = addr[:idx], addr[idx:]
    else:
        mask = ''

    return ipaddress.ip_network(_normalize_scoped_ip(addr) + mask)

def open_file(filename: FilePath, mode: str, buffering: int = -1) -> IO[bytes]:
    """Open a file with home directory expansion"""

    return open(Path(filename).expanduser(), mode, buffering=buffering)

@overload
def read_file(filename: FilePath) -> bytes:
    """Read from a binary file with home directory expansion"""

@overload
def read_file(filename: FilePath, mode: Literal['rb']) -> bytes:
    """Read from a binary file with home directory expansion"""

@overload
def read_file(filename: FilePath, mode: Literal['r']) -> str:
    """Read from a text file with home directory expansion"""

def read_file(filename, mode = 'rb'):
    """Read from a file with home directory expansion"""

    with open_file(filename, mode) as f:
        return f.read()

def write_file(filename: FilePath, data: bytes, mode: str = 'wb') -> int:
    """Write or append to a file with home directory expansion"""

    with open_file(filename, mode) as f:
        return f.write(data)

def _parse_units(value: str, suffixes: Mapping[str, int], label: str) -> float:
    """Parse a series of integers followed by unit suffixes"""

    matches = _unit_pattern.split(value)

    if matches[-1]:
        matches.append('')
    else:
        matches.pop()

    try:
        return sum(float(matches[i]) * suffixes[matches[i+1].lower()]
                   for i in range(0, len(matches), 2))
    except KeyError:
        raise ValueError('Invalid ' + label) from None

def parse_byte_count(value: str) -> int:
    """Parse a byte count with optional k, m, or g suffixes"""

    return int(_parse_units(value, _byte_units, 'byte count'))

def parse_time_interval(value: str) -> float:
    """Parse a time interval with optional s, m, h, d, or w suffixes"""

    return _parse_units(value, _time_units, 'time interval')

_ACM = TypeVar('_ACM', bound=AsyncContextManager, covariant=True)

class _ACMWrapper(Generic[_ACM]):
    """Async context manager wrapper"""

    def __init__(self, coro: Awaitable[_ACM]):
        self._coro = coro
        self._coro_result: Optional[_ACM] = None

    def __await__(self) -> Generator[Any, None, _ACM]:
        return self._coro.__await__()

    async def __aenter__(self) -> _ACM:
        self._coro_result = await self._coro

        return await self._coro_result.__aenter__()

    async def __aexit__(self, exc_type: Optional[Type[BaseException]],
                        exc_value: Optional[BaseException],
                        traceback: Optional[TracebackType]) -> Optional[bool]:
        assert self._coro_result is not None

        exit_result = await self._coro_result.__aexit__(
            exc_type, exc_value, traceback)

        self._coro_result = None

        return exit_result

_ACMCoro = Callable[..., Awaitable[_ACM]]
_ACMWrapperFunc = Callable[..., _ACMWrapper[_ACM]]

def async_context_manager(coro: _ACMCoro[_ACM]) -> _ACMWrapperFunc[_ACM]:
    """Decorator for functions returning asynchronous context managers

       This decorator can be used on functions which return objects
       intended to be async context managers. The object returned by
       the function should implement __aenter__ and __aexit__ methods
       to run when the async context is entered and exited.

       This wrapper also allows the use of "await" on the function being
       decorated, to return the context manager without entering it.

    """

    @functools.wraps(coro)
    def context_wrapper(*args, **kwargs) -> _ACMWrapper[_ACM]:
        """Return an async context manager wrapper for this coroutine"""

        return _ACMWrapper(coro(*args, **kwargs))

    return context_wrapper

async def maybe_wait_closed(writer: '_SupportsWaitClosed') -> None:
    """Wait for a StreamWriter to close, if Python version supports it

       Python 3.8 triggers a false error report about garbage collecting
       an open stream if a close is in progress when a StreamWriter is
       garbage collected. This can be avoided by calling wait_closed(),
       but that method is not available in Python releases prior to 3.7.
       This function wraps this call, ignoring the error if the method
       is not available.

    """

    try:
        await writer.wait_closed()
    except AttributeError: # pragma: no cover
        pass

class Options:
    """Container for configuration options"""

    kwargs: Dict[str, object]

    def __init__(self, options: Optional['Options'] = None, **kwargs: object):
        if options:
            if not isinstance(options, type(self)):
                raise TypeError('Invalid %s, got %s' %
                                (type(self).__name__, type(options).__name__))

            self.kwargs = options.kwargs.copy()
        else:
            self.kwargs = {}

        self.kwargs.update(kwargs)
        self.prepare(**self.kwargs)

    def prepare(self, **kwargs: object) -> None:
        """Pre-process configuration options"""

    def update(self, kwargs: Dict[str, object]) -> None:
        """Update options based on keyword parameters passed in"""

        self.kwargs.update(kwargs)
        self.prepare(**self.kwargs)

class _RecordMeta(type):
    """Metaclass for general-purpose record type"""

    def __new__(mcs: Type['_RecordMeta'], name: str, bases: Tuple[type, ...],
                ns: Dict[str, object]) -> '_RecordMeta':
        if name != 'Record':
            fields = cast(Mapping[str, str],
                          ns.get('__annotations__', {})).keys()
            defaults = {k: ns.get(k) for k in fields}

            ns = {k: v for k, v in ns.items() if k not in fields}
            ns['__slots__'] = defaults

        return cast(_RecordMeta, super().__new__(mcs, name, bases, ns))

class Record(metaclass=_RecordMeta):
    """Generic Record class"""

    __slots__: Mapping[str, object] = {}

    def __init__(self, *args: object, **kwargs: object):
        for k, v in self.__slots__.items():
            setattr(self, k, v)

        for k, v in zip(self.__slots__, args):
            setattr(self, k, v)

        for k, v in kwargs.items():
            setattr(self, k, v)

    def __repr__(self) -> str:
        return '%s(%s)' % (type(self).__name__,
                           ', '.join('%s=%r' % (k, getattr(self, k))
                                     for k in self.__slots__))

    def __str__(self) -> str:
        values = ((k, self._format(k, getattr(self, k)))
                  for k in self.__slots__)

        return ', '.join('%s: %s' % (k, v) for k, v in values if v is not None)

    def _format(self, k: str, v: object) -> Optional[str]:
        """Format a field as a string"""

        # pylint: disable=no-self-use,unused-argument

        return str(v)

class Error(Exception):
    """General SSH error"""

    def __init__(self, code: int, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(reason)
        self.code = code
        self.reason = reason
        self.lang = lang

class DisconnectError(Error):
    """SSH disconnect error

       This exception is raised when a serious error occurs which causes
       the SSH connection to be disconnected. Exception codes should be
       taken from :ref:`disconnect reason codes <DisconnectReasons>`.
       See below for exception subclasses tied to specific disconnect
       reasons if you want to customize your handling by reason.

       :param code:
           Disconnect reason, taken from :ref:`disconnect reason
           codes <DisconnectReasons>`
       :param reason:
           A human-readable reason for the disconnect
       :param lang: (optional)
           The language the reason is in
       :type code: `int`
       :type reason: `str`
       :type lang: `str`

    """

class CompressionError(DisconnectError):
    """SSH compression error

       This exception is raised when an error occurs while compressing
       or decompressing data sent on the SSH connection.

       :param reason:
           Details about the compression error
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_COMPRESSION_ERROR, reason, lang)

class ConnectionLost(DisconnectError):
    """SSH connection lost

       This exception is raised when the SSH connection to the remote
       system is unexpectedly lost. It can also occur as a result of
       the remote system failing to respond to keepalive messages or
       as a result of a login timeout, when those features are enabled.

       :param reason:
           Details about the connection failure
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_CONNECTION_LOST, reason, lang)

class HostKeyNotVerifiable(DisconnectError):
    """SSH host key not verifiable

       This exception is raised when the SSH server's host key or
       certificate is not verifiable.

       :param reason:
           Details about the host key verification failure
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_HOST_KEY_NOT_VERIFIABLE, reason, lang)

class IllegalUserName(DisconnectError):
    """SSH illegal user name

       This exception is raised when an error occurs while processing
       the username sent during the SSL handshake.

       :param reason:
           Details about the illegal username
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_ILLEGAL_USER_NAME, reason, lang)

class KeyExchangeFailed(DisconnectError):
    """SSH key exchange failed

       This exception is raised when the SSH key exchange fails.

       :param reason:
           Details about the connection failure
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_KEY_EXCHANGE_FAILED, reason, lang)

class MACError(DisconnectError):
    """SSH MAC error

       This exception is raised when an error occurs while processing
       the message authentication code (MAC) of a message on the SSH
       connection.

       :param reason:
           Details about the MAC error
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_MAC_ERROR, reason, lang)

class PermissionDenied(DisconnectError):
    """SSH permission denied

       This exception is raised when there are no authentication methods
       remaining to complete SSH client authentication.

       :param reason:
           Details about the SSH protocol error detected
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_NO_MORE_AUTH_METHODS_AVAILABLE, reason, lang)

class ProtocolError(DisconnectError):
    """SSH protocol error

       This exception is raised when the SSH connection is disconnected
       due to an SSH protocol error being detected.

       :param reason:
           Details about the SSH protocol error detected
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_PROTOCOL_ERROR, reason, lang)

class ProtocolNotSupported(DisconnectError):
    """SSH protocol not supported

       This exception is raised when the remote system sends an SSH
       protocol version which is not supported.

       :param reason:
           Details about the unsupported SSH protocol version
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_PROTOCOL_ERROR, reason, lang)

class ServiceNotAvailable(DisconnectError):
    """SSH service not available

       This exception is raised when an unexpected service name is
       received during the SSH handshake.

       :param reason:
           Details about the unexpected SSH service
       :param lang: (optional)
           The language the reason is in
       :type reason: `str`
       :type lang: `str`

    """

    def __init__(self, reason: str, lang: str = DEFAULT_LANG):
        super().__init__(DISC_SERVICE_NOT_AVAILABLE, reason, lang)

class ChannelOpenError(Error):
    """SSH channel open error

       This exception is raised by connection handlers to report
       channel open failures.

       :param code:
           Channel open failure reason, taken from :ref:`channel open
           failure reason codes <ChannelOpenFailureReasons>`
       :param reason:
           A human-readable reason for the channel open failure
       :param lang:
           The language the reason is in
       :type code: `int`
       :type reason: `str`
       :type lang: `str`

    """

class ChannelListenError(Exception):
    """SSH channel listen error

       This exception is raised to report failures in setting up
       remote SSH connection listeners.

       :param details:
           Details of the listen failure
       :type details: `str`

    """

class PasswordChangeRequired(Exception):
    """SSH password change required

       This exception is raised during password validation on the
       server to indicate that a password change is required. It
       should be raised when the password provided is valid but
       expired, to trigger the client to provide a new password.

       :param prompt:
           The prompt requesting that the user enter a new password
       :param lang:
           The language that the prompt is in
       :type prompt: `str`
       :type lang: `str`

    """

    def __init__(self, prompt: str, lang: str = DEFAULT_LANG):
        super().__init__('Password change required: %s' % prompt)
        self.prompt = prompt
        self.lang = lang

class BreakReceived(Exception):
    """SSH break request received

       This exception is raised on an SSH server stdin stream when the
       client sends a break on the channel.

       :param msec:
           The duration of the break in milliseconds
       :type msec: `int`

    """

    def __init__(self, msec: int):
        super().__init__('Break for %s msec' % msec)
        self.msec = msec

class SignalReceived(Exception):
    """SSH signal request received

       This exception is raised on an SSH server stdin stream when the
       client sends a signal on the channel.

       :param signal:
           The name of the signal sent by the client
       :type signal: `str`

    """

    def __init__(self, signal: str):
        super().__init__('Signal: %s' % signal)
        self.signal = signal

class SoftEOFReceived(Exception):
    """SSH soft EOF request received

       This exception is raised on an SSH server stdin stream when the
       client sends an EOF from within the line editor on the channel.

    """

    def __init__(self) -> None:
        super().__init__('Soft EOF')

class TerminalSizeChanged(Exception):
    """SSH terminal size change notification received

       This exception is raised on an SSH server stdin stream when the
       client sends a terminal size change on the channel.

       :param width:
           The new terminal width
       :param height:
           The new terminal height
       :param pixwidth:
           The new terminal width in pixels
       :param pixheight:
           The new terminal height in pixels
       :type width: `int`
       :type height: `int`
       :type pixwidth: `int`
       :type pixheight: `int`

    """

    def __init__(self, width: int, height: int, pixwidth: int, pixheight: int):
        super().__init__('Terminal size change: (%s, %s, %s, %s)' %
                         (width, height, pixwidth, pixheight))
        self.width = width
        self.height = height
        self.pixwidth = pixwidth
        self.pixheight = pixheight

_disc_error_map = {
    DISC_PROTOCOL_ERROR: ProtocolError,
    DISC_KEY_EXCHANGE_FAILED: KeyExchangeFailed,
    DISC_MAC_ERROR: MACError,
    DISC_COMPRESSION_ERROR: CompressionError,
    DISC_SERVICE_NOT_AVAILABLE: ServiceNotAvailable,
    DISC_PROTOCOL_VERSION_NOT_SUPPORTED: ProtocolNotSupported,
    DISC_HOST_KEY_NOT_VERIFIABLE: HostKeyNotVerifiable,
    DISC_CONNECTION_LOST: ConnectionLost,
    DISC_NO_MORE_AUTH_METHODS_AVAILABLE: PermissionDenied,
    DISC_ILLEGAL_USER_NAME: IllegalUserName
}

def construct_disc_error(code: int, reason: str, lang: str) -> DisconnectError:
    """Map disconnect error code to appropriate DisconnectError exception"""

    try:
        return _disc_error_map[code](reason, lang)
    except KeyError:
        return DisconnectError(code, '%s (error %d)' % (reason, code), lang)


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import django.core.validators

class Migration(migrations.Migration):

    dependencies = [
        ('app', '0016_auto_20150828_0735'),
    ]

    operations = [
        migrations.AlterField(
            model_name='object',
            name='background_transparency',
            field=models.IntegerField(default=100, null=True, blank=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)]),
            preserve_default=True,
        ),
    ]


# -*- coding: utf-8 -*-
"""
无签名版本
"""
import re
import json
from scrapy import Spider
from scrapy.http import Request
from douyin_app.docs.conf import HEADER

class DouyinIdolVideoSpider(Spider):
    name = "idol_douyin_video"

    idol_url = ''
    video_list_url = 'https://api.amemv.com/aweme/v1/aweme/post/?user_id={}&max_cursor={}&count=20&device_id=39681429254&ac=wifi&channel=xiaomi&aid=1128&app_name=aweme'

    max_cursor = 0
    uid = None

    def __init__(self, url):
        super(DouyinIdolVideoSpider, self).__init__()
        self.idol_url = url

    def start_requests(self):
        try:
            self.uid = re.findall(r'user/(\d+)', self.idol_url)[0]
            self.logger.info('解析到idol信息{}(•‾̑⌣‾̑•)✧˖°'.format(self.uid))
            yield self.start_get_video_list(self.uid)
        except Exception:
            self.logger.error('解析不到视频信息,,Ծ‸Ծ,,')

    def start_get_video_list(self, uid):
        url = self.video_list_url.format(uid, self.max_cursor)
        header = HEADER
        return Request(url=url, headers=header, callback=self.get_video_list)

    def start_get_video(self, url, desc):
        url = url
        header = HEADER
        return Request(url=url, headers=header, callback=self.get_video, meta={'desc': desc})

    def get_video_list(self, response):
        content = response.body.decode('utf-8')
        content = json.loads(content)
        video_list = content.get('aweme_list')
        if video_list:
            for video in video_list:
                download_url = video.get('video').get('play_addr_lowbr').get('url_list')[0]
                desc = video.get('desc')
                self.logger.info('解析到下载链接()(•‾̑⌣‾̑•)✧˖°', format(download_url))
                yield self.start_get_video(download_url, desc)
        if content.get('has_more'):
            self.max_cursor = content.get('max_cursor')
            yield self.start_get_video_list(self.uid)

    def get_video(self, response):
        desc = response.meta.get('desc')
        content = response.body
        with open('./douyin_app/videos/{}.mp4'.format(desc), 'wb') as f:
            f.write(content)
        self.logger.info('下载完成๑乛◡乛๑')


"""
Original multirc format:
    {
        data: [
            {
                id: str,
                paragraph: {
                    text: {
                    },
                    questions: [
                        {
                            question: str,
                            sentences_used: [ int, ],
                            idx: int,
                            multisent: bool // don't know what this is
                            answers: [
                                {
                                    text: str,
                                    isAnswer: bool,
                                    scores: {} //empty
                                },
                                ...

                            ]
                        },
                        ...
                    ]
                }
            },
            ...
        ]
    }
"""
import json
import argparse
from pathlib import Path
from typing import Dict, List, Mapping, Generator, Optional, Union
from copy import deepcopy
import itertools
import re
import logging
from .reader import DatasetReader
from .types import (Sample, SingleQuestionSample,
                    SingleQuestionSingleOptionSample, NLIWithOptionsSample,
                    PureNLISample)

from dataclasses import dataclass, asdict
logger = logging.getLogger(__name__)

@dataclass
class OriginalMultircSample(Sample):
    paragraph: Dict

html_tags = re.compile(r'<[^>]+>')
setence_tags = re.compile(r'Sent\s+\d+:')
html_plus_sentence_tags = re.compile(r"<[^>]+>|Sent\s+\d+:")

class MultircReader(DatasetReader):
    def __init__(self,
                 input_type: str = 'OriginalMultircSample',
                 output_type: str = 'SingleQuestionSingleOptionSample'):
        self.input_type = input_type
        self.output_type = output_type

    def _read_data(self, path: Path) -> Dict:
        with open(path) as f:
            samples = json.load(f)

        return samples

    def read(self, path: Path,
             return_dict: bool = False) -> List[Union[Sample, Dict]]:

        if self.input_type == 'OriginalMultircSample':

            def reader_func(p: Path) -> List[Sample]:
                samples = ((self._read_data(p))['data'])
                # remove html

                for s in samples:
                    s['paragraph']['text'] = html_plus_sentence_tags.sub(
                        '', s['paragraph']['text'])

                return [OriginalMultircSample(**x) for x in samples]
        else:
            raise ValueError(f"input_type {self.input_type} not supported")

        if self.output_type == 'SingleQuestionSingleOptionSample':

            def sample_converter(
                    x: OriginalMultircSample) -> OriginalMultircSample:

                return x  # do nothing

            def aggregate_converter(
                    x: List[OriginalMultircSample]
            ) -> List[SingleQuestionSingleOptionSample]:
                all_res = []

                for s in x:
                    para = s.paragraph['text']

                    for q in s.paragraph['questions']:
                        for ans_i, a in enumerate(q['answers']):

                            all_res.append(
                                SingleQuestionSingleOptionSample(
                                    id=s.id + f"_{q['idx']}" + f"_{ans_i}",
                                    article=para,
                                    question=q['question'],
                                    option=a['text'],
                                    label=int(a['isAnswer'])))

                return all_res

        else:
            raise ValueError(f"outpu_type {self.output_type} not supported")

        input_samples = [sample_converter(s) for s in reader_func(path)]
        output_samples = aggregate_converter(input_samples)

        if return_dict:
            return [s.__dict__ for s in output_samples]
        else:
            return output_samples


from fastapi import APIRouter, HTTPException, status, Query
from datetime import timedelta
from datetime import datetime
from github import Github
import random

router = APIRouter()

github = Github()

@router.get("/repo/health")
def repo_health_check():
    return {"status": "OK"}

# return each contributor with their number of commits in the last week
@router.get("/repo/contributors")
def return_individual_contributions(repo_name:str):
    try:
        repository = github.get_repo(repo_name)
    except:
        raise HTTPException(status_code=404, detail="Repository not found")

    contributor_list = repository.get_stats_contributors()

    contributors_info = {}

    counter = 0
    for contributor in reversed(contributor_list):
        if counter == 6:
            break
        weekly_contribution = contributor.weeks
        contributors_info[contributor.author.name] = weekly_contribution[-1].c
        counter = counter + 1

    return contributors_info

# return total number of commits so for in the week (commits since the most recent monday)
@router.get("/repo/totalweeklycommits")
def return_weekly_commits(repo_name : str):
    try:
        repository = github.get_repo(repo_name)
    except:
        raise HTTPException(status_code=404, detail="Repository not found")

    today = datetime.datetime.now()
    most_recent_monday = today - timedelta(days=today.weekday())
    commits=repository.get_commits(since=most_recent_monday)

    return {
        "Commits in the last week":commits.totalCount,
        "Commits since": str(most_recent_monday.date())
    }

# return total number of commits in the past week
@router.get("/repo/commits")
def return_weekly_commits(repo_name : str, start : datetime = Query(None), end : datetime = Query(None)):

    # the url that the user should be passing in is something like ?start=2022-01-01&end=2022-01-31

    # parses the dates passed in to a datetime object. This is the format that the github api uses
    start_date = datetime.datetime.strptime(start, "%Y-%m-%d")
    end_date = datetime.datetime.strptime(end, "%Y-%m-%d")

    repository = github.get_repo(repo_name)
    commits=repository.get_commits(since=start_date, until=end_date)

    return {"commits":commits.totalCount}

# return percentage increase for commits in the past week
@router.get("/repo/commitsincrease")
def calculate_commits_increase(repo_name:str):

    #find commits this week
    today = datetime.datetime.now()
    most_recent_monday = today - timedelta(days=today.weekday())
    last_weeks_monday = most_recent_monday - timedelta(days=7)

    try:
        repository = github.get_repo(repo_name)
    except:
        raise HTTPException(status_code=404, detail="Repository not found")

    this_week = repository.get_commits(since=most_recent_monday)
    commits_this_week = this_week.totalCount

    #find commits last week
    last_week = repository.get_commits(since=last_weeks_monday, until=most_recent_monday)
    commits_last_week = last_week.totalCount

    #find percentage increase
    percentage = ""
    if commits_last_week == 0:
        commits_this_week * 100
        percentage = str(commits_this_week) + "%"
    else:
        difference = commits_this_week - commits_last_week
        difference = difference / commits_last_week
        difference = round(difference*100, 1)
        percentage = str(difference) + "%"

    return{
        "Increase in commits in the past week": percentage,
        "Last week's date":str(last_weeks_monday.date())
    }

# @router.get("/issues")
# def get_repo_issues(repo_name : str):
#     repository = github.get_repo(repo_name)

#     issue_list = {}
#     for issue in repository.get_issues():
#         currentIssue = {
#             "Assignee": issue.assignee.assignee,
#             "Id": issue.id,
#             "Commit Id": issue.commit_id,
#             "Event": issue.event,
#             "Date created": issue.created_at,
#         }
#         issue_list[issue.assignee.assignee] = currentIssue

#     return issue_list

@router.get("/dummy/repo/commits")
def dummy_repo_commits(start: str, end: str):
    # generate a random number of commits based on the start and end dates
    # this is just a dummy method for testing purposes

    # get the number fo days between the start and end dates
    start_date = datetime.strptime(start, "%Y-%m-%d")
    end_date = datetime.strptime(end, "%Y-%m-%d")
    delta = end_date - start_date
    print(delta)

    # generate a random number of commits based on the number of days
    num_commits = random.randint(0, delta.days)

    authors = ['John', 'Jane', 'Bob', 'Alice', 'Joe', 'Mary', 'Tom', 'Sally', 'Sue', 'Sam']

    last_num_commits = random.randint(0, delta.days)

    #  get thepercentage change in commits
    percentage = 0
    if last_num_commits == 0:
        num_commits * 100
        percentage = num_commits
    else:
        difference = num_commits - last_num_commits
        difference = difference / last_num_commits
        difference = round(difference*100, 1)
        percentage = difference

    print(percentage)

    return {
        "num_commits": num_commits,
        "percent_change": percentage,
        "authors": authors,
        "start_date": start,
        "end_date": end,
    }

@router.get("/dummy/repo/bugs")
def dummy_repo_commits(start: str, end: str):
    start_date = datetime.strptime(start, "%Y-%m-%d")
    end_date = datetime.strptime(end, "%Y-%m-%d")
    delta = end_date - start_date

    monthly_bug_rate = []

    random.seed(0)

    x = 0

    for i in range(delta.days):
        # values have to be positive
        dx = random.gauss(0, 1)
        if dx < 0:
            dx = 0
        x += dx
        monthly_bug_rate.append(int(x))

    print(monthly_bug_rate)

    return {
        "num_bugs": len(monthly_bug_rate),
        "num_bugs_prev": 10,
        "percent_change": 20,
        "start_date": start,
        "end_date": end,
        "values": monthly_bug_rate,

    }


from django.shortcuts import render
from django.views.generic import View
from .models import *

# Create your views here.

class ProjectsList(View):
    def get(self,request):
        try:
            us = User.objects.get(username=request.user)
        except:
            us = None
        projects = Project.objects.filter(user=us)
        return render(request,'taskmain/projects_list.html',{'projects':projects,'us':us})

class ProjectDetail(View):
    def get(self,request,slug):
        project = Project.objects.get(slug=slug)
        return render(request,'taskmain/project_detail.html',{'project':project})


from selenium import webdriver

import json
import traceback
import urllib.request

def parse_page(driver):
    script_clue = "q(\"talkPage.init\","
    try:
        for script in driver.find_elements_by_tag_name("script"):
            content = script.get_attribute("innerHTML")
            if content.startswith(script_clue):
                content = content.lstrip(script_clue).rstrip(")")
                json_content = json.loads(content)
                title = json_content["__INITIAL_DATA__"]["name"]
                download_url = json_content["__INITIAL_DATA__"]["talks"][0]["downloads"]["subtitledDownloads"]["en"]["high"]
                return title, download_url
    except Exception:
        print(traceback.format_exc())
        print("Unable to parse page. Stopping.")
        exit(-1)

def main(url):
    print("Processing URL %s..." % url)
    options = webdriver.FirefoxOptions()
    options.add_argument("--headless")
    driver = webdriver.Firefox(firefox_options=options)
    driver.get(url)
    title, download_url = parse_page(driver)
    print("TED talk: {}\nDownload URL: {}".format(title, download_url))
    file_title = title + ".mp4"
    print("Downloading file {}...".format(file_title))
    try:
        urllib.request.urlretrieve(download_url, file_title)
        print("Download completed.")
    except Exception:
        print(traceback.format_exc())
        print("Unable to download video. Stopping.")
        exit(-1)

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(
        description="A simple tool to download TED talks via CLI.")
    parser.add_argument("-t", "--talk", type=str, required=True,
                        help="Link to TED talk")
    args = parser.parse_args()
    main(args.talk)


import pandas as pd
from sklearn import model_selection
import numpy as np
from sklearn import datasets

def create_k_folds():
    # csv with image id, image location and image label.
    df = pd.read_csv("train.csv")

    # create a new column called kfold and fill it with -1
    df["kfold"] = -1

    # the next step is to randomize the rows of the data
    df = df.sample(frac=1).reset_index(drop=True)

    # initiate the kfold class from model_selection module
    kf = model_selection.KFold(n_splits=5)

    # fill the new kfold column
    for fold, (trn_, val_) in enumerate(kf.split(X=df)):
        df.loc[val_, "kfold"] = fold

    # save the new csv with kfold column
    df.to_csv("train_folds.csv", index=False)

def create_stratified_k_folds():
    # csv with image id, image location and image label.
    df = pd.read_csv("train.csv")

    # create a new column called kfold and fill it with -1
    df["kfold"] = -1

    # the next step is to randomize the rows of the data
    df = df.sample(frac=1).reset_index(drop=True)

    # fetch targets
    y = df.target.values

    # initiate the kfold class from model_selection module
    kf = model_selection.StratifiedKFold(n_splits=5)

    # fill the new kfold column
    for fold, (trn_, val_) in enumerate(kf.split(X=df, y=y)):
        df.loc[val_, "kfold"] = fold

    # save the new csv with kfold column
    df.to_csv("train_folds.csv", index=False)

def create_stratified_k_fold_for_regression(data):
    # create a new column called kfold and fill it with -1
    data["kfold"] = -1

    # the next step is to randomize the rows of the data
    data = data.sample(frac=1).reset_index(drop=True)

    # calculate the number of bins by Sturge's rule
    # I take the floor of the value, you can also just round it.
    num_bins = int(np.floor(1 + np.log2(len(data))))

    # bin tagets
    data.loc[:, "bins"] = pd.cut(data["target"], bins=num_bins, labels=False)

    # initiate the kfold class from model_selection module
    kf = model_selection.StratifiedKFold(n_splits=5)

    # fill the new kfold column note that, instead of targets, we use bins!
    for f, (t_, v_) in enumerate(kf.split(X=data, y=data.bins.values)):
        data.loc[v_, "kfold"] = f

    # drop the bins column
    data = data.drop("bins", axis=1)

    # return data frame with folds
    return data

if __name__ == '__main__':
    # We create a sample dataset with 1500 samples and 100 features and 1 target
    X, y = datasets.make_regression(n_samples=1500, n_features=100, n_targets=1)

    # create a data frame out of our numpy arrays
    df = pd.DataFrame(X, columns=[f"f_{i}" for i in range(X.shape[1])])

    df.loc[:, "target"] = y

    # create folds
    df = create_stratified_k_fold_for_regression(data=df)
    a = 0


#!/usr/bin/env python3
"""Resolve docker container's name into IPv4 address"""

import docker
from ipaddress import ip_address, IPv4Address, IPv6Address
from threading import Thread
from twisted.internet import reactor, defer
from twisted.names import client, dns, server

LISTEN_ADDRESS = "127.0.0.1"
DNS_PORT = 53

class DockerResolver(client.Resolver):
    """Resolve container name into IP address."""
    def __init__(self, dockerClient, servers=None):
        super().__init__(resolv=None, servers=servers)
        self.dockerClient = dockerClient
        self.runningContainers = {}
        for c in dockerClient.containers.list():
            containerName = c.attrs["Name"][1:]
            containerNetworks = c.attrs["NetworkSettings"]["Networks"]
            for k, v in containerNetworks.items():
                containerIPv4 = v["IPAddress"]
                containerIPv6 = v["GlobalIPv6Address"]
                if not containerIPv6:
                    containerIPv6 = None

                shouldAddContainer = False
                if (("Health" in c.attrs["State"] and
                        c.attrs["State"]["Health"]["Status"] == "healthy") or
                        "Health" not in c.attrs["State"]):
                    shouldAddContainer = True
                if shouldAddContainer:
                    self.addContainer(containerName,
                                      containerIPv4,
                                      containerIPv6)

    def addContainer(self, containerName, containerIPv4, containerIPv6=None):
        if containerName not in self.runningContainers:
            self.runningContainers[containerName] = []
        self.runningContainers[containerName].append(containerIPv4)
        if containerIPv6:
            self.runningContainers[containerName].append(containerIPv6)

    def removeContainer(self, containerName):
        self.runningContainers.pop(containerName, None)

    def __lookup(self, query, timeout=None, type="A"):
        allowedTypes = ("A", "AAAA")
        if type not in allowedTypes:
            raise ValueError
        domain = query.decode()
        if domain in self.runningContainers:
            answers = []
            authority = []
            additional = []
            for address in self.runningContainers[domain]:
                if ((type == "A"
                        and not isinstance(ip_address(address), IPv4Address))
                    or (type == "AAAA"
                        and not isinstance(ip_address(address), IPv6Address))):
                    continue
                record = getattr(dns, "Record_%s" % type)
                p = record(address=address.encode())
                dnsType = getattr(dns, "%s" % type)
                answer = dns.RRHeader(name=query, payload=p, type=dnsType)
                answers.append(answer)
            return defer.succeed((answers, authority, additional))
        else:
            return None

    def lookupAddress(self, query, timeout=None):
        response = self.__lookup(query, timeout, "A")
        if response:
            return response
        else:
            return super().lookupAddress(query, timeout)

    def lookupIPV6Address(self, query, timeout=None):
        response = self.__lookup(query, timeout, "AAAA")
        if response:
            return response
        else:
            return super().lookupIPV6Address(query, timeout)

    def __findContainerByPTRQuery(self, PTRQuery):
        query = PTRQuery.decode().rstrip(".")

        if query.endswith(".in-addr.arpa"):
            ip_list = query.rstrip(".in-addr.arpa").split(".")
            i = 0
            ipQuery = ""
            while i < len(ip_list):
                i += 1
                ipQuery += ip_list[-i]
                if i != len(ip_list):
                    ipQuery += "."
            ipQuery = ip_address(ipQuery)
        elif query.endswith(".ip6.arpa"):
            ip_list = query.rstrip(".ip6.arpa")[::-1].split(".")
            i = 0
            ipQuery = ""
            while i < len(ip_list):
                ipQuery += ip_list[i]
                i += 1
                if i % 4 == 0 and i != len(ip_list):
                    ipQuery += ":"
            ipQuery = ip_address(ipQuery)
        else:
            return None

        for containerName, IPs in self.runningContainers.items():
            for ip in IPs:
                if ipQuery == ip_address(ip):
                    return containerName

        return None

    def lookupPointer(self, query, timeout=None):
        answers = []
        authority = []
        additional = []

        containerName = self.__findContainerByPTRQuery(query)

        if containerName is None:
            return super().lookupPointer(query, timeout)

        p = dns.Record_PTR(name=containerName)
        answer = dns.RRHeader(name=query, payload=p, type=dns.PTR)
        answers.append(answer)
        return defer.succeed((answers, authority, additional))

class EventsListener(Thread):
    """Listen on start and die events."""
    def __init__(self, resolver):
        super().__init__()
        self.resolver = resolver
        self.eventListener = None

    def run(self):
        self.eventListener = self.resolver.dockerClient.events(
                            filters={"event": ["connect",
                                               "disconnect",
                                               "health_status"]},
                            decode=True)
        for e in self.eventListener:
            callback_prefix = e["Action"]
            if "health_status:" in e["Action"]:
                callback_prefix = e["Action"][:(e["Action"].index(':'))]
            callback = getattr(self, callback_prefix + "Callback")
            callback(e)

    def join(self, timeout=None):
        self.eventListener.close()
        super().join(timeout)

    def __add_container(self, container):
        containerName = container["Name"].lstrip('/')
        containerNetworks = container["NetworkSettings"]["Networks"]

        for k, v in containerNetworks.items():
            containerIPv4 = v["IPAddress"]
            containerIPv6 = v["GlobalIPv6Address"]
            shouldAddContainer = True
            # ContainerNetworks contains all the networks. So if we connect a
            # second (or third) network after container started, we fire
            # connect event several times. This means we should ensure that
            # containerName appears once in resolver.runningContainers list.
            if containerName in self.resolver.runningContainers:
                thisContainer = self.resolver.runningContainers[containerName]
                if containerIPv4 in thisContainer:
                    shouldAddContainer = False

            if not containerIPv6:
                containerIPv6 = None

            if shouldAddContainer:
                self.resolver.addContainer(containerName,
                                           containerIPv4,
                                           containerIPv6)

    def connectCallback(self, event):
        containerID = event["Actor"]["Attributes"]["container"]
        api = self.resolver.dockerClient.api
        container = api.inspect_container(containerID)

        if ("Health" not in container["State"] or
                container["State"]["Health"]["Status"] == "healthy"):
            self.__add_container(container)

    def disconnectCallback(self, event):
        containerID = event["Actor"]["Attributes"]["container"]
        api = self.resolver.dockerClient.api
        try:
            container = api.inspect_container(containerID)
            containerName = container["Name"].lstrip('/')
            self.resolver.removeContainer(containerName)
        except docker.errors.NotFound:
            pass

    def health_statusCallback(self, event):
        api = self.resolver.dockerClient.api
        container = api.inspect_container(event["id"])

        if ("Health" in container["State"] and
                container["State"]["Health"]["Status"] == "healthy"):
            self.__add_container(container)

class DockerDNS():
    """Start and stop DockerDNS Service"""
    def __init__(self, port=None, listenAddress=None, forwarders=None):
        if not isinstance(forwarders, list):
            raise TypeError

        self.port = port
        self.listenAddress = listenAddress
        self.forwarders = forwarders

        self.eventsListener = None

        self.udp_listener = None
        self.tcp_listener = None

        if self.port is None:
            self.port = DNS_PORT
        if self.listenAddress is None:
            self.listenAddress = LISTEN_ADDRESS

    def start(self):
        """Configure and execute the DNS server."""
        dockerClient = docker.from_env()
        resolver = DockerResolver(dockerClient=dockerClient,
                                  servers=self.forwarders)

        self.eventsListener = EventsListener(resolver)
        self.eventsListener.start()
        factory = server.DNSServerFactory(clients=[resolver])
        protocol = dns.DNSDatagramProtocol(controller=factory)
        self.udp_listener = reactor.listenUDP(port=self.port,
                                              protocol=protocol,
                                              interface=self.listenAddress)
        self.tcp_listener = reactor.listenTCP(port=self.port,
                                              factory=factory,
                                              interface=self.listenAddress)
        reactor.run()

    def clean(self):
        """Clean all the resources"""
        self.stop()
        self.eventsListener.join()

    def stop(self):
        """Stop the reactor if running"""
        if reactor.running:
            self.udp_listener.stopListening()
            self.tcp_listener.stopListening()
            reactor.stop()


from django.db import models

# Create your models here.
class Movie(models.Model):
    id = models.BigAutoField(
        primary_key=True
    )
    name = models.CharField(
        max_length=250,
        verbose_name="Moive name"
    )
    desc = models.TextField(
        verbose_name="Description"
    )
    year = models.PositiveBigIntegerField(
        verbose_name="release year"
    )
    image = models.ImageField(
        upload_to='gallery',
        verbose_name="Images"
    )
    director = models.CharField(
        max_length=100
    )


import json
import os
import traceback

from discord import AllowedMentions, Embed, Forbidden
from discord.ext import commands

class Core(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.db = self.bot.db

    async def push_link_json(self, guild) -> None:
        data = {}
        for invite in (await guild.invites()):
            data[f'{invite.code}'] = f'{invite.uses}'
        file = open(f'./data/{guild.id}.json', 'w')
        json.dump(data, file, indent=4)

    @commands.Cog.listener()
    async def on_invite_create(self, invite):
        await self.push_link_json(invite.guild)

    @commands.Cog.listener()
    async def on_invite_remove(self, invite):
        await self.push_link_json(invite.guild)

    @commands.Cog.listener()
    async def on_guild_join(self, guild):
        await self.push_link_json(guild)

    @commands.Cog.listener()
    async def on_member_join(self, member):

        guild_data = self.db.list_invite_link(member.guild.id)
        if not guild_data:
            return

        data = {}
        for invite in (await member.guild.invites()):
            data[f'{invite.code}'] = f'{invite.uses}'
        if os.path.exists(f'./data/{member.guild.id}.json'):
            with open(f'./data/{member.guild.id}.json', 'r', encoding='UTF-8') as config:
                g_data = json.load(config)
        else:
            return

        code = list(dict(data.items() - g_data.items()).items())[0]
        link_role = self.db.fetch_invite_role(member.guild.id, code[0])

        if not link_role:
            return

        role = member.guild.get_role(link_role[0])
        if role:
            try:
                await member.add_roles(role)
            except Forbidden:
                return
        await self.push_link_json(member.guild)

async def setup(bot):
    await bot.add_cog(Core(bot))


import csv
from dataclasses import dataclass, field
from itertools import count

from ..configs import Configs
from ..utils import add_bytes, stringify
from .actions import MONSTER_ACTIONS, Action
from .autoabilities import AUTOABILITIES
from .characters import CHARACTERS, Character
from .constants import (
    Element,
    ElementalAffinity,
    EquipmentSlots,
    EquipmentType,
    GameVersion,
    Rarity,
    Stat,
    Status,
)
from .file_functions import get_resource_path
from .items import ITEMS, ItemDrop
from .text_characters import TEXT_CHARACTERS

@dataclass
class Monster:
    name: str
    stats: dict[Stat, int]
    elemental_affinities: dict[Element, ElementalAffinity]
    status_resistances: dict[Status, int]
    poison_tick_damage: int
    zanmato_level: int
    armored: bool
    undead: bool
    auto_statuses: list[Status]
    gil: int
    ap: dict[str, int]
    item_1: dict[str, int | dict[Rarity, ItemDrop | None]]
    item_2: dict[str, int | dict[Rarity, ItemDrop | None]]
    steal: dict[str | Rarity, int | ItemDrop | None]
    bribe: dict[str, int | ItemDrop | None]
    equipment: dict[str, int | list | dict[Character, list[int]]]
    actions: dict[str, Action]
    zones: list[str] = field(default_factory=list)

    def __str__(self) -> str:
        return self.name

def _get_prize_structs(file_path: str) -> dict[str, list[int]]:
    """Retrieves the prize structs for enemies."""
    absolute_file_path = get_resource_path(file_path)
    with open(absolute_file_path) as file_object:
        file_reader = csv.reader(file_object, delimiter=",")
        monsters_data = {}
        for line in file_reader:
            prize_struct = [int(value, 16) for value in line]
            # gets the name of the monster from the prize struct itself
            # name is null (0x00) terminated
            monster_name = ""
            for character_id in prize_struct[408:430]:
                if character_id == 0:
                    break
                monster_name += TEXT_CHARACTERS[character_id]
            monster_name = stringify(monster_name)
            # if the name is already in the dictionary
            # appends it with an underscore and a number
            # from 2 to 8
            if monster_name in monsters_data:
                for i in count(2):
                    new_name = f"{monster_name}_{i}"
                    if new_name not in monsters_data:
                        monsters_data[new_name] = prize_struct
                        break
            else:
                monsters_data[monster_name] = prize_struct
    return monsters_data

def _patch_prize_structs_for_hd(
    prize_structs: dict[str, list[int]],
) -> dict[str, list[int]]:
    """Apply changes made in the HD version to the prize structs."""

    def patch_abilities(
        monster_name: str,
        abilities: tuple[int, int, int, int, int, int, int],
        equipment_type: EquipmentType = EquipmentType.WEAPON,
    ) -> None:
        """Modifies ability values 1-7 of every character's weapon
        or armor ability array.
        """
        # base address for abilities in the prize struct
        base_address = 178
        type_offset = 0 if equipment_type == EquipmentType.WEAPON else 1
        # place the abilities values at the correct offsets
        for owner_index in range(7):
            offset = (type_offset + (owner_index * 2)) * 16
            for slot in range(7):
                slot_offset = (slot + 1) * 2
                address = base_address + offset + slot_offset
                prize_structs[monster_name][address] = abilities[slot]

    # in the HD version equipment droprates were modified
    # from 8/255 to 12/255 for these enemies
    monster_names = (
        "condor",
        "dingo",
        "water_flan",
        "condor_2",
        "dingo_2",
        "water_flan_2",
        "dinonix",
        "killer_bee",
        "yellow_element",
        "worker",
        "vouivre_2",
        "raldo_2",
        "floating_eye",
        "ipiria",
        "mi'ihen_fang",
        "raldo",
        "vouivre",
        "white_element",
        "funguar",
        "gandarewa",
        "lamashtu",
        "raptor",
        "red_element",
        "thunder_flan",
        "bite_bug",
        "bunyip",
        "garm",
        "simurgh",
        "snow_flan",
        "bunyip_2",
        "aerouge",
        "buer",
        "gold_element",
        "kusariqqu",
        "melusine",
        "blue_element",
        "iguion",
        "murussu",
        "wasp",
        "evil_eye",
        "ice_flan",
        "mafdet",
        "snow_wolf",
        "guado_guardian_2",
        "alcyone",
        "mech_guard",
        "mushussu",
        "sand_wolf",
        "bomb_2",
        "evil_eye_2",
        "guado_guardian_3",
        "warrior_monk",
        "warrior_monk_2",
        "aqua_flan",
        "bat_eye",
        "cave_iguion",
        "sahagin_2",
        "swamp_mafdet",
        "sahagin_3",
        "flame_flan",
        "mech_scouter",
        "mech_scouter_2",
        "nebiros",
        "shred",
        "skoll",
        "flame_flan",
        "nebiros",
        "shred",
        "skoll",
        "dark_element",
        "imp",
        "nidhogg",
        "yowie",
    )
    for monster_name in monster_names:
        prize_structs[monster_name][139] = 12

    # all the enemies that have ability arrays modified in the HD version
    # besaid
    patch_abilities("dingo", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("condor", (0, 0, 0, 0, 126, 126, 126))
    patch_abilities("water_flan", (42, 42, 42, 42, 125, 125, 125))
    patch_abilities("dingo_2", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("condor_2", (0, 0, 0, 0, 126, 126, 126))
    patch_abilities("water_flan_2", (42, 42, 42, 42, 125, 125, 125))

    # kilika
    patch_abilities("dinonix", (38, 42, 38, 30, 126, 126, 126))
    patch_abilities("killer_bee", (38, 42, 34, 30, 126, 126, 126))
    patch_abilities("yellow_element", (38, 38, 38, 38, 125, 125, 125))

    # luca
    patch_abilities("vouivre_2", (38, 42, 34, 30, 124, 124, 124))

    # mi'ihen
    patch_abilities("raldo_2", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("bomb", (30, 30, 30, 30, 30, 30, 125))
    patch_abilities("dual_horn", (67, 30, 30, 30, 30, 127, 127))
    patch_abilities("floating_eye", (38, 42, 34, 30, 99, 126, 126))
    patch_abilities("ipiria", (38, 42, 38, 30, 126, 126, 126))
    patch_abilities("mi'ihen_fang", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("raldo", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("vouivre", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("white_element", (34, 34, 34, 34, 125, 125, 125))

    # mushroom rock road
    patch_abilities("gandarewa", (38, 38, 38, 38, 125, 125, 125))
    patch_abilities("lamashtu", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("raptor", (38, 42, 38, 30, 126, 126, 126))
    patch_abilities("red_element", (30, 30, 30, 30, 125, 125, 125))
    patch_abilities("thunder_flan", (38, 38, 38, 38, 125, 125, 125))

    # djose highroad
    patch_abilities("bite_bug", (38, 42, 34, 30, 126, 126, 126))
    patch_abilities("bunyip", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("garm", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("simurgh", (0, 0, 0, 0, 126, 126, 126))
    patch_abilities("snow_flan", (34, 34, 34, 34, 125, 125, 125))

    # moonflow
    patch_abilities("bunyip_2", (38, 42, 34, 30, 124, 124, 124))

    # thunder plains
    patch_abilities("aerouge", (38, 38, 38, 38, 125, 125, 125))
    patch_abilities("buer", (38, 42, 34, 30, 99, 126, 126))
    patch_abilities("gold_element", (38, 38, 38, 38, 125, 125, 125))
    patch_abilities("kusariqqu", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("melusine", (38, 42, 38, 30, 126, 126, 126))

    # macalania woods
    patch_abilities("blue_element", (42, 42, 42, 42, 125, 125, 125))
    patch_abilities("chimera", (104, 104, 103, 103, 103, 103, 125))
    patch_abilities("iguion", (38, 42, 38, 30, 126, 126, 126))
    patch_abilities("murussu", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("wasp", (38, 42, 34, 30, 126, 126, 126))

    # lake macalania
    patch_abilities("evil_eye", (38, 42, 34, 30, 99, 126, 126))
    patch_abilities("ice_flan", (34, 34, 34, 34, 125, 125, 125))
    patch_abilities("mafdet", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("snow_wolf", (38, 42, 34, 30, 124, 124, 124))

    # bikanel
    patch_abilities("alcyone", (0, 0, 0, 0, 126, 126, 126))
    patch_abilities("mushussu", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("sand_wolf", (38, 42, 34, 30, 124, 124, 124))

    # home
    patch_abilities("bomb_2", (30, 30, 30, 30, 30, 30, 125))
    patch_abilities("chimera_2", (104, 104, 103, 103, 103, 103, 125))
    patch_abilities("dual_horn_2", (67, 67, 67, 30, 30, 127, 127))
    patch_abilities("evil_eye_2", (38, 42, 34, 30, 99, 126, 126))

    # via purifico
    patch_abilities("aqua_flan", (42, 42, 42, 42, 125, 125, 125))
    patch_abilities("bat_eye", (38, 42, 34, 30, 99, 126, 126))
    patch_abilities("cave_iguion", (38, 42, 38, 30, 126, 126, 126))
    patch_abilities("swamp_mafdet", (38, 42, 34, 30, 124, 124, 124))

    # calm lands
    patch_abilities("chimera_brain", (104, 104, 104, 104, 103, 103, 125))
    patch_abilities("flame_flan", (30, 30, 30, 30, 125, 125, 125))
    patch_abilities("nebiros", (38, 42, 34, 30, 126, 126, 126))
    patch_abilities("shred", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("skoll", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("defender_x", (100, 99, 99, 99, 99, 99, 124))

    # cavern of the stolen fayth
    patch_abilities("dark_element", (42, 30, 30, 34, 125, 125, 125))
    patch_abilities("defender", (99, 99, 99, 99, 98, 98, 124))
    patch_abilities("ghost", (104, 104, 104, 103, 103, 103, 125))
    patch_abilities("imp", (38, 38, 38, 38, 125, 125, 125))
    patch_abilities("nidhogg", (38, 42, 34, 30, 124, 124, 124))
    patch_abilities("valaha", (67, 67, 67, 30, 30, 127, 127))
    patch_abilities("yowie", (38, 42, 38, 30, 126, 126, 126))
    return prize_structs

def get_raw_data_string(prize_struct: list[str]) -> str:
    string = ""
    for index, byte in enumerate(prize_struct):
        # every 16 bytes make a new line
        if index % 16 == 0:
            string += "\n"
            string += " ".join([f"[{hex(index + i)[2:]:>3}]" for i in range(16)])
            string += "\n"
        # print the bytes' value
        # string += f' {hex(byte)[2:]:>3}  '
        string += f" {byte:>3}  "
        # string += f' {byte:08b}  '
    return string

def _get_monster_data(monster_id: str, prize_struct: list[int]) -> Monster:
    """Get a Monster from his prize struct."""

    def get_elements() -> dict[str, str]:
        elements = {
            Element.FIRE: 0b00001,
            Element.ICE: 0b00010,
            Element.THUNDER: 0b00100,
            Element.WATER: 0b01000,
            Element.HOLY: 0b10000,
        }
        affinities = {}
        for element, value in elements.items():
            if prize_struct[43] & value:
                affinities[element] = ElementalAffinity.ABSORBS
            elif prize_struct[44] & value:
                affinities[element] = ElementalAffinity.IMMUNE
            elif prize_struct[45] & value:
                affinities[element] = ElementalAffinity.RESISTS
            elif prize_struct[46] & value:
                affinities[element] = ElementalAffinity.WEAK
            else:
                affinities[element] = ElementalAffinity.NEUTRAL
        return affinities

    def get_abilities(address: int) -> dict[str, list[str | None]]:
        abilities = {}
        equipment_types = (EquipmentType.WEAPON, 0), (EquipmentType.ARMOR, 16)
        for equipment_type, offset in equipment_types:
            abilities[equipment_type] = []
            for i in range(address + offset, address + 16 + offset, 2):
                if prize_struct[i + 1] == 128:
                    ability_name = AUTOABILITIES[prize_struct[i]]
                else:
                    ability_name = None
                abilities[equipment_type].append(ability_name)
        return abilities

    monster_name = ""
    for character_id in prize_struct[408:430]:
        if character_id == 0:
            break
        monster_name += TEXT_CHARACTERS[character_id]
    for i in range(16):
        if monster_id.endswith(f"_{i}"):
            monster_name += f"#{i}"
            break

    stats = {
        Stat.HP: add_bytes(*prize_struct[20:24]),
        Stat.MP: add_bytes(*prize_struct[24:28]),
        "overkill_threshold": add_bytes(*prize_struct[28:32]),
        Stat.STRENGTH: prize_struct[32],
        Stat.DEFENSE: prize_struct[33],
        Stat.MAGIC: prize_struct[34],
        Stat.MAGIC_DEFENSE: prize_struct[35],
        Stat.AGILITY: prize_struct[36],
        Stat.LUCK: prize_struct[37],
        Stat.EVASION: prize_struct[38],
        Stat.ACCURACY: prize_struct[39],
    }

    gil = add_bytes(*prize_struct[128:130])
    ap = {
        "normal": add_bytes(*prize_struct[130:132]),
        "overkill": add_bytes(*prize_struct[132:134]),
    }
    item_1 = {
        "drop_chance": prize_struct[136],
        "normal": {Rarity.COMMON: None, Rarity.RARE: None},
        "overkill": {Rarity.COMMON: None, Rarity.RARE: None},
    }
    if prize_struct[141] == 32:
        item_1["normal"][Rarity.COMMON] = ItemDrop(
            ITEMS[prize_struct[140]], prize_struct[148], False
        )
    if prize_struct[143] == 32:
        item_1["normal"][Rarity.RARE] = ItemDrop(
            ITEMS[prize_struct[142]], prize_struct[149], True
        )
    if prize_struct[153] == 32:
        item_1["overkill"][Rarity.COMMON] = ItemDrop(
            ITEMS[prize_struct[152]], prize_struct[160], False
        )
    if prize_struct[155] == 32:
        item_1["overkill"][Rarity.RARE] = ItemDrop(
            ITEMS[prize_struct[154]], prize_struct[161], True
        )

    item_2 = {
        "drop_chance": prize_struct[137],
        "normal": {Rarity.COMMON: None, Rarity.RARE: None},
        "overkill": {Rarity.COMMON: None, Rarity.RARE: None},
    }
    if prize_struct[145] == 32:
        item_2["normal"][Rarity.COMMON] = ItemDrop(
            ITEMS[prize_struct[144]], prize_struct[150], False
        )
    if prize_struct[147] == 32:
        item_2["normal"][Rarity.RARE] = ItemDrop(
            ITEMS[prize_struct[146]], prize_struct[151], True
        )
    if prize_struct[157] == 32:
        item_2["overkill"][Rarity.COMMON] = ItemDrop(
            ITEMS[prize_struct[156]], prize_struct[162], False
        )
    if prize_struct[159] == 32:
        item_2["overkill"][Rarity.RARE] = ItemDrop(
            ITEMS[prize_struct[158]], prize_struct[163], True
        )

    steal = {
        "base_chance": prize_struct[138],
        Rarity.COMMON: None,
        Rarity.RARE: None,
    }
    if prize_struct[165] == 32:
        steal[Rarity.COMMON] = ItemDrop(
            ITEMS[prize_struct[164]], prize_struct[168], False
        )
    if prize_struct[167] == 32:
        steal[Rarity.RARE] = ItemDrop(ITEMS[prize_struct[166]], prize_struct[169], True)
    bribe = {
        "cost": float("nan"),
        "item": None,
    }
    if prize_struct[171] == 32:
        bribe["item"] = ItemDrop(ITEMS[prize_struct[170]], prize_struct[172], False)

    elemental_affinities = get_elements()

    status_resistances = {
        Status.DEATH: prize_struct[47],
        Status.ZOMBIE: prize_struct[48],
        Status.PETRIFY: prize_struct[49],
        Status.POISON: prize_struct[50],
        Status.POWER_BREAK: prize_struct[51],
        Status.MAGIC_BREAK: prize_struct[52],
        Status.ARMOR_BREAK: prize_struct[53],
        Status.MENTAL_BREAK: prize_struct[54],
        Status.CONFUSE: prize_struct[55],
        Status.BERSERK: prize_struct[56],
        Status.PROVOKE: prize_struct[57],
        Status.THREATEN: prize_struct[58],
        Status.SLEEP: prize_struct[59],
        Status.SILENCE: prize_struct[60],
        Status.DARK: prize_struct[61],
        Status.PROTECT: prize_struct[62],
        Status.SHELL: prize_struct[63],
        Status.REFLECT: prize_struct[64],
        Status.NULBLAZE: prize_struct[65],
        Status.NULFROST: prize_struct[66],
        Status.NULSHOCK: prize_struct[67],
        Status.NULTIDE: prize_struct[68],
        Status.REGEN: prize_struct[69],
        Status.HASTE: prize_struct[70],
        Status.SLOW: prize_struct[71],
    }
    poison_tick_damage = stats[Stat.HP] * prize_struct[42] // 100
    undead = prize_struct[72] == 2
    auto_statuses = []
    if prize_struct[74] & 0b00100000:
        auto_statuses.append(Status.REFLECT)
    if prize_struct[75] & 0b00000011 and prize_struct[74] & 0b11000000:
        auto_statuses.append(Status.NULALL)
    if prize_struct[75] & 0b00000100:
        auto_statuses.append(Status.REGEN)

    equipment = {
        "drop_chance": prize_struct[139],
        "bonus_critical_chance": prize_struct[175],
        "base_weapon_damage": prize_struct[176],
        "slots_modifier": prize_struct[173],
        "slots_range": [],
        "max_ability_rolls_modifier": prize_struct[177],
        "max_ability_rolls_range": [],
        "added_to_inventory": bool(prize_struct[174]),
    }

    for i in range(8):
        slots_mod = equipment["slots_modifier"] + i - 4
        slots = (slots_mod + ((slots_mod >> 31) & 3)) >> 2
        if slots < EquipmentSlots.MIN:
            slots = EquipmentSlots.MIN.value
        elif slots > EquipmentSlots.MAX:
            slots = EquipmentSlots.MAX.value
        equipment["slots_range"].append(slots)
        ab_mod = equipment["max_ability_rolls_modifier"] + i - 4
        ab_rolls = (ab_mod + ((ab_mod >> 31) & 7)) >> 3
        equipment["max_ability_rolls_range"].append(ab_rolls)

    equipment["ability_arrays"] = {}
    for c, i in zip(CHARACTERS.values(), range(178, 371, 32)):
        equipment["ability_arrays"][c.name] = get_abilities(i)

    armored = bool(prize_struct[40] & 0b00000001)
    zanmato_level = prize_struct[402]
    actions = MONSTER_ACTIONS[monster_id]
    if not actions:
        actions.update(MONSTER_ACTIONS["generic_actions"])
    monster = Monster(
        name=monster_name,
        stats=stats,
        elemental_affinities=elemental_affinities,
        status_resistances=status_resistances,
        poison_tick_damage=poison_tick_damage,
        zanmato_level=zanmato_level,
        armored=armored,
        undead=undead,
        auto_statuses=auto_statuses,
        gil=gil,
        ap=ap,
        item_1=item_1,
        item_2=item_2,
        steal=steal,
        bribe=bribe,
        equipment=equipment,
        actions=actions,
    )
    return monster

PRIZE_STRUCTS = _get_prize_structs("tracker\\data\\ffx_mon_data.csv")
if Configs.game_version is GameVersion.HD:
    PRIZE_STRUCTS = _patch_prize_structs_for_hd(PRIZE_STRUCTS)

MONSTERS = {k: _get_monster_data(k, v) for k, v in PRIZE_STRUCTS.items()}


"""Regridding operator."""

# Standard library
import dataclasses as dc
import typing

# Third-party
import numpy as np
import xarray as xr
from rasterio import transform, warp
from rasterio.crs import CRS

Resampling: typing.TypeAlias = warp.Resampling

# For more information: check https://epsg.io/<id>
CRS_ALIASES = {
    "geolatlon": "epsg:4326",  # WGS84
    "swiss": "epsg:21781",  # Swiss CH1903 / LV03
    "swiss03": "epsg:21781",  # Swiss CH1903 / LV03
    "swiss95": "epsg:2056",  # Swiss CH1903+ / LV95
    "boaga-west": "epsg:3003",  # Monte Mario / Italy zone 1
    "boaga-east": "epsg:3004",  # Monte Mario / Italy zone 2
}

def _get_crs(geo):
    if geo["gridType"] != "rotated_ll":
        raise NotImplementedError("Unsupported grid type")

    lon = geo["longitudeOfSouthernPoleInDegrees"]
    lat = -1 * geo["latitudeOfSouthernPoleInDegrees"]

    return CRS.from_string(
        f"+proj=ob_tran +o_proj=longlat +o_lat_p={lat} +lon_0={lon} +datum=WGS84"
    )

def _normalise(angle: float) -> float:
    return np.fmod(angle + 180, 360) - 180

@dc.dataclass
class RegularGrid:
    """Class defining a regular grid.

    Attributes
    ----------
    crs : CRS
        Coordinate reference system.
    nx : int
        Number of grid points in the x direction.
    ny : int
        Number of grid points in the y direction.
    xmin : float
        Coordinate of the first grid point in the x direction.
    xmax : float
        Coordinate of the last grid point in the x direction.
    ymin : float
        Coordinate of the first grid point in the y direction.
    ymax : float
        Coordinate of the last grid point in the y direction.

    """

    crs: CRS
    nx: int
    ny: int
    xmin: float
    xmax: float
    ymin: float
    ymax: float

    @classmethod
    def from_field(cls, field: xr.DataArray):
        """Extract grid parameters from grib metadata.

        Parameters
        ----------
        field : xarray.DataArray
            field containing the relevant metadata.

        """
        geo = field.geography
        obj = cls(
            crs=_get_crs(geo),
            nx=geo["Ni"],
            ny=geo["Nj"],
            xmin=_normalise(geo["longitudeOfFirstGridPointInDegrees"]),
            xmax=_normalise(geo["longitudeOfLastGridPointInDegrees"]),
            ymin=geo["latitudeOfFirstGridPointInDegrees"],
            ymax=geo["latitudeOfLastGridPointInDegrees"],
        )
        if abs(obj.dx - geo["iDirectionIncrementInDegrees"]) > 1e-5:
            raise ValueError("Inconsistent grid parameters")
        if abs(obj.dy - geo["jDirectionIncrementInDegrees"]) > 1e-5:
            raise ValueError("Inconsistent grid parameters")
        return obj

    @classmethod
    def parse_regrid_operator(cls, op: str):
        """Parse fieldextra out_regrid_target string.

        Parameters
        ----------
        op : str
            fieldextra out_regrid_target definition
            i.e. crs,xmin,ymin,xmay,ymax,dx,dy.

        """
        crs_str, *grid_params = op.split(",")
        crs = CRS.from_string(CRS_ALIASES[crs_str])
        xmin, ymin, xmax, ymax, dx, dy = map(float, grid_params)
        if abs(dx) < 1e-10 or abs(dy) < 1e-10:
            raise ValueError("Inconsistent regrid parameters")
        nx = (xmax - xmin) / dx + 1
        ny = (ymax - ymin) / dy + 1
        if nx != int(nx) or ny != int(ny):
            raise ValueError("Inconsistent regrid parameters")
        return cls(crs, int(nx), int(ny), xmin, xmax, ymin, ymax)

    @property
    def dx(self) -> float:
        return (self.xmax - self.xmin) / (self.nx - 1)

    @property
    def dy(self) -> float:
        return (self.ymax - self.ymin) / (self.ny - 1)

    @property
    def transform(self) -> transform.Affine:
        return transform.from_origin(
            west=self.xmin - self.dx / 2,
            north=self.ymax + self.dy / 2,
            xsize=self.dx,
            ysize=self.dy,
        )

def regrid(field: xr.DataArray, dst: RegularGrid, resampling: Resampling):
    """Regrid a field.

    Parameters
    ----------
    field : xarray.DataArray
        Input field defined on a regular grid in rotated latlon coordinates.
    dst : RegularGrid
        Destination grid onto which to project the field.
    resampling : Resampling
        Resampling method, alias of rasterio.warp.Resampling.

    Raises
    ------
    ValueError
        If the input field is not defined on a regular grid in rotated latlon or
        if the input field geography metadata does not have consistent grid parameters.

    Returns
    -------
    xarray.DataArray
        Field regridded in the destination grid.

    """
    src = RegularGrid.from_field(field)

    def reproject_layer(field):
        output = np.zeros((dst.ny, dst.nx))
        warp.reproject(
            source=field[::-1],
            destination=output,
            src_crs=src.crs,
            src_transform=src.transform,
            dst_crs=dst.crs,
            dst_transform=dst.transform,
            resampling=resampling,
        )
        return output[::-1]

    # output dims renamed to workaround limitation that overlapping dims in the input
    # must not change in size
    return xr.apply_ufunc(
        reproject_layer,
        field,
        input_core_dims=[["y", "x"]],
        output_core_dims=[["y1", "x1"]],
        vectorize=True,
    ).rename({"x1": "x", "y1": "y"})


"""Escea Fireplace UDP messaging module

   Implements simple UDP messages to Fireplace and receiving responses
"""

import asyncio
import logging

from asyncio import Lock
from asyncio.base_events import BaseEventLoop
from async_timeout import timeout
from typing import Any, Dict

# Pescea imports:
from .message import Message, CommandID, expected_response
from .udp_endpoints import open_local_endpoint, open_remote_endpoint

_LOG = logging.getLogger(__name__)

# Port used for discovery and integration
# (same port is used for replies)
CONTROLLER_PORT = 3300

# Time to wait for results from server
REQUEST_TIMEOUT = 5.0

Responses = Dict[str, Message]

class Datagram:
    """Send UDP Datagrams to fireplace and receive responses"""

    def __init__(
        self, event_loop: BaseEventLoop, device_ip: str, sending_lock: Lock
    ) -> None:
        """Create a simple datagram client interface.

        Args:
            event_loop: loop to use for coroutines
            device_addr: Device network address. Usually specified as IP
                address (can be a broadcast address in the case of fireplace search)
            sending_lock: Provided to attempt to make thread safe

        Raises:
            ConnectionRefusedError: If no Escea fireplace is discovered, or no
                device discovered at the given IP address, or the UID does not match
        """
        self._ip = device_ip
        self._event_loop = event_loop
        self.sending_lock = sending_lock

    @property
    def ip(self) -> str:
        """Target IP address"""
        return self._ip

    def set_ip(self, ip_addr: str) -> None:
        """Change the Target IP address"""
        self._ip = ip_addr

    async def send_command(self, command: CommandID, data: Any = None) -> Responses:
        """Send command via UDP
        Returns received response(s) and IP addresses they come from

        Args:
        - command: Fireplace command (refer Message)
        - data: ignored except for setting desired temperature

        Raises ConnectionError if unable to send command
        """
        message = Message(command=command, set_temp=data)
        responses = dict()  # type: Responses
        broadcast = command == CommandID.SEARCH_FOR_FIRES
        local = None
        remote = None

        # set up receiver before we send anything
        async with self.sending_lock:
            try:
                local = await open_local_endpoint(
                    port=CONTROLLER_PORT,
                    loop=self._event_loop,
                    reuse_port=True,
                )
                remote = await open_remote_endpoint(
                    host=self._ip,
                    port=CONTROLLER_PORT,
                    loop=self._event_loop,
                    allow_broadcast=broadcast,
                )
                remote.send(message.bytearray_)
                remote.close()
                async with timeout(REQUEST_TIMEOUT):
                    while True:

                        data, (addr, _) = await local.receive()
                        response = Message(incoming=data)
                        if response.is_command:
                            if not broadcast:
                                _LOG.error(
                                    "Unexpected command id: %s", response.command_id
                                )
                        else:  # response
                            if response.response_id != expected_response(command):
                                _LOG.debug(
                                    "Message response id: %s does not match command id: %s",
                                    response.response_id,
                                    command,
                                )
                            else:
                                responses[addr] = response
                        if not broadcast:
                            break
                    local.close()
            except (asyncio.TimeoutError, ValueError):
                pass
            finally:
                if remote is not None and not remote.closed:
                    remote.close()
                if local is not None and not local.closed:
                    local.close()

        if len(responses) == 0:
            _LOG.debug(
                "Unable to send UDP message - Local endpoint closed:%s, Remote endpoint closed:%s",
                "None" if local is None else local.closed,
                "None" if remote is None else remote.closed,
            )
            raise ConnectionError("Unable to send/receive UDP message")

        return responses


# -*- coding: utf-8 -*-
"""
Created on Fri Apr 14 15:00:34 2017

@author: rsotoc
"""

import numpy as np
import pandas as pd
import time

import re
import nltk
from nltk.corpus import stopwords
from nltk.util import ngrams
from sklearn.model_selection import train_test_split

from bs4 import BeautifulSoup
from sklearn.naive_bayes import MultinomialNB

def document_features_ngrams(document, global_features):
    features = [0] * len(global_features)
    nd = len(document)
    for elem in document:
        if elem in global_features:
            tf = document.freq(elem) / nd
            index, idf = global_features[elem]
            if ( idf > 0 ):
                features[index] = tf * idf
    return features

# ------------------------------------------------------------------
movies_reviews = pd.read_csv("Movies Reviews/labeledTrainData.tsv", sep='\t')

# Limpiar los documentos. Conservar sólo plabras (alfabéticas) y pasar a minúsculas
movies_reviews.review = list(map(lambda row: re.sub("[^a-zA-Z]", " ",
                                BeautifulSoup(row, "lxml").get_text().lower()),
                                 movies_reviews.review))

# Agregar una columna con la conversión de mensajes a listas de palabras
# Sin eliminar las palabras vacías
movies_reviews["words"] = list(map(lambda row: row.split(), movies_reviews.review))
corpus_len = len(movies_reviews.words)

# Agregar una columna con la conversión de mensajes a listas de palabras
# Se eliminan las palabras vacías
stop_words = set(stopwords.words("english"))
most_common_words = nltk.FreqDist(w for wl in movies_reviews.words for w in wl)
#my_stop_words = [ w for (w,f) in most_common_words.most_common(15)]

movies_reviews["words"] = list(map(lambda row: [w for w in row.split() if not w in stop_words],
                                   movies_reviews.review))

movies_reviews["bigrams"] = list(map(lambda row: list(ngrams(row,2)),
                                   movies_reviews.words))

# Generar un arreglo con los valores de clasificación
Sentiments = np.array([int(x) for x in movies_reviews.sentiment])

#movies_reviews["trigrams"] = list(map(lambda row: list(ngrams(row,3)),
#                                   movies_reviews.words))

#words_frq = nltk.FreqDist(w.lower() for wl in movies_reviews.words for w in wl
#                         ).most_common(4000)
bigrams_frq = nltk.FreqDist(w for wl in movies_reviews.bigrams for w in wl
                            ).most_common(4000)

print("empezando")
start_time = time.time()
bag_idf = {}
for i, (elem, f) in zip (range(len(bigrams_frq)), bigrams_frq):
    nt = 0
    for row in movies_reviews.bigrams:
        if elem in row:
            nt += 1
    bag_idf[elem] = (i, np.log(corpus_len / nt))

featuresets_bigrams = [
    document_features_ngrams(nltk.FreqDist(w for w in d),
                             bag_idf) for d in movies_reviews["bigrams"]]

#trigrams_frq = nltk.FreqDist(w for wl in movies_reviews.trigrams for w in wl
#                            ).most_common(4000)

#featuresets_words = [
#    document_features_ngrams(d, words_frq) for d in movies_reviews["words"]]
#
#bag_dict = {}
#for i, (elem, f) in zip (range(len(bigrams_frq)), bigrams_frq):
#    bag_dict[elem] = (i, float(f)/word_bag_len)
#featuresets_bigrams = [
#    document_features_ngrams(nltk.FreqDist(d), bigrams_frq)
#    for d in movies_reviews["bigrams"]]
#featuresets_trigrams = [
#    document_features_ngrams(nltk.FreqDist(d), trigrams_frq)
#    for d in movies_reviews["trigrams"]]
elapsed_time = time.time() - start_time

#for i in range(100):
#    print(sum(x > 0 for x in featuresets_bigrams[i]))

bigrams_train, bigrams_test, biy_train, biy_test = train_test_split(
    featuresets_bigrams, Sentiments, test_size=0.1)

# Entrenamiento de un clasificador Multinomial Bayes ingenuo
clfM = MultinomialNB()
clfM.fit(bigrams_train, biy_train)
print(elapsed_time)

# Pruebas del clasificador
predictions_train = clfM.predict(bigrams_train)
fails_train = sum(biy_train  != predictions_train)
print("Puntos mal clasificados en el conjunto de entrenamiento: {} de {} ({}%)\n"
      .format(fails_train, len(bigrams_train), 100*fails_train/len(bigrams_train)))
predictions_test = clfM.predict(bigrams_test)
fails_test = sum(biy_test  != predictions_test)
print("Puntos mal clasificados en el conjunto de prueba: {} de {} ({}%)\n"
      .format(fails_test, len(bigrams_test), 100*fails_test/len(bigrams_test)))


import os
import cv2
import sys
import math
import time
import numpy as np
import matplotlib.pyplot as plt

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5 import uic

# 이미지를 읽어서 pyqt로 보여주는 함수
def cvtPixmap(frame, img_size):
    frame = cv2.resize(frame, img_size)
    height, width, channel = frame.shape
    bytesPerLine = 3 * width
    qImg = QImage(frame.data,
                  width,
                  height,
                  bytesPerLine,
                  QImage.Format_RGB888).rgbSwapped()
    qpixmap = QPixmap.fromImage(qImg)

    return qpixmap

# 동공 주변 반사광 채우는 함수
def fill_reflected_light(ori_img, min_thr, iteration=2, add_inter_idx=1):
    if len(ori_img.shape) == 3:
        ori_img = cv2.cvtColor(ori_img, cv2.COLOR_BGR2GRAY)
    ret, img_thresh = cv2.threshold(ori_img, min_thr, 255, cv2.THRESH_BINARY)

    kernel = np.ones((3, 3), np.uint8)
    img_thresh = cv2.dilate(img_thresh, kernel, iterations=iteration)  # 팽창연산-대상을 확장한 후 작은 구멍을 채우는 방식
    draw_img = ori_img.copy()  # 원본 이미지 복사

    reflection_points = np.where(img_thresh == 255)  # 화소값이 255인 인덱스 반환
    for y, x in zip(reflection_points[0], reflection_points[1]):
        # x 픽설의 왼쪽 픽셀이 l_x, 오른쪽 픽셀이 r_x
        # l_x는 0이상의 값을 가지고, r_x는 이미지크기보다 작아야 함
        l_x, r_x = x - 1, x + 1
        l_x = l_x if l_x >= 0 else 0
        r_x = r_x if r_x < img_thresh.shape[1] else img_thresh.shape[1] - 1

        # l_x, r_x가 이미지크기 범위 안에있고, 반사광이면 1칸씩 이동
        while l_x >= 0 and img_thresh[y][l_x] == 255:
            l_x -= 1
        while r_x < (img_thresh.shape[1] - 1) and img_thresh[y][r_x] == 255:
            r_x += 1

        # 반사광에서 가장 인접한 값이 아닌, 조금 옆의 값으로 반사광 채우기
        # 이미 위에서 dilation 연산을 통해 경계가 두꺼워져 반사광 조금 옆의 값을 가져왔기 때문에 add_inter_idx의 큰 의미는 없음
        l_x -= add_inter_idx
        r_x += add_inter_idx
        l_x = l_x if l_x >= 0 else 0
        r_x = r_x if r_x < img_thresh.shape[1] else img_thresh.shape[1] - 1

        l_val = int(ori_img[y][l_x])
        r_val = int(ori_img[y][r_x])
        draw_img[y][x] = int((l_val + r_val) / 2)  # 반사광 채우기
    return draw_img

# 동공 검출 함수
def getPupil(img, thresh, area_val, symmetry_val, fill_cond_val):
    '''
    :param img: 입력 동공 이미지
    :param thresh:
    :param area_val:
    :param symmetry_val:
    :param fill_cond_val:
    :return: ((동공중심 x,y), 반지름, (외접 사각형 x,y,w,h))
    condition으로 끝나는 변수 3개가 모두 1로 만족해야 res에 append할 수 있음
    area_condition : 직사각형 contour로 둘러싸인 부분의 면적
    symmetry_condition : 1-종횡비율(contour를 둘러싼 직사각형의 너비/높이 비율)이 symmetry_val(0.2)보다 작으면 1-> 정사각형에 가까워야함
    fill_condition : (contour로 둘러싸인 면적인 area/위에서 계산한 내접원의 넓이)을 계산해 얼마나 채워져있는지 비교
    '''
    res = []

    if len(img.shape) == 3:
        gray = cv2.cvtColor(~img, cv2.COLOR_BGR2GRAY)
    else:
        gray = img
    ret, thresh_gray = cv2.threshold(gray, thresh[0], thresh[1], cv2.THRESH_BINARY)
    contours, hierarchy = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    draw_img = img.copy()
    for i in range(len(contours)):
        # # 컨투어 각각 시각화
        # cv2.drawContours(draw_img, [contours[i]], 0, (0, 0, 255), 2)
        # cv2.putText(draw_img, str(i), tuple(contours[i][0][0]), cv2.FONT_HERSHEY_COMPLEX, 0.5, (0, 255, 0), 1)
        # print(i, hierarchy[0][i])
        # cv2.imshow('contour detection', draw_img)
        # cv2.waitKey(0)

    # for contour in contours:
        area = cv2.contourArea(contours[i])
        rect = cv2.boundingRect(contours[i])
        x, y, width, height = rect  # 직사각형 모양 바운딩 박스의 좌표, 너비, 높이
        radius = 0.25 * (width + height)  # 내접원의 반지름(내 생각엔 직사각형 모양의 contour의 내접원의 반지름같음)

        area_condition = (area_val <= area)
        symmetry_condition = (abs(1 - float(width) / float(height)) <= symmetry_val/10)
        fill_condition = (abs(1 - (area / (math.pi * math.pow(radius, 2.0)))) <= fill_cond_val/10)

        # 3가지 조건을 모두 만족해야 동공 영역
        if area_condition and symmetry_condition and fill_condition:
            res.append(((int(x + radius), int(y + radius)), int(1 * radius), rect))  # 동공중심 x좌표, y좌표, 반지름, rect(외접 사각형)

    return res, thresh_gray

# 동공 지름 구하기
def get_pupil_size(roi, binary_eye, pupil_info, add_radius):
    info = pupil_info[0]  # (동공중심x,y), 반지름, (외접 사각형 x,y,w,h)
    # rect_roi = info[2]
    rect_roi = pupil_info[0][2]

    box_x, box_y, width, height = rect_roi
    box_x = box_x - add_radius if box_x - add_radius >= 0 else 0
    box_y = box_y - add_radius if box_y - add_radius >= 0 else 0
    width = width + (2 * add_radius) if width + (2 * add_radius) <= roi.shape[1] else roi.shape[1]
    height = height + (2 * add_radius) if height + (2 * add_radius) <= roi.shape[0] else roi.shape[0]
    img_eye_only = binary_eye[box_y:box_y + height, box_x:box_x + width].copy()
    img_eye_only = np.where(img_eye_only == 255, 1, img_eye_only)

    cv2.rectangle(roi, (box_x, box_y), ((box_x + width), (box_y + height)), (0, 255, 255), 2)  # 동공주변 노란색 박스

    # 동공 영역 새로 길이
    max_idx, max_val = 0, 0
    for col_idx in range(img_eye_only.shape[0]):
        col_val = sum(img_eye_only[col_idx])
        if max_val < col_val:
            max_idx = col_idx
            max_val = col_val

    # 동공 영역이 시작되는 좌우 지점 찾기
    l_row, r_row = 0, img_eye_only.shape[1]
    for row_idx in range(img_eye_only.shape[1] - 1):
        row_val = sum(img_eye_only[:, row_idx])
        if row_val != 0:
            l_row = row_idx
    for row_idx in range(img_eye_only.shape[1] - 1, 0, -1):
        row_val = sum(img_eye_only[:, row_idx])
        if row_val != 0:
            r_row = row_idx

    # 동공의 세로 길이를 찾아 가로로 그림
    cv2.line(roi,
             (box_x + l_row, box_y + max_idx),
             (box_x + r_row, box_y + max_idx),
             (0, 0, 255), 2)  # 동공의 지름 그리기

    return roi, max_val

def frames_to_timecode(total_frames, frame_rate=30, drop=False):
    """
    Method that converts frames to SMPTE timecode.

    :param total_frames: Number of frames
    :param frame_rate: frames per second
    :param drop: true if time code should drop frames, false if not
    :returns: SMPTE timecode as string, e.g. '01:02:12:32' or '01:02:12;32'
    """
    if drop and frame_rate not in [29.97, 59.94]:
        raise NotImplementedError("Time code calculation logic only supports drop frame "
                                  "calculations for 29.97 and 59.94 fps.")

    # for a good discussion around time codes and sample code, see
    # http://andrewduncan.net/timecodes/

    # round fps to the nearest integer
    # note that for frame rates such as 29.97 or 59.94,
    # we treat them as 30 and 60 when converting to time code
    # then, in some cases we 'compensate' by adding 'drop frames',
    # e.g. jump in the time code at certain points to make sure that
    # the time code calculations are roughly right.
    #
    # for a good explanation, see
    # https://documentation.apple.com/en/finalcutpro/usermanual/index.html#chapter=D%26section=6
    fps_int = int(round(frame_rate))

    if drop:
        # drop-frame-mode
        # add two 'fake' frames every minute but not every 10 minutes
        #
        # example at the one minute mark:
        #
        # frame: 1795 non-drop: 00:00:59:25 drop: 00:00:59;25
        # frame: 1796 non-drop: 00:00:59:26 drop: 00:00:59;26
        # frame: 1797 non-drop: 00:00:59:27 drop: 00:00:59;27
        # frame: 1798 non-drop: 00:00:59:28 drop: 00:00:59;28
        # frame: 1799 non-drop: 00:00:59:29 drop: 00:00:59;29
        # frame: 1800 non-drop: 00:01:00:00 drop: 00:01:00;02
        # frame: 1801 non-drop: 00:01:00:01 drop: 00:01:00;03
        # frame: 1802 non-drop: 00:01:00:02 drop: 00:01:00;04
        # frame: 1803 non-drop: 00:01:00:03 drop: 00:01:00;05
        # frame: 1804 non-drop: 00:01:00:04 drop: 00:01:00;06
        # frame: 1805 non-drop: 00:01:00:05 drop: 00:01:00;07
        #
        # example at the ten minute mark:
        #ㄺ
        # frame: 17977 non-drop: 00:09:59:07 drop: 00:09:59;25
        # frame: 17978 non-drop: 00:09:59:08 drop: 00:09:59;26
        # frame: 17979 non-drop: 00:09:59:09 drop: 00:09:59;27
        # frame: 17980 non-drop: 00:09:59:10 drop: 00:09:59;28
        # frame: 17981 non-drop: 00:09:59:11 drop: 00:09:59;29
        # frame: 17982 non-drop: 00:09:59:12 drop: 00:10:00;00
        # frame: 17983 non-drop: 00:09:59:13 drop: 00:10:00;01
        # frame: 17984 non-drop: 00:09:59:14 drop: 00:10:00;02
        # frame: 17985 non-drop: 00:09:59:15 drop: 00:10:00;03
        # frame: 17986 non-drop: 00:09:59:16 drop: 00:10:00;04
        # frame: 17987 non-drop: 00:09:59:17 drop: 00:10:00;05

        # calculate number of drop frames for a 29.97 std NTSC
        # workflow. Here there are 30*60 = 1800 frames in one
        # minute

        # 2는 왜 뺴지?
        FRAMES_IN_ONE_MINUTE = 1800 - 2
        FRAMES_IN_TEN_MINUTES = (FRAMES_IN_ONE_MINUTE * 10) - 2

        ten_minute_chunks = total_frames / FRAMES_IN_TEN_MINUTES  # 10분짜리가 몇 묶음인지
        one_minute_chunks = total_frames % FRAMES_IN_TEN_MINUTES  # 1분짜리가 몇 묶음인지

        ten_minute_part = 18 * ten_minute_chunks
        one_minute_part = 2 * ((one_minute_chunks - 2) / FRAMES_IN_ONE_MINUTE)

        if one_minute_part < 0:
            one_minute_part = 0

        # add extra frames
        total_frames += ten_minute_part + one_minute_part

        # for 60 fps drop frame calculations, we add twice the number of frames
        if fps_int == 60:
            total_frames = total_frames * 2

        # time codes are on the form 12:12:12;12
        smpte_token = ";"

    else:
        # time codes are on the form 12:12:12:12
        smpte_token = ":"

    # now split our frames into time code
    hours = int(total_frames / (3600 * fps_int))
    minutes = int(total_frames / (60 * fps_int) % 60)
    seconds = int(total_frames / fps_int % 60)
    frames = int(total_frames % fps_int)
    return "%02d:%02d:%02d%s%02d" % (hours, minutes, seconds, smpte_token, frames)


# Import necessary modules and libraries
from dotenv import load_dotenv
import os
import base64
from requests import post, get
import json

# Load environment variables from .env file
load_dotenv()

# Import CLIENT_ID and CLIENT_SECRET from environment variables
client_id = os.getenv("CLIENT_ID")
client_secret = os.getenv("CLIENT_SECRET")

# Function to get Spotify API token using client credentials
def get_token():
    # Combine client_id and client_secret and encode in base64
    auth_string = client_id + ":" + client_secret
    auth_bytes = auth_string.encode("utf-8")
    auth_base64 = str(base64.b64encode(auth_bytes), "utf-8")

    # API endpoint for token retrieval
    url = "https://accounts.spotify.com/api/token"

    # Headers for token request
    headers = {
        "Authorization": "Basic " + auth_base64,
        "Content-Type": "application/x-www-form-urlencoded"
    }

    # Data for token request
    data = {"grant_type": "client_credentials"}

    # Send a POST request to retrieve the token
    result = post(url, headers=headers, data=data)

    # Parse the JSON response to extract the access token
    json_result = json.loads(result.content)
    token = json_result["access_token"]
    return token

# Function to generate the authorization header with the provided token
def get_auth_header(token):
    return {"Authorization": "Bearer " + token}

# Function to search for an artist by name
def search_for_artist(token, artist_name):
    url = "https://api.spotify.com/v1/search"
    headers = get_auth_header(token)
    query = f"?q={artist_name}&type=artist&limit=1"

    # Build the query URL and send a GET request to search for the artist
    query_url = url + query
    result = get(query_url, headers=headers)

    # Parse the JSON response to extract the artist information
    json_result = json.loads(result.content)["artists"]["items"]

    if len(json_result) == 0:
        print("No artist found...")
        return None
    else:
        return json_result[0]

# Function to get top tracks of an artist in a specific country
def get_songs_by_artist(token, artist_id, country):
    url =f"https://api.spotify.com/v1/artists/{artist_id}/top-tracks?country={country}"
    headers =  get_auth_header(token)

    # Send a GET request to retrieve the artist's top tracks in the specified country
    result = get(url, headers=headers)

    # Parse the JSON response to extract the list of tracks
    json_result = json.loads(result.content)["tracks"]
    return json_result

def get_artist_name():
    artist_name = input("Enter an artist name: ")
    return artist_name

# Get the API token
token = get_token()

artist_name = get_artist_name()

# Search for an artist by name and get their ID
result = search_for_artist(token, artist_name)
artist_id = result["id"]

# Get top tracks of the artist in different countries
songsTR = get_songs_by_artist(token, artist_id, "TR")
songsUS = get_songs_by_artist(token, artist_id, "US")

#clears the text file
open("text.txt", "w").close()

#Opens a text file
file = open("text.txt", "a")

#Writes top ten songs from us into the text file
for idx, song in enumerate(songsUS):
    file.write(f"{idx + 1}. {song['name']}\n")

#Closes the text file
file.close()

# Print the top tracks for each country
for idx, song in enumerate(songsTR):
    print(f"{idx + 1}. {song['name']}")

print("\n")

for idx, song in enumerate(songsUS):
    print(f"{idx + 1}. {song['name']}")