import enum
import platform
import typing
import math
from functools import lru_cache
from publicsuffix2 import get_sld, get_tld

import urwid
import urwid.util

from mitmproxy import flow
from mitmproxy.http import HTTPFlow
from mitmproxy.utils import human, emoji
from mitmproxy.tcp import TCPFlow
from mitmproxy import dns
from mitmproxy.dns import DNSFlow

# Detect Windows Subsystem for Linux and Windows
IS_WINDOWS_OR_WSL = "Microsoft" in platform.platform() or "Windows" in platform.platform()

def is_keypress(k):
    """
        Is this input event a keypress?
    """
    if isinstance(k, str):
        return True

def highlight_key(str, key, textattr="text", keyattr="key"):
    l = []
    parts = str.split(key, 1)
    if parts[0]:
        l.append((textattr, parts[0]))
    l.append((keyattr, key))
    if parts[1]:
        l.append((textattr, parts[1]))
    return l

KEY_MAX = 30

def format_keyvals(
        entries: typing.Iterable[typing.Tuple[str, typing.Union[None, str, urwid.Widget]]],
        key_format: str = "key",
        value_format: str = "text",
        indent: int = 0
) -> typing.List[urwid.Columns]:
    """
    Format a list of (key, value) tuples.

    Args:
        entries: The list to format. keys must be strings, values can also be None or urwid widgets.
            The latter makes it possible to use the result of format_keyvals() as a value.
        key_format: The display attribute for the key.
        value_format: The display attribute for the value.
        indent: Additional indent to apply.
    """
    max_key_len = max((len(k) for k, v in entries if k is not None), default=0)
    max_key_len = min(max_key_len, KEY_MAX)

    if indent > 2:
        indent -= 2  # We use dividechars=2 below, which already adds two empty spaces

    ret = []
    for k, v in entries:
        if v is None:
            v = urwid.Text("")
        elif not isinstance(v, urwid.Widget):
            v = urwid.Text([(value_format, v)])
        ret.append(
            urwid.Columns(
                [
                    ("fixed", indent, urwid.Text("")),
                    (
                        "fixed",
                        max_key_len,
                        urwid.Text([(key_format, k)])
                    ),
                    v
                ],
                dividechars=2
            )
        )
    return ret

def fcol(s: str, attr: str) -> typing.Tuple[str, int, urwid.Text]:
    s = str(s)
    return (
        "fixed",
        len(s),
        urwid.Text(
            [
                (attr, s)
            ]
        )
    )

if urwid.util.detected_encoding:
    SYMBOL_REPLAY = "\u21ba"
    SYMBOL_RETURN = "\u2190"
    SYMBOL_MARK = "\u25cf"
    SYMBOL_UP = "\u21E7"
    SYMBOL_DOWN = "\u21E9"
    SYMBOL_ELLIPSIS = "\u2026"
    SYMBOL_FROM_CLIENT = "\u21d2"
    SYMBOL_TO_CLIENT = "\u21d0"
else:
    SYMBOL_REPLAY = "[r]"
    SYMBOL_RETURN = "<-"
    SYMBOL_MARK = "#"
    SYMBOL_UP = "^"
    SYMBOL_DOWN = " "
    SYMBOL_ELLIPSIS = "~"
    SYMBOL_FROM_CLIENT = "->"
    SYMBOL_TO_CLIENT = "<-"

SCHEME_STYLES = {
    'http': 'scheme_http',
    'https': 'scheme_https',
    'ws': 'scheme_ws',
    'wss': 'scheme_wss',
    'tcp': 'scheme_tcp',
    'dns': 'scheme_dns',
}
HTTP_REQUEST_METHOD_STYLES = {
    'GET': 'method_get',
    'POST': 'method_post',
    'DELETE': 'method_delete',
    'HEAD': 'method_head',
    'PUT': 'method_put'
}
HTTP_RESPONSE_CODE_STYLE = {
    2: "code_200",
    3: "code_300",
    4: "code_400",
    5: "code_500",
}

class RenderMode(enum.Enum):
    TABLE = 1
    """The flow list in table format, i.e. one row per flow."""
    LIST = 2
    """The flow list in list format, i.e. potentially multiple rows per flow."""
    DETAILVIEW = 3
    """The top lines in the detail view."""

def fixlen(s: str, maxlen: int) -> str:
    if len(s) <= maxlen:
        return s.ljust(maxlen)
    else:
        return s[0:maxlen - len(SYMBOL_ELLIPSIS)] + SYMBOL_ELLIPSIS

def fixlen_r(s: str, maxlen: int) -> str:
    if len(s) <= maxlen:
        return s.rjust(maxlen)
    else:
        return SYMBOL_ELLIPSIS + s[len(s) - maxlen + len(SYMBOL_ELLIPSIS):]

def render_marker(marker: str) -> str:
    rendered = emoji.emoji.get(marker, SYMBOL_MARK)

    # The marker can only be one glyph. Some emoji that use zero-width joiners (ZWJ)
    # will not be rendered as a single glyph and instead will show
    # multiple glyphs. Just use the first glyph as a fallback.
    # https://emojipedia.org/emoji-zwj-sequence/
    return rendered[0]

class TruncatedText(urwid.Widget):
    def __init__(self, text, attr, align='left'):
        self.text = text
        self.attr = attr
        self.align = align
        super().__init__()

    def pack(self, size, focus=False):
        return (len(self.text), 1)

    def rows(self, size, focus=False):
        return 1

    def render(self, size, focus=False):
        text = self.text
        attr = self.attr
        if self.align == 'right':
            text = text[::-1]
            attr = attr[::-1]

        text_len = urwid.util.calc_width(text, 0, len(text))
        if size is not None and len(size) > 0:
            width = size[0]
        else:
            width = text_len

        if width >= text_len:
            remaining = width - text_len
            if remaining > 0:
                c_text = text + ' ' * remaining
                c_attr = attr + [('text', remaining)]
            else:
                c_text = text
                c_attr = attr
        else:
            trim = urwid.util.calc_trim_text(text, 0, width - 1, 0, width - 1)
            visible_text = text[0:trim[1]]
            if trim[3] == 1:
                visible_text += ' '
            c_text = visible_text + SYMBOL_ELLIPSIS
            c_attr = (urwid.util.rle_subseg(attr, 0, len(visible_text.encode())) +
                      [('focus', len(SYMBOL_ELLIPSIS.encode()))])

        if self.align == 'right':
            c_text = c_text[::-1]
            c_attr = c_attr[::-1]

        return urwid.TextCanvas([c_text.encode()], [c_attr], maxcol=width)

def truncated_plain(text, attr, align='left'):
    return TruncatedText(text, [(attr, len(text.encode()))], align)

# Work around https://github.com/urwid/urwid/pull/330
def rle_append_beginning_modify(rle, a_r):
    """
    Append (a, r) (unpacked from *a_r*) to BEGINNING of rle.
    Merge with first run when possible

    MODIFIES rle parameter contents. Returns None.
    """
    a, r = a_r
    if not rle:
        rle[:] = [(a, r)]
    else:
        al, run = rle[0]
        if a == al:
            rle[0] = (a, run + r)
        else:
            rle[0:0] = [(a, r)]

def colorize_host(host):
    tld = get_tld(host)
    sld = get_sld(host)

    attr = []

    tld_size = len(tld)
    sld_size = len(sld) - tld_size

    for letter in reversed(range(len(host))):
        character = host[letter]
        if tld_size > 0:
            style = 'url_domain'
            tld_size -= 1
        elif tld_size == 0:
            style = 'text'
            tld_size -= 1
        elif sld_size > 0:
            sld_size -= 1
            style = 'url_extension'
        else:
            style = 'text'
        rle_append_beginning_modify(attr, (style, len(character.encode())))
    return attr

def colorize_req(s):
    path = s.split('?', 2)[0]
    i_query = len(path)
    i_last_slash = path.rfind('/')
    i_ext = path[i_last_slash + 1:].rfind('.')
    i_ext = i_last_slash + i_ext if i_ext >= 0 else len(s)
    in_val = False
    attr = []
    for i in range(len(s)):
        c = s[i]
        if ((i < i_query and c == '/') or
                (i < i_query and i > i_last_slash and c == '.') or
                (i == i_query)):
            a = 'url_punctuation'
        elif i > i_query:
            if in_val:
                if c == '&':
                    in_val = False
                    a = 'url_punctuation'
                else:
                    a = 'url_query_value'
            else:
                if c == '=':
                    in_val = True
                    a = 'url_punctuation'
                else:
                    a = 'url_query_key'
        elif i > i_ext:
            a = 'url_extension'
        elif i > i_last_slash:
            a = 'url_filename'
        else:
            a = 'text'
        urwid.util.rle_append_modify(attr, (a, len(c.encode())))
    return attr

def colorize_url(url):
    parts = url.split('/', 3)
    if len(parts) < 4 or len(parts[1]) > 0 or parts[0][-1:] != ':':
        return [('error', len(url))]  # bad URL
    return [
               (SCHEME_STYLES.get(parts[0], "scheme_other"), len(parts[0]) - 1),
               ('url_punctuation', 3),  # ://
           ] + colorize_host(parts[2]) + colorize_req('/' + parts[3])

def format_http_content_type(content_type: str) -> typing.Tuple[str, str]:
    content_type = content_type.split(";")[0]
    if content_type.endswith('/javascript'):
        style = 'content_script'
    elif content_type.startswith('text/'):
        style = 'content_text'
    elif (content_type.startswith('image/') or
          content_type.startswith('video/') or
          content_type.startswith('font/') or
          "/x-font-" in content_type):
        style = 'content_media'
    elif content_type.endswith('/json') or content_type.endswith('/xml'):
        style = 'content_data'
    elif content_type.startswith('application/'):
        style = 'content_raw'
    else:
        style = 'content_other'
    return content_type, style

def format_duration(duration: float) -> typing.Tuple[str, str]:
    pretty_duration = human.pretty_duration(duration)
    style = 'gradient_%02d' % int(99 - 100 * min(math.log2(1 + 1000 * duration) / 12, 0.99))
    return pretty_duration, style

def format_size(num_bytes: int) -> typing.Tuple[str, str]:
    pretty_size = human.pretty_size(num_bytes)
    style = 'gradient_%02d' % int(99 - 100 * min(math.log2(1 + num_bytes) / 20, 0.99))
    return pretty_size, style

def format_left_indicators(
        *,
        focused: bool,
        intercepted: bool,
        timestamp: float
):
    indicators: typing.List[typing.Union[str, typing.Tuple[str, str]]] = []
    if focused:
        indicators.append(("focus", ">>"))
    else:
        indicators.append("  ")
    pretty_timestamp = human.format_timestamp(timestamp)[-8:]
    if intercepted:
        indicators.append(("intercept", pretty_timestamp))
    else:
        indicators.append(("text", pretty_timestamp))
    return "fixed", 10, urwid.Text(indicators)

def format_right_indicators(
        *,
        replay: bool,
        marked: str,
):
    indicators: typing.List[typing.Union[str, typing.Tuple[str, str]]] = []
    if replay:
        indicators.append(("replay", SYMBOL_REPLAY))
    else:
        indicators.append(" ")
    if bool(marked):
        indicators.append(("mark", render_marker(marked)))
    else:
        indicators.append("  ")
    return "fixed", 3, urwid.Text(indicators)

@lru_cache(maxsize=800)
def format_http_flow_list(
        *,
        render_mode: RenderMode,
        focused: bool,
        marked: str,
        is_replay: bool,
        request_method: str,
        request_scheme: str,
        request_host: str,
        request_path: str,
        request_url: str,
        request_http_version: str,
        request_timestamp: float,
        request_is_push_promise: bool,
        intercepted: bool,
        response_code: typing.Optional[int],
        response_reason: typing.Optional[str],
        response_content_length: typing.Optional[int],
        response_content_type: typing.Optional[str],
        duration: typing.Optional[float],
        error_message: typing.Optional[str],
) -> urwid.Widget:
    req = []

    if render_mode is RenderMode.DETAILVIEW:
        req.append(fcol(human.format_timestamp(request_timestamp), "highlight"))
    else:
        if focused:
            req.append(fcol(">>", "focus"))
        else:
            req.append(fcol("  ", "focus"))

    method_style = HTTP_REQUEST_METHOD_STYLES.get(request_method, "method_other")
    req.append(fcol(request_method, method_style))

    if request_is_push_promise:
        req.append(fcol('PUSH_PROMISE', 'method_http2_push'))

    preamble_len = sum(x[1] for x in req) + len(req) - 1

    if request_http_version not in ("HTTP/1.0", "HTTP/1.1"):
        request_url += " " + request_http_version
    if intercepted and not response_code:
        url_style = "intercept"
    elif response_code or error_message:
        url_style = "text"
    else:
        url_style = "title"

    if render_mode is RenderMode.DETAILVIEW:
        req.append(
            urwid.Text([(url_style, request_url)])
        )
    else:
        req.append(truncated_plain(request_url, url_style))

    req.append(format_right_indicators(replay=is_replay, marked=marked))

    resp = [
        ("fixed", preamble_len, urwid.Text(""))
    ]
    if response_code:
        if intercepted:
            style = "intercept"
        else:
            style = ""

        status_style = style or HTTP_RESPONSE_CODE_STYLE.get(response_code // 100, "code_other")
        resp.append(fcol(SYMBOL_RETURN, status_style))
        resp.append(fcol(str(response_code), status_style))
        if response_reason and render_mode is RenderMode.DETAILVIEW:
            resp.append(fcol(response_reason, status_style))

        if response_content_type:
            ct, ct_style = format_http_content_type(response_content_type)
            resp.append(fcol(ct, style or ct_style))

        if response_content_length:
            size, size_style = format_size(response_content_length)
        elif response_content_length == 0:
            size = "[no content]"
            size_style = "text"
        else:
            size = "[content missing]"
            size_style = "text"
        resp.append(fcol(size, style or size_style))

        if duration:
            dur, dur_style = format_duration(duration)
            resp.append(fcol(dur, style or dur_style))
    elif error_message:
        resp.append(fcol(SYMBOL_RETURN, "error"))
        resp.append(urwid.Text([("error", error_message)]))

    return urwid.Pile([
        urwid.Columns(req, dividechars=1),
        urwid.Columns(resp, dividechars=1)
    ])

@lru_cache(maxsize=800)
def format_http_flow_table(
        *,
        render_mode: RenderMode,
        focused: bool,
        marked: str,
        is_replay: typing.Optional[str],
        request_method: str,
        request_scheme: str,
        request_host: str,
        request_path: str,
        request_url: str,
        request_http_version: str,
        request_timestamp: float,
        request_is_push_promise: bool,
        intercepted: bool,
        response_code: typing.Optional[int],
        response_reason: typing.Optional[str],
        response_content_length: typing.Optional[int],
        response_content_type: typing.Optional[str],
        duration: typing.Optional[float],
        error_message: typing.Optional[str],
) -> urwid.Widget:
    items = [
        format_left_indicators(
            focused=focused,
            intercepted=intercepted,
            timestamp=request_timestamp
        )
    ]

    if intercepted and not response_code:
        request_style = "intercept"
    else:
        request_style = ""

    scheme_style = request_style or SCHEME_STYLES.get(request_scheme, "scheme_other")
    items.append(fcol(fixlen(request_scheme.upper(), 5), scheme_style))

    if request_is_push_promise:
        method_style = 'method_http2_push'
    else:
        method_style = request_style or HTTP_REQUEST_METHOD_STYLES.get(request_method, "method_other")
    items.append(fcol(fixlen(request_method, 4), method_style))

    items.append(('weight', 0.25, TruncatedText(request_host, colorize_host(request_host), 'right')))
    items.append(('weight', 1.0, TruncatedText(request_path, colorize_req(request_path), 'left')))

    if intercepted and response_code:
        response_style = "intercept"
    else:
        response_style = ""

    if response_code:

        status = str(response_code)
        status_style = response_style or HTTP_RESPONSE_CODE_STYLE.get(response_code // 100, "code_other")

        if response_content_length and response_content_type:
            content, content_style = format_http_content_type(response_content_type)
            content_style = response_style or content_style
        elif response_content_length:
            content = ''
            content_style = 'content_none'
        elif response_content_length == 0:
            content = "[no content]"
            content_style = 'content_none'
        else:
            content = "[content missing]"
            content_style = 'content_none'

    elif error_message:
        status = 'err'
        status_style = 'error'
        content = error_message
        content_style = 'error'

    else:
        status = ''
        status_style = 'text'
        content = ''
        content_style = ''

    items.append(fcol(fixlen(status, 3), status_style))
    items.append(('weight', 0.15, truncated_plain(content, content_style, 'right')))

    if response_content_length:
        size, size_style = format_size(response_content_length)
        items.append(fcol(fixlen_r(size, 5), response_style or size_style))
    else:
        items.append(("fixed", 5, urwid.Text("")))

    if duration:
        duration_pretty, duration_style = format_duration(duration)
        items.append(fcol(fixlen_r(duration_pretty, 5), response_style or duration_style))
    else:
        items.append(("fixed", 5, urwid.Text("")))

    items.append(format_right_indicators(
        replay=bool(is_replay),
        marked=marked,
    ))
    return urwid.Columns(items, dividechars=1, min_width=15)

@lru_cache(maxsize=800)
def format_tcp_flow(
        *,
        render_mode: RenderMode,
        focused: bool,
        timestamp_start: float,
        marked: str,
        client_address,
        server_address,
        total_size: int,
        duration: typing.Optional[float],
        error_message: typing.Optional[str],
):
    conn = f"{human.format_address(client_address)} <-> {human.format_address(server_address)}"

    items = []

    if render_mode in (RenderMode.TABLE, RenderMode.DETAILVIEW):
        items.append(
            format_left_indicators(focused=focused, intercepted=False, timestamp=timestamp_start)
        )
    else:
        if focused:
            items.append(fcol(">>", "focus"))
        else:
            items.append(fcol("  ", "focus"))

    if render_mode is RenderMode.TABLE:
        items.append(fcol("TCP  ", SCHEME_STYLES["tcp"]))
    else:
        items.append(fcol("TCP", SCHEME_STYLES["tcp"]))

    items.append(('weight', 1.0, truncated_plain(conn, "text", 'left')))
    if error_message:
        items.append(('weight', 1.0, truncated_plain(error_message, "error", 'left')))

    if total_size:
        size, size_style = format_size(total_size)
        items.append(fcol(fixlen_r(size, 5), size_style))
    else:
        items.append(("fixed", 5, urwid.Text("")))

    if duration:
        duration_pretty, duration_style = format_duration(duration)
        items.append(fcol(fixlen_r(duration_pretty, 5), duration_style))
    else:
        items.append(("fixed", 5, urwid.Text("")))

    items.append(format_right_indicators(replay=False, marked=marked))

    return urwid.Pile([
        urwid.Columns(items, dividechars=1, min_width=15)
    ])

@lru_cache(maxsize=800)
def format_dns_flow(
        *,
        render_mode: RenderMode,
        focused: bool,
        intercepted: bool,
        marked: str,
        is_replay: typing.Optional[str],
        op_code: str,
        request_timestamp: float,
        domain: str,
        type: str,
        response_code: typing.Optional[str],
        response_code_http_equiv: int,
        answer: typing.Optional[str],
        error_message: str,
        duration: typing.Optional[float],
):
    items = []

    if render_mode in (RenderMode.TABLE, RenderMode.DETAILVIEW):
        items.append(format_left_indicators(focused=focused, intercepted=intercepted, timestamp=request_timestamp))
    else:
        items.append(fcol(">>" if focused else "  ", "focus"))

    scheme_style = "intercepted" if intercepted else SCHEME_STYLES["dns"]
    t = f"DNS {op_code}"
    if render_mode is RenderMode.TABLE:
        t = fixlen(t, 10)
    items.append(fcol(t, scheme_style))
    items.append(('weight', 0.5, TruncatedText(domain, colorize_host(domain), 'right')))
    items.append(fcol("(" + fixlen(type, 5)[:len(type)] + ") =", "text"))

    items.append(("weight", 1, (
        truncated_plain("..." if answer is None else "?" if not answer else answer, "text")
        if error_message is None else
        truncated_plain(error_message, "error")
    )))
    status_style = "intercepted" if intercepted else HTTP_RESPONSE_CODE_STYLE.get(response_code_http_equiv // 100, "code_other")
    items.append(fcol(fixlen("" if response_code is None else response_code, 9), status_style))

    if duration:
        duration_pretty, duration_style = format_duration(duration)
        items.append(fcol(fixlen_r(duration_pretty, 5), duration_style))
    else:
        items.append(("fixed", 5, urwid.Text("")))

    items.append(format_right_indicators(
        replay=bool(is_replay),
        marked=marked,
    ))
    return urwid.Pile([
        urwid.Columns(items, dividechars=1, min_width=15)
    ])

def format_flow(
        f: flow.Flow,
        *,
        render_mode: RenderMode,
        hostheader: bool = False,  # pass options directly if we need more stuff from them
        focused: bool = True,
) -> urwid.Widget:
    """
    This functions calls the proper renderer depending on the flow type.
    We also want to cache the renderer output, so we extract all attributes
    relevant for display and call the render with only that. This assures that rows
    are updated if the flow is changed.
    """
    duration: typing.Optional[float]
    error_message: typing.Optional[str]
    if f.error:
        error_message = f.error.msg
    else:
        error_message = None

    if isinstance(f, TCPFlow):
        total_size = 0
        for message in f.messages:
            total_size += len(message.content)
        if f.messages:
            duration = f.messages[-1].timestamp - f.client_conn.timestamp_start
        else:
            duration = None
        return format_tcp_flow(
            render_mode=render_mode,
            focused=focused,
            timestamp_start=f.client_conn.timestamp_start,
            marked=f.marked,
            client_address=f.client_conn.peername,
            server_address=f.server_conn.address,
            total_size=total_size,
            duration=duration,
            error_message=error_message,
        )
    elif isinstance(f, DNSFlow):
        if f.response:
            duration = f.response.timestamp - f.request.timestamp
            response_code_str: typing.Optional[str] = dns.response_codes.to_str(f.response.response_code)
            response_code_http_equiv = dns.response_codes.http_equiv_status_code(f.response.response_code)
            answer = ", ".join(str(x) for x in f.response.answers)
        else:
            duration = None
            response_code_str = None
            response_code_http_equiv = 0
            answer = None
        return format_dns_flow(
            render_mode=render_mode,
            focused=focused,
            intercepted=f.intercepted,
            marked=f.marked,
            is_replay=f.is_replay,
            op_code=dns.op_codes.to_str(f.request.op_code),
            request_timestamp=f.request.timestamp,
            domain=f.request.questions[0].name if f.request.questions else "",
            type=dns.types.to_str(f.request.questions[0].type) if f.request.questions else "",
            response_code=response_code_str,
            response_code_http_equiv=response_code_http_equiv,
            answer=answer,
            error_message=error_message,
            duration=duration,
        )
    elif isinstance(f, HTTPFlow):
        intercepted = f.intercepted
        response_content_length: typing.Optional[int]
        if f.response:
            if f.response.raw_content is not None:
                response_content_length = len(f.response.raw_content)
            else:
                response_content_length = None
            response_code: typing.Optional[int] = f.response.status_code
            response_reason: typing.Optional[str] = f.response.reason
            response_content_type = f.response.headers.get("content-type")
            if f.response.timestamp_end:
                duration = max([f.response.timestamp_end - f.request.timestamp_start, 0])
            else:
                duration = None
        else:
            response_content_length = None
            response_code = None
            response_reason = None
            response_content_type = None
            duration = None

        scheme = f.request.scheme
        if f.websocket is not None:
            if scheme == "https":
                scheme = "wss"
            elif scheme == "http":
                scheme = "ws"

        if render_mode in (RenderMode.LIST, RenderMode.DETAILVIEW):
            render_func = format_http_flow_list
        else:
            render_func = format_http_flow_table
        return render_func(
            render_mode=render_mode,
            focused=focused,
            marked=f.marked,
            is_replay=f.is_replay,
            request_method=f.request.method,
            request_scheme=scheme,
            request_host=f.request.pretty_host if hostheader else f.request.host,
            request_path=f.request.path,
            request_url=f.request.pretty_url if hostheader else f.request.url,
            request_http_version=f.request.http_version,
            request_timestamp=f.request.timestamp_start,
            request_is_push_promise='h2-pushed-stream' in f.metadata,
            intercepted=intercepted,
            response_code=response_code,
            response_reason=response_reason,
            response_content_length=response_content_length,
            response_content_type=response_content_type,
            duration=duration,
            error_message=error_message,
        )

    else:
        raise NotImplementedError()


"""
anime.py contains the base classes required for other anime classes.
"""
import os
import logging
import copy
import importlib

from anime_downloader.sites.exceptions import AnimeDLError, NotFoundError
from anime_downloader import util
from anime_downloader.config import Config
from anime_downloader.extractors import get_extractor
from anime_downloader.downloader import get_downloader

logger = logging.getLogger(__name__)

class Anime:
    """
    Base class for all anime classes.

    Parameters
    ----------
    url: string
        URL of the anime.
    quality: One of ['360p', '480p', '720p', '1080p']
        Quality of episodes
    fallback_qualities: list
        The order of fallback.

    Attributes
    ----------
    sitename: str
        name of the site
    title: str
        Title of the anime
    meta: dict
        metadata about the anime. [Can be empty]
    QUALITIES: list
        Possible qualities for the site
    """
    sitename = ''
    title = ''
    meta = dict()
    subclasses = {}
    QUALITIES = ['360p', '480p', '720p', '1080p']

    @classmethod
    def search(cls, query):
        """
        Search searches for the anime using the query given.

        Parameters
        ----------
        query: str
            query is the query keyword to be searched.

        Returns
        -------
        list
            List of :py:class:`~anime_downloader.sites.anime.SearchResult`
        """
        return

    def __init__(self, url=None, quality='720p',
                 fallback_qualities=None,
                 _skip_online_data=False):
        self.url = url

        if fallback_qualities is None:
            fallback_qualities = ['720p', '480p', '360p']

        self._fallback_qualities = [
            q for q in fallback_qualities if q in self.QUALITIES]

        if quality in self.QUALITIES:
            self.quality = quality
        else:
            raise AnimeDLError(
                'Quality {0} not found in {1}'.format(quality, self.QUALITIES))

        if not _skip_online_data:
            logger.info('Extracting episode info from page')
            self._episode_urls = self.get_data()
            self._len = len(self._episode_urls)

    @classmethod
    def verify_url(cls, url):
        if cls.sitename in url:
            return True
        return False

    @property
    def config(self):
        return Config['siteconfig'][self.sitename]

    def __init_subclass__(cls, sitename, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses[sitename] = cls

    @classmethod
    def factory(cls, sitename: str):
        """
        factory returns the appropriate subclass for the given site name.

        Parameters
        ----------
        sitename: str
            sitename is the name of the site

        Returns
        -------
        subclass of :py:class:`Anime`
            Sub class of :py:class:`Anime`
        """
        return cls.subclasses[sitename]

    @classmethod
    def new_anime(cls, sitename: str):
        """
        new_anime is a factory which returns the anime class corresposing to
        `sitename`

        Returns
        -------
        subclass of Anime
        """
        module = importlib.import_module(
            'anime_downloader.sites.{}'.format(sitename)
        )
        for c in dir(module):
            if issubclass(c, cls):
                return c
        raise ImportError("Cannot find subclass of {}".format(cls))

    def get_data(self):
        """
        get_data is called inside the :code:`__init__` of
        :py:class:`~anime_downloader.sites.anime.BaseAnime`. It is used to get
        the necessary data about the anime and it's episodes.

        This function calls
        :py:class:`~anime_downloader.sites.anime.BaseAnime._scarpe_episodes`
        and
        :py:class:`~anime_downloader.sites.anime.BaseAnime._scrape_metadata`

        TODO: Refactor this so that classes which need not be soupified don't
        have to overload this function.

        Returns
        -------
        list
            A list of tuples of episodes containing episode name and
            episode url.
            Ex::
                [('1', 'https://9anime.is/.../...', ...)]

        """
        self._episode_urls = []
        try:
            self._scrape_metadata()
        except Exception as e:
            logger.debug('Metadata scraping error: {}'.format(e))

        self._episode_urls = self._scrape_episodes()
        self._len = len(self._episode_urls)

        logger.debug('EPISODE IDS: length: {}, ids: {}'.format(
            self._len, self._episode_urls))

        if not isinstance(self._episode_urls[0], tuple):
            self._episode_urls = [(no+1, id) for no, id in
                                enumerate(self._episode_urls)]

        return self._episode_urls

    def __getitem__(self, index):
        episode_class = AnimeEpisode.subclasses[self.sitename]
        if isinstance(index, int):
            try:
                ep_id = self._episode_urls[index]
            except IndexError as e:
                raise RuntimeError("No episode found with index") from e
            return episode_class(ep_id[1], parent=self,
                                 ep_no=ep_id[0])
        elif isinstance(index, slice):
            anime = copy.deepcopy(self)
            try:
                anime._episode_urls = anime._episode_urls[index]
            except IndexError as e:
                raise RuntimeError("No episode found with index") from e
            return anime
        return None

    def __iter__(self):
        episode_class = AnimeEpisode.subclasses[self.sitename]
        for ep_id in self._episode_urls:
            yield episode_class(ep_id[1], parent=self, ep_no=ep_id[0])

    def __repr__(self):
        return '''
Site: {name}
Anime: {title}
Episode count: {length}
'''.format(name=self.sitename, title=self.title, length=len(self))

    def __len__(self):
        return self._len

    def __str__(self):
        return self.title

    def _scarpe_episodes(self):
        """
        _scarpe_episodes is function which has to be overridden by the base
        classes to scrape the episode urls from the web page.

        Parameters
        ----------
        soup: `bs4.BeautifulSoup`
            soup is the html of the anime url after passing through
            BeautifulSoup.

        Returns
        -------
        :code:`list` of :code:`str`
            A list of episode urls.
        """
        return

    def _scrape_metadata(self):
        """
        _scrape_metadata is function which has to be overridden by the base
        classes to scrape the metadata of anime from the web page.

        Parameters
        ----------
        soup: :py:class:`bs4.BeautifulSoup`
            soup is the html of the anime url after passing through
            BeautifulSoup.
        """
        return

class AnimeEpisode:
    """
    Base class for all Episode classes.

    Parameters
    ----------
    url: string
        URL of the episode.
    quality: One of ['360p', '480p', '720p', '1080p']
        Quality of episode
    fallback_qualities: list
        The order of fallback.

    Attributes
    ----------
    sitename: str
        name of the site
    title: str
        Title of the anime
    meta: dict
        metadata about the anime. [Can be empty]
    ep_no: string
        Episode number/title of the episode
    pretty_title: string
        Pretty title of episode in format <animename>-<ep_no>
    """
    QUALITIES = []
    title = ''
    stream_url = ''
    subclasses = {}

    def __init__(self, url, parent: Anime = None, ep_no=None):

        self.ep_no = ep_no
        self.url = url
        self.quality = parent.quality
        self.QUALITIES = parent.QUALITIES
        self._parent = parent
        self._sources = None
        self.pretty_title = '{}-{}'.format(self._parent.title, self.ep_no)

        logger.debug("Extracting stream info of id: {}".format(self.url))

        def try_data():
            self.get_data()
            # Just to verify the source is acquired
            self.source().stream_url
        try:
            try_data()
        except NotFoundError:
            # Issue #28
            qualities = copy.copy(self._parent._fallback_qualities)
            try:
                qualities.remove(self.quality)
            except ValueError:
                pass
            for quality in qualities:
                logger.warning('Quality {} not found. Trying {}.'.format(
                    self.quality, quality))
                self.quality = quality
                try:
                    try_data()
                    return
                except NotFoundError:
                    pass
            logger.warning(f'Skipping episode: {self.ep_no}')

    def __init_subclass__(cls, sitename: str, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses[sitename] = cls
        cls.sitename = sitename

    @classmethod
    def factory(cls, sitename: str):
        return cls.subclasses[sitename]

    @property
    def config(self):
        return Config['siteconfig'][self.sitename]

    def source(self, index=0):
        """
        Get the source for episode

        Returns
        -------
        `anime_downloader.extractors.base_extractor.BaseExtractor`
            Extractor depending on the source.
        """
        if not self._sources:
            self.get_data()
        try:
            sitename, url = self._sources[index]
        except TypeError:
            return self._sources[index]
        except IndexError:
            raise NotFoundError("No episode sources found.")

        ext = get_extractor(sitename)(url, quality=self.quality)
        self._sources[index] = ext

        return ext

    def get_data(self):
        self._sources = self._get_sources()
        logger.debug('Sources : {}'.format(self._sources))

    def _get_sources(self):
        raise NotImplementedError

    def sort_sources(self, data):
        """
        Formatted data should look something like this

        [
        {'extractor': 'mp4upload', 'url': 'https://twist.moe/mp4upload/...', 'server': 'mp4upload', 'version': 'subbed'},
        {'extractor': 'vidstream', 'url': 'https://twist.moe/vidstream/...', 'server': 'vidstream', 'version': 'dubbed'},
        {'extractor': 'no_extractor', 'url': 'https://twist.moe/anime/...', 'server': 'default', 'version': 'subbed'}
        ]

        extractor = the extractor the link should be passed to
        url = url to be passed to the extractor
        server = the server name used in config
        version = subbed/dubbed

        The config should consist of a list with servers in preferred order and a preferred language, eg

        "servers":["vidstream","default","mp4upload"],
        "version":"subbed"

        Using the example above, this function will return: [('no_extractor', 'https://twist.moe/anime/...')]
        as it prioritizes preferred language over preferred server
        """

        version = self.config.get('version','subbed') #TODO add a flag for this
        servers = self.config.get('servers',[''])

        logger.debug('Data : {}'.format(data))

        #Sorts the dicts by preferred server in config
        sorted_by_server = sorted(data, key=lambda x: servers.index(x['server']) if x['server'] in servers else len(data))

        #Sorts the above by preferred language
        #resulting in a list with the dicts sorted by language and server
        #with language being prioritized over server
        sorted_by_lang = list(sorted(sorted_by_server, key=lambda x: x['version'] == version, reverse=True))
        logger.debug('Sorted sources : {}'.format(sorted_by_lang))

        return '' if not sorted_by_lang else [(sorted_by_lang[0]['extractor'],sorted_by_lang[0]['url'])]

    def download(self, force=False, path=None,
                 format='{anime_title}_{ep_no}', range_size=None):
        """
        Downloads episode. This might be removed in a future release.

        Parameters
        ----------
        force: bool
            Whether to force download or not.
        path: string
            Path to the directory/file where the file should be downloaded to.
        format: string
            The format of the filename if not provided.
        """
        # TODO: Remove this shit
        logger.info('Downloading {}'.format(self.pretty_title))
        if format:
            file_name = util.format_filename(format, self)+'.mp4'

        if path is None:
            path = './' + file_name
        if path.endswith('.mp4'):
            path = path
        else:
            path = os.path.join(path, file_name)

        Downloader = get_downloader('http')
        downloader = Downloader(self.source(),
                                path, force, range_size=range_size)

        downloader.download()

class SearchResult:
    """
    SearchResult class holds the search result of a search done by an Anime
    class

    Parameters
    ----------
    title: str
        Title of the anime.
    url: str
        URL of the anime
    poster: str
        URL for the poster of the anime.
    meta: dict
        Additional metadata regarding the anime.

    Attributes
    ----------
    title: str
        Title of the anime.
    url: str
        URL of the anime
    poster: str
        URL for the poster of the anime.
    meta: dict
        Additional metadata regarding the anime.
    meta_info: dict
        Metadata regarding the anime. Not shown in the results, used to match with MAL
    """

    def __init__(self, title, url, poster='', meta='', meta_info={}):
        self.title = title
        self.url = url
        self.poster = poster
        self.meta = meta
        self.meta_info = meta_info

    def __repr__(self):
        return '<SearchResult Title: {} URL: {}>'.format(self.title, self.url)

    def __str__(self):
        return self.title

    @property
    def pretty_metadata(self):
        """
        pretty_metadata is the prettified version of metadata
        """
        if self.meta:
            return ' | '.join(val for _, val in self.meta.items())
        return ''


import sys

from fastapi import FastAPI, Request

from .exceptions import CustomHTTPException
from .routers import oauth, webhooks

if sys.version_info[1] < 7:
    from backports.datetime_fromisoformat import MonkeyPatch

    MonkeyPatch.patch_fromisoformat()

app = FastAPI()

@app.exception_handler(CustomHTTPException)
def custom_http_exception_handler(request: Request, exc: CustomHTTPException):
    return exc.response

app.include_router(oauth.router)
app.include_router(webhooks.router)


#Copyright ReportLab Europe Ltd. 2000-2017
#see license.txt for license details
#history https://hg.reportlab.com/hg-public/reportlab/log/tip/tools/pythonpoint/styles/__init__.py


# -*- coding: utf-8 -*-
"""
Fuel inventory library (UOX)

Script to run computations. It will produce a set of folders and outputfiles
and a csv file storing linking the output file paths to the BU, CT, IE values.

zsolt elter 2019
"""
import numpy as np
import os
import math
#import pandas as pd
#from PDfunctions import *
def fuelinput(wp):
    """
    function to calculate the weight percentage of MOX nuclides

    formulae from http://holbert.faculty.asu.edu/eee460/NumberDensity.pdf

    Parameters
    ----------
    wp : float
        Plutonium content in percentage

    Returns
    -------
    fuelstr : str
        Serpent formatted material composition

    Notes
    -----
    1, Right now the temperature is hard coded (ie ZAID ends with '.15c'), this can be modified.
    2, Right now the density of fuel is hard coded, this can be modified
    3, The fuel string includes Cf nuclides with 0.0w%. This is to force Serpent2 to include these
    nuclides. The reason to include them because they might be relevant in subsequent neutron coincidence
    based calculations.
    """
    u=1000*1.660539040e-27  #g
    NA=6.0221409e23   ##/mol
    M={'U235': 235.0439299*u*NA,
       'U234': 234.0409521*u*NA,
       'U238': 238.05078826*u*NA,
       'Pu238': 238.0495599*u*NA,
       'Pu239': 239.0521634*u*NA,
       'Pu240': 240.0538135*u*NA,
       'Pu241': 241.0568515*u*NA,
       'Pu242': 242.0587426*u*NA}

    Puvec={'Pu238':2.5/100,'Pu239':54.7/100,'Pu240':26.1/100,'Pu241':9.5/100,'Pu242':7.2/100}
    Uvec={'U234':0.0012/100,'U235':0.25/100,'U238':99.7488/100} #czsolti 0.00119 rounded to get 1
    MO16= 15.99491461956*u*NA
    rhoMOX=10.5     #g/cm3 czsolti this density falls out from the equations

    wp=wp/100

    MU=1/sum([Uvec[iso]/M[iso] for iso in Uvec])
    MPu=1/sum([Puvec[iso]/M[iso] for iso in Puvec])

    MHM=(1-wp)*MU+wp*MPu
    MMOX=MHM+2*MO16

    rhoHM=rhoMOX*(MHM/MMOX)
    rhoO=rhoMOX*(MO16/MMOX)

    MVOL={}
    for iso in Uvec:
        MVOL[iso] = (1-wp)*Uvec[iso]*rhoHM
    for iso in Puvec:
        MVOL[iso] = wp*Puvec[iso]*rhoHM

    M_O16=(rhoO*2)
    M_TOT=sum(MVOL.values())+M_O16

    fuelstr='mat MOX -10.5 burn 1'
    fuelstr=fuelstr+'\n 92234.15c  -%.8f'%(MVOL['U234']/M_TOT)
    fuelstr=fuelstr+'\n 92235.15c  -%.8f'%(MVOL['U235']/M_TOT)
    fuelstr=fuelstr+'\n 92238.15c  -%.8f'%(MVOL['U238']/M_TOT)
    fuelstr=fuelstr+'\n 94238.15c  -%.8f'%(MVOL['Pu238']/M_TOT)
    fuelstr=fuelstr+'\n 94239.15c  -%.8f'%(MVOL['Pu239']/M_TOT)
    fuelstr=fuelstr+'\n 94240.15c  -%.8f'%(MVOL['Pu240']/M_TOT)
    fuelstr=fuelstr+'\n 94241.15c  -%.8f'%(MVOL['Pu241']/M_TOT)
    fuelstr=fuelstr+'\n 94242.15c  -%.8f'%(MVOL['Pu242']/M_TOT)
    fuelstr=fuelstr+'\n 8016.15c   -%.8f'%(M_O16/M_TOT)
    fuelstr=fuelstr+'\n 98249.15c  -0.0'
    fuelstr=fuelstr+'\n 98250.15c  -0.0'
    fuelstr=fuelstr+'\n 98251.15c  -0.0'
    fuelstr=fuelstr+'\n 98252.15c  -0.0'
    fuelstr=fuelstr+'\n 98253.15c  -0.0'
    fuelstr=fuelstr+'\n 98254.15c  -0.0'
    return fuelstr

### SCRIPT to run

###Init array for CTs-> can be modified if other CT values are preferred.
CT=0
CTs=[0]
decstep=[]
while CT<70*365:
    if CT<10*365:
        decstep.append(91.25)
        CT=CT+91.25
        CTs.append(CT)
    elif CT<40*365:
        decstep.append(2*91.25)
        CT=CT+2*91.25
        CTs.append(CT)
    else:
        decstep.append(4*91.25)
        CT=CT+4*91.25
        CTs.append(CT)

#csv header
csvstr=',BU,CT,IE,fuelType,reactorType,serpent\n'

#path to be updated
path=os.getcwd()+'/'
dataFrame='fuellog_strategicPWR_MOX.csv'
inputFileRun = open(dataFrame,'a')
inputFileRun.write(csvstr)
inputFileRun.close()

inputFileBU = open('MOX_manyBU')
inputFileBURefStr = inputFileBU.read()
inputFileBU.close()

inputFileCT = open('MOX_manyCT')
inputFileCTRefStr = inputFileCT.read()
inputFileCT.close()

IE=np.linspace(4,10,31)
idfuel=0
for ie in IE:
    fstr=fuelinput(ie)
    inputFileBUStr = inputFileBURefStr
    inputFileBUStr = inputFileBUStr.replace('fuelstr', fstr)

    sfile='sPWR_MOX_IE_%d'%(ie*10)
    os.chdir(path+'serpent_files/')
    os.system('mkdir IE%d'%(ie*10))
    os.chdir(path+'serpent_files/IE%d/'%(ie*10))
    inputFileRun = open(sfile,'w')
    inputFileRun.write(inputFileBUStr)
    inputFileRun.close()

    #pathV=path+'serpent_filesPWR_BIC/'
    #os.system('ssh '+node+' "nice sss2 '+pathV+sfile+' -omp 64"')
    os.system('nice sss2 '+sfile+' -omp 64')

    bu=5.0
    for bui in range(10,147):  #5-70 MWd/kgU
        if bui not in [0,21,42,63,84,105,126]:#downtime
            os.chdir(path+'serpent_files/IE%d/'%(ie*10))
            spentmat = open(sfile+'.bumat'+str(bui)).read()
            spentmat=spentmat.replace('MOXp1r1','MOX')
            spentmat=spentmat.replace('\n            1001.15c',' burn 1\n            1001.15c')

            inputFileCTStr = inputFileCTRefStr
            inputFileCTStr = inputFileCTStr.replace('matstr', spentmat)

            sfilect='sPWR_MOX_IE_%d_BU_%d'%(ie*10,bu*10)

            os.system('mkdir BU%d'%(bu*10))
            os.chdir(path+'serpent_files/IE%d/BU%d/'%(ie*10,bu*10))
            inputFileRun = open(sfilect,'w')
            inputFileRun.write(inputFileCTStr)
            inputFileRun.close()
            os.system('nice sss2 '+sfilect+' -omp 64')
            for cti in range(131):
                filepath=path+'serpent_files/IE%d/BU%d/'%(ie*10,bu*10)+sfilect+'.bumat'+str(cti)

                csvstr='%d,%.2f,%.2f,%.2f,MOX,PWR,%s\n'%(idfuel,bu,CTs[cti],ie,filepath)
                idfuel=idfuel+1
                os.chdir(path)
                inputFileRun = open(dataFrame,'a')
                inputFileRun.write(csvstr)
                inputFileRun.close()
            bu=bu+0.5


from supervisor.supervisord import SupervisorStates
from supervisor.xmlrpc import Faults
from supervisor.xmlrpc import RPCError

API_VERSION = '0.2'

class CacheNamespaceRPCInterface:
    """ A Supervisor RPC interface that provides the ability
    to cache abritrary data in the Supervisor instance as key/value pairs.
    """

    def __init__(self, supervisord):
        self.supervisord = supervisord
        self.cache = {}

    def _update(self, text):
        self.update_text = text # for unit tests, mainly

        state = self.supervisord.get_state()

        if state == SupervisorStates.SHUTDOWN:
            raise RPCError(Faults.SHUTDOWN_STATE)

        # XXX fatal state

    # RPC API methods

    def getAPIVersion(self):
        """ Return the version of the RPC API used by supervisor_cache

        @return string  version
        """
        self._update('getAPIVersion')
        return API_VERSION

    def getKeys(self):
        """ Return keys for all data stored in the cache

        @return  array   An array of strings representing cache keys
        """
        self._update('getKeys')
        return sorted(self.cache.keys())

    def getCount(self):
        """ Return a count of all items in the cache

        @return  integer   Count of items
        """
        self._update('getCount')
        return len(self.cache)

    def store(self, key, data):
        """ Store a string value in the cache, referenced by 'key'

        @param  string key   A string to use as a cache key
        @param  string data  A string for cache value
        @return boolean      Always true unless error
        """
        self._update('store')
        self._validateKey(key)

        # XMLRPC can handle non-string values
        #if not isinstance(data, str):
        #    why = 'Cache data must be a string'
        #    raise RPCError(Faults.INCORRECT_PARAMETERS, why)

        self.cache[key] = data
        return True

    def fetch(self, key):
        """ Retrieve data from cache stored under 'key'

        @param  string key  The cache key
        @return string      Cache data stored at key
        """
        self._update('fetch')
        self._validateKey(key)

        data = self.cache.get(key)
        if data is None:
            raise RPCError(Faults.BAD_NAME)
        return data

    def delete(self, key):
        """ Delete data stored in cache under 'key'

        @param  string  key  The key to delete from the cache
        @return boolean      Always true unless error.
        """
        self._update('delete')
        self._validateKey(key)

        if key in self.cache:
            del self.cache[key]
        return True

    def clear(self):
        """ Clear the cache

        @return boolean  Always true unless error.
        """
        self._update('clear')
        self.cache.clear()
        return True

    def _validateKey(self, key):
        """ validate 'key' is suitable for a cache key name """
        if not isinstance(key, str) or (key == ''):
            why = 'Cache key must be a non-empty string'
            raise RPCError(Faults.BAD_NAME, why)

def make_cache_rpcinterface(supervisord, **config):
    return CacheNamespaceRPCInterface(supervisord)


from tensorflow.keras import layers, models, datasets, optimizers
import numpy as np

def neural_network_spatial():
    input_ = layers.Input(shape=(32,32,3))
    cnn = layers.Conv2D(16, (3,3), activation="relu") (input_)
    cnn = layers.SpatialDropout2D(0.2) (cnn)
    cnn = layers.MaxPooling2D() (cnn)

    cnn = layers.Conv2D(32, (3,3), activation="relu") (cnn)
    cnn = layers.SpatialDropout2D(0.5) (cnn)
    cnn = layers.MaxPooling2D() (cnn)

    flatten = layers.GlobalMaxPooling2D() (cnn)

    dense = layers.Dense(32, activation="relu") (flatten)
    dense = layers.Dropout(0.5) (dense)
    dense = layers.Dense(16,  activation="relu") (dense)

    output = layers.Dense(10, activation="softmax") (dense)

    opt = optimizers.Adam()

    m= models.Model(input_, output)
    m.compile(optimizer=opt,
              loss='categorical_crossentropy',
              metrics=['accuracy'])

    return m
model = neural_network_spatial() # get model
print(model.summary())


import coloredlogs
import logging
import os

logging.basicConfig(
    filename="plex_doctor.log",
    level=logging.DEBUG,
    format='%(levelname)s: "%(asctime)s - %(message)s',
)

log = logging.getLogger("PLEX-DOCTOR")
log.setLevel(logging.DEBUG)

LOGLEVEL = os.environ.get("LOGLEVEL", "INFO").upper()
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(
    logging.Formatter('%(levelname)s: "%(asctime)s - %(message)s')
)

log.addHandler(stream_handler)

coloredlogs.install(LOGLEVEL, logger=log)


#!/usr/bin/env python
"""VHDL generation of unary operators"""

import common

__author__ = "Jon Dawson"
__copyright__ = "Copyright 2010, Jonathan P Dawson"
__license__ = "MIT"
__version__ = "0.1.3"
__maintainer__ = "Jon Dawson"
__email__ = "chips@jondawson.org.uk"
__status__ = "Prototype"

def write(stream):

    identifier = stream.get_identifier()
    bits = stream.get_bits()
    identifier_a = stream.a.get_identifier()
    constant = stream.constant

    expressions = {
    'srn' : "STREAM_{0} <= SR( STREAM_{1}, {2})",
    'sln' : "STREAM_{0} <= SL( STREAM_{1}, {2})",
    'abs' : "STREAM_{0} <= ABSOLUTE(STREAM_{1})",
    'invert' : "STREAM_{0} <= not STREAM_{1}",
    'not'  : "STREAM_{0} <= LNOT(STREAM_{1})",
    }

    expression = expressions[stream.function].format(identifier, identifier_a, common.binary(constant, bits))
    expression = "          {0};".format(expression)

    ports = [
    ]

    declarations = [
    "  signal STATE_{0}      : BINARY_STATE_TYPE;".format(identifier),
    "  signal STREAM_{0}     : std_logic_vector({1} downto 0);".format(identifier, bits - 1),
    "  signal STREAM_{0}_STB : std_logic;".format(identifier),
    "  signal STREAM_{0}_ACK : std_logic;".format(identifier),
    "",
    ]

    definitions = [
    "  --file: {0}, line: {1}".format(stream.filename, stream.lineno),
    "  --STREAM {0} Unary({1}, {2}, '{3}')".format(identifier, identifier_a, constant, stream.function),
    "  process",
    "  begin",
    "    wait until rising_edge(CLK);",
    "    case STATE_{0} is".format(identifier),
    "      when BINARY_INPUT =>",
    "        if STREAM_{0}_STB = '1' then".format(identifier_a),
    "          STREAM_{0}_ACK <= '1';".format(identifier_a),
    expression,
    "          STREAM_{0}_STB <= '1';".format(identifier),
    "          STATE_{0} <= BINARY_OUTPUT;".format(identifier),
    "        end if;",
    "      when BINARY_OUTPUT =>",
    "        STREAM_{0}_ACK <= '0';".format(identifier_a),
    "        if STREAM_{0}_ACK = '1' then".format(identifier),
    "           STREAM_{0}_STB <= '0';".format(identifier),
    "           STATE_{0} <= BINARY_INPUT;".format(identifier),
    "        end if;",
    "     end case;",
    "     if RST = '1' then",
    "       STREAM_{0}_STB <= '0';".format(identifier),
    "       STREAM_{0}_ACK <= '0';".format(identifier_a),
    "       STATE_{0} <= BINARY_INPUT;".format(identifier),
    "     end if;",
    "  end process;",
    "",
    ]

    return ports, declarations, definitions


from arm.logicnode.arm_nodes import *

class OnContactArrayNode(ArmLogicTreeNode):
    """Activates the output when the given rigid body make contact with other given rigid bodies."""
    bl_idname = 'LNOnContactArrayNode'
    bl_label = 'On Contact Array'
    arm_section = 'contact'
    arm_version = 1

    property0: EnumProperty(
        items = [('begin', 'Begin', 'The contact between the rigid bodies begins'),
                 ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'),
                 ('end', 'End', 'The contact between the rigid bodies ends')],
        name='', default='begin')

    def init(self, context):
        super(OnContactArrayNode, self).init(context)
        self.add_input('ArmNodeSocketObject', 'RB')
        self.add_input('ArmNodeSocketArray', 'RBs')

        self.add_output('ArmNodeSocketAction', 'Out')

    def draw_buttons(self, context, layout):
        layout.prop(self, 'property0')


#!/usr/bin/env python

from load import ROOT as R
from gna.unittest import *
from gna.env import env
import gna.constructors as C
import numpy as N
from gna import context
import gna.bindings.arrayview

@floatcopy(globals(), True)
def test_vararray_preallocated_v01(function_name):
    ns = env.globalns(function_name)

    names  = [ 'zero', 'one', 'two', 'three', 'four', 'five' ]
    values = N.arange(len(names), dtype=context.current_precision_short())
    variables = R.vector('variable<%s>'%context.current_precision())()

    with context.allocator(100) as allocator:
        for name, value in zip(names, values):
            par = ns.defparameter(name, central=value, relsigma=0.1)
            variables.push_back(par.getVariable())

        with ns:
            vsum = C.VarSum(names, 'sum', ns=ns)
            vsum_var=ns['sum'].get()
            variables.push_back(vsum_var.getVariable())
            vprod = C.VarProduct(names, 'product', ns=ns)
            vprod_var=ns['product'].get()
            variables.push_back(vprod_var.getVariable())

        va = C.VarArrayPreallocated(variables)

    pool=allocator.view()
    res=va.vararray.points.data()

    values_all = N.zeros(shape=values.size+2, dtype=values.dtype)
    values_all[:-2]=values
    values_all[-2]=values_all[:-2].sum()
    values_all[-1]=values_all[:-2].prod()

    print('Python array:', values_all)
    print('VarArray (preallocated):', res)
    print('Pool:', pool)

    assert (values_all==res).all()
    assert (values_all==pool).all()
    assert (res==pool).all()

    for i, (val, name) in enumerate(enumerate(names, 2)):
        ns[name].set(val)
        values_all[i]=val
        values_all[-2]=values_all[:-2].sum()
        values_all[-1]=values_all[:-2].prod()
        res=va.vararray.points.data()

        print('Iteration', i)
        print('    Python array:', values_all)
        print('    VarArray (preallocated):', res)

        assert (values_all==res).all()
        assert (values_all==pool).all()
        assert (res==pool).all()

if __name__ == '__main__':
    run_unittests(globals())


import json
import datetime
from pyld import jsonld

from core.testing import DatabaseTest
from core.util.datetime_helpers import utc_now
from .test_controller import ControllerTest

from core.model import (
    Annotation,
    create,
)

from api.annotations import (
    AnnotationWriter,
    AnnotationParser,
)
from api.problem_details import *

class AnnotationTest(DatabaseTest):

    def _patron(self):
        """Create a test patron who has opted in to annotation sync."""
        patron = super(AnnotationTest, self)._patron()
        patron.synchronize_annotations = True
        return patron

class TestAnnotationWriter(AnnotationTest, ControllerTest):

    def test_annotations_for(self):
        patron = self._patron()

        # The patron doesn't have any annotations yet.
        assert [] == AnnotationWriter.annotations_for(patron)

        identifier = self._identifier()
        annotation, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier,
            motivation=Annotation.IDLING,
        )

        # The patron has one annotation.
        assert [annotation] == AnnotationWriter.annotations_for(patron)
        assert [annotation] == AnnotationWriter.annotations_for(patron, identifier)

        identifier2 = self._identifier()
        annotation2, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier2,
            motivation=Annotation.IDLING,
        )

        # The patron has two annotations for different identifiers.
        assert set([annotation, annotation2]) == set(AnnotationWriter.annotations_for(patron))
        assert [annotation] == AnnotationWriter.annotations_for(patron, identifier)
        assert [annotation2] == AnnotationWriter.annotations_for(patron, identifier2)

    def test_annotation_container_for(self):
        patron = self._patron()

        with self.app.test_request_context("/"):
            container, timestamp = AnnotationWriter.annotation_container_for(patron)

            assert (set([AnnotationWriter.JSONLD_CONTEXT, AnnotationWriter.LDP_CONTEXT]) ==
                set(container['@context']))
            assert "annotations" in container["id"]
            assert set(["BasicContainer", "AnnotationCollection"]) == set(container["type"])
            assert 0 == container["total"]

            first_page = container["first"]
            assert "AnnotationPage" == first_page["type"]

            # The page doesn't have a context, since it's in the container.
            assert None == first_page.get('@context')

            # The patron doesn't have any annotations yet.
            assert 0 == container['total']

            # There's no timestamp since the container is empty.
            assert None == timestamp

            # Now, add an annotation.
            identifier = self._identifier()
            annotation, ignore = create(
                self._db, Annotation,
                patron=patron,
                identifier=identifier,
                motivation=Annotation.IDLING,
            )
            annotation.timestamp = utc_now()

            container, timestamp = AnnotationWriter.annotation_container_for(patron)

            # The context, type, and id stay the same.
            assert (set([AnnotationWriter.JSONLD_CONTEXT, AnnotationWriter.LDP_CONTEXT]) ==
                set(container['@context']))
            assert "annotations" in container["id"]
            assert identifier.identifier not in container["id"]
            assert set(["BasicContainer", "AnnotationCollection"]) == set(container["type"])

            # But now there is one item.
            assert 1 == container['total']

            first_page = container["first"]

            assert 1 == len(first_page['items'])

            # The item doesn't have a context, since it's in the container.
            first_item = first_page['items'][0]
            assert None == first_item.get('@context')

            # The timestamp is the annotation's timestamp.
            assert annotation.timestamp == timestamp

            # If the annotation is deleted, the container will be empty again.
            annotation.active = False

            container, timestamp = AnnotationWriter.annotation_container_for(patron)
            assert 0 == container['total']
            assert None == timestamp

    def test_annotation_container_for_with_identifier(self):
        patron = self._patron()
        identifier = self._identifier()

        with self.app.test_request_context("/"):
            container, timestamp = AnnotationWriter.annotation_container_for(patron, identifier)

            assert (set([AnnotationWriter.JSONLD_CONTEXT, AnnotationWriter.LDP_CONTEXT]) ==
                set(container['@context']))
            assert "annotations" in container["id"]
            assert identifier.identifier in container["id"]
            assert set(["BasicContainer", "AnnotationCollection"]) == set(container["type"])
            assert 0 == container["total"]

            first_page = container["first"]
            assert "AnnotationPage" == first_page["type"]

            # The page doesn't have a context, since it's in the container.
            assert None == first_page.get('@context')

            # The patron doesn't have any annotations yet.
            assert 0 == container['total']

            # There's no timestamp since the container is empty.
            assert None == timestamp

            # Now, add an annotation for this identifier, and one for a different identifier.
            annotation, ignore = create(
                self._db, Annotation,
                patron=patron,
                identifier=identifier,
                motivation=Annotation.IDLING,
            )
            annotation.timestamp = utc_now()

            other_annotation, ignore = create(
                self._db, Annotation,
                patron=patron,
                identifier=self._identifier(),
                motivation=Annotation.IDLING,
            )

            container, timestamp = AnnotationWriter.annotation_container_for(patron, identifier)

            # The context, type, and id stay the same.
            assert (set([AnnotationWriter.JSONLD_CONTEXT, AnnotationWriter.LDP_CONTEXT]) ==
                set(container['@context']))
            assert "annotations" in container["id"]
            assert identifier.identifier in container["id"]
            assert set(["BasicContainer", "AnnotationCollection"]) == set(container["type"])

            # But now there is one item.
            assert 1 == container['total']

            first_page = container["first"]

            assert 1 == len(first_page['items'])

            # The item doesn't have a context, since it's in the container.
            first_item = first_page['items'][0]
            assert None == first_item.get('@context')

            # The timestamp is the annotation's timestamp.
            assert annotation.timestamp == timestamp

            # If the annotation is deleted, the container will be empty again.
            annotation.active = False

            container, timestamp = AnnotationWriter.annotation_container_for(patron, identifier)
            assert 0 == container['total']
            assert None == timestamp

    def test_annotation_page_for(self):
        patron = self._patron()

        with self.app.test_request_context("/"):
            page = AnnotationWriter.annotation_page_for(patron)

            # The patron doesn't have any annotations, so the page is empty.
            assert AnnotationWriter.JSONLD_CONTEXT == page['@context']
            assert 'annotations' in page['id']
            assert 'AnnotationPage' == page['type']
            assert 0 == len(page['items'])

            # If we add an annotation, the page will have an item.
            identifier = self._identifier()
            annotation, ignore = create(
                self._db, Annotation,
                patron=patron,
                identifier=identifier,
                motivation=Annotation.IDLING,
            )

            page = AnnotationWriter.annotation_page_for(patron)

            assert 1 == len(page['items'])

            # But if the annotation is deleted, the page will be empty again.
            annotation.active = False

            page = AnnotationWriter.annotation_page_for(patron)

            assert 0 == len(page['items'])

    def test_annotation_page_for_with_identifier(self):
        patron = self._patron()
        identifier = self._identifier()

        with self.app.test_request_context("/"):
            page = AnnotationWriter.annotation_page_for(patron, identifier)

            # The patron doesn't have any annotations, so the page is empty.
            assert AnnotationWriter.JSONLD_CONTEXT == page['@context']
            assert 'annotations' in page['id']
            assert identifier.identifier in page['id']
            assert 'AnnotationPage' == page['type']
            assert 0 == len(page['items'])

            # If we add an annotation, the page will have an item.
            annotation, ignore = create(
                self._db, Annotation,
                patron=patron,
                identifier=identifier,
                motivation=Annotation.IDLING,
            )

            page = AnnotationWriter.annotation_page_for(patron, identifier)
            assert 1 == len(page['items'])

            # If a different identifier has an annotation, the page will still have one item.
            other_annotation, ignore = create(
                self._db, Annotation,
                patron=patron,
                identifier=self._identifier(),
                motivation=Annotation.IDLING,
            )

            page = AnnotationWriter.annotation_page_for(patron, identifier)
            assert 1 == len(page['items'])

            # But if the annotation is deleted, the page will be empty again.
            annotation.active = False

            page = AnnotationWriter.annotation_page_for(patron, identifier)
            assert 0 == len(page['items'])

    def test_detail_target(self):
        patron = self._patron()
        identifier = self._identifier()
        target = {
            "http://www.w3.org/ns/oa#hasSource": {
                "@id": identifier.urn
            },
            "http://www.w3.org/ns/oa#hasSelector": {
                "@type": "http://www.w3.org/ns/oa#FragmentSelector",
                "http://www.w3.org/1999/02/22-rdf-syntax-ns#value": "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)"
            }
        }

        annotation, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier,
            motivation=Annotation.IDLING,
            target=json.dumps(target),
        )

        with self.app.test_request_context("/"):
            detail = AnnotationWriter.detail(annotation)

            assert "annotations/%i" % annotation.id in detail["id"]
            assert "Annotation" == detail['type']
            assert Annotation.IDLING == detail['motivation']
            compacted_target = {
                "source": identifier.urn,
                "selector": {
                    "type": "FragmentSelector",
                    "value": "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)"
                }
            }
            assert compacted_target == detail["target"]

    def test_detail_body(self):
        patron = self._patron()
        identifier = self._identifier()
        body = {
            "@type": "http://www.w3.org/ns/oa#TextualBody",
            "http://www.w3.org/ns/oa#bodyValue": "A good description of the topic that bears further investigation",
            "http://www.w3.org/ns/oa#hasPurpose": {
                "@id": "http://www.w3.org/ns/oa#describing"
            }
        }

        annotation, ignore = create(
            self._db, Annotation,
            patron=patron,
            identifier=identifier,
            motivation=Annotation.IDLING,
            content=json.dumps(body),
        )

        with self.app.test_request_context("/"):
            detail = AnnotationWriter.detail(annotation)

            assert "annotations/%i" % annotation.id in detail["id"]
            assert "Annotation" == detail['type']
            assert Annotation.IDLING == detail['motivation']
            compacted_body = {
                "type": "TextualBody",
                "bodyValue": "A good description of the topic that bears further investigation",
                "purpose": "describing"
            }
            assert compacted_body == detail["body"]

class TestAnnotationParser(AnnotationTest):
    def setup_method(self):
        super(TestAnnotationParser, self).setup_method()
        self.pool = self._licensepool(None)
        self.identifier = self.pool.identifier
        self.patron = self._patron()

    def _sample_jsonld(self, motivation=Annotation.IDLING):
        data = dict()
        data["@context"] = [AnnotationWriter.JSONLD_CONTEXT,
                            {'ls': Annotation.LS_NAMESPACE}]
        data["type"] = "Annotation"
        motivation = motivation.replace(Annotation.LS_NAMESPACE, 'ls:')
        motivation = motivation.replace(Annotation.OA_NAMESPACE, 'oa:')
        data["motivation"] = motivation
        data["body"] = {
            "type": "TextualBody",
            "bodyValue": "A good description of the topic that bears further investigation",
            "purpose": "describing"
        }
        data["target"] = {
            "source": self.identifier.urn,
            "selector": {
                "type": "oa:FragmentSelector",
                "value": "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)"
            }
        }
        return data

    def test_parse_invalid_json(self):
        annotation = AnnotationParser.parse(self._db, "not json", self.patron)
        assert INVALID_ANNOTATION_FORMAT == annotation

    def test_invalid_identifier(self):
        # If the target source can't be parsed as a URN we send
        # INVALID_ANNOTATION_TARGET
        data = self._sample_jsonld()
        data['target']['source'] = 'not a URN'
        annotation = AnnotationParser.parse(
            self._db, json.dumps(data), self.patron
        )
        assert INVALID_ANNOTATION_TARGET == annotation

    def test_null_id(self):
        # A JSON-LD document can have its @id set to null -- it's the
        # same as if the @id wasn't present -- but the jsonld library
        # can't handle this, so we need to test it specially.
        self.pool.loan_to(self.patron)
        data = self._sample_jsonld()
        data['id'] = None
        annotation = AnnotationParser.parse(
            self._db, json.dumps(data), self.patron
        )
        assert isinstance(annotation, Annotation)

    def test_parse_expanded_jsonld(self):
        self.pool.loan_to(self.patron)

        data = dict()
        data['@type'] = ["http://www.w3.org/ns/oa#Annotation"]
        data["http://www.w3.org/ns/oa#motivatedBy"] = [{
            "@id": Annotation.IDLING
        }]
        data["http://www.w3.org/ns/oa#hasBody"] = [{
            "@type" : ["http://www.w3.org/ns/oa#TextualBody"],
            "http://www.w3.org/ns/oa#bodyValue": [{
                "@value": "A good description of the topic that bears further investigation"
            }],
            "http://www.w3.org/ns/oa#hasPurpose": [{
                "@id": "http://www.w3.org/ns/oa#describing"
            }]
        }]
        data["http://www.w3.org/ns/oa#hasTarget"] = [{
            "http://www.w3.org/ns/oa#hasSelector": [{
                "@type": ["http://www.w3.org/ns/oa#FragmentSelector"],
                "http://www.w3.org/1999/02/22-rdf-syntax-ns#value": [{
                    "@value": "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)"
                }]
            }],
            "http://www.w3.org/ns/oa#hasSource": [{
                "@id": self.identifier.urn
            }],
        }]

        data_json = json.dumps(data)

        annotation = AnnotationParser.parse(self._db, data_json, self.patron)
        assert self.patron.id == annotation.patron_id
        assert self.identifier.id == annotation.identifier_id
        assert Annotation.IDLING == annotation.motivation
        assert True == annotation.active
        assert json.dumps(data["http://www.w3.org/ns/oa#hasTarget"][0]) == annotation.target
        assert json.dumps(data["http://www.w3.org/ns/oa#hasBody"][0]) == annotation.content

    def test_parse_compacted_jsonld(self):
        self.pool.loan_to(self.patron)

        data = dict()
        data["@type"] = "http://www.w3.org/ns/oa#Annotation"
        data["http://www.w3.org/ns/oa#motivatedBy"] = {
            "@id": Annotation.IDLING
        }
        data["http://www.w3.org/ns/oa#hasBody"] = {
            "@type": "http://www.w3.org/ns/oa#TextualBody",
            "http://www.w3.org/ns/oa#bodyValue": "A good description of the topic that bears further investigation",
            "http://www.w3.org/ns/oa#hasPurpose": {
                "@id": "http://www.w3.org/ns/oa#describing"
            }
        }
        data["http://www.w3.org/ns/oa#hasTarget"] = {
            "http://www.w3.org/ns/oa#hasSource": {
                "@id": self.identifier.urn
            },
            "http://www.w3.org/ns/oa#hasSelector": {
                "@type": "http://www.w3.org/ns/oa#FragmentSelector",
                "http://www.w3.org/1999/02/22-rdf-syntax-ns#value": "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)"
            }
        }

        data_json = json.dumps(data)
        expanded = jsonld.expand(data)[0]

        annotation = AnnotationParser.parse(self._db, data_json, self.patron)
        assert self.patron.id == annotation.patron_id
        assert self.identifier.id == annotation.identifier_id
        assert Annotation.IDLING == annotation.motivation
        assert True == annotation.active
        assert json.dumps(expanded["http://www.w3.org/ns/oa#hasTarget"][0]) == annotation.target
        assert json.dumps(expanded["http://www.w3.org/ns/oa#hasBody"][0]) == annotation.content

    def test_parse_jsonld_with_context(self):
        self.pool.loan_to(self.patron)

        data = self._sample_jsonld()
        data_json = json.dumps(data)
        expanded = jsonld.expand(data)[0]

        annotation = AnnotationParser.parse(self._db, data_json, self.patron)

        assert self.patron.id == annotation.patron_id
        assert self.identifier.id == annotation.identifier_id
        assert Annotation.IDLING == annotation.motivation
        assert True == annotation.active
        assert json.dumps(expanded["http://www.w3.org/ns/oa#hasTarget"][0]) == annotation.target
        assert json.dumps(expanded["http://www.w3.org/ns/oa#hasBody"][0]) == annotation.content

    def test_parse_jsonld_with_bookmarking_motivation(self):
        """You can create multiple bookmarks in a single book."""
        self.pool.loan_to(self.patron)

        data = self._sample_jsonld(motivation=Annotation.BOOKMARKING)
        data_json = json.dumps(data)
        annotation = AnnotationParser.parse(self._db, data_json, self.patron)
        assert Annotation.BOOKMARKING == annotation.motivation

        # You can't create another bookmark at the exact same location --
        # you just get the same annotation again.
        annotation2 = AnnotationParser.parse(self._db, data_json, self.patron)
        assert annotation == annotation2

        # But unlike with IDLING, you _can_ create multiple bookmarks
        # for the same identifier, so long as the selector value
        # (ie. the location within the book) is different.
        data['target']['selector']['value'] = 'epubcfi(/3/4[chap01ref]!/4[body01]/15[para05]/3:10)'
        data_json = json.dumps(data)
        annotation3 = AnnotationParser.parse(self._db, data_json, self.patron)
        assert annotation3 != annotation
        assert 2 == len(self.patron.annotations)

    def test_parse_jsonld_with_invalid_motivation(self):
        self.pool.loan_to(self.patron)

        data = self._sample_jsonld()
        data["motivation"] = "not-a-valid-motivation"
        data_json = json.dumps(data)

        annotation = AnnotationParser.parse(self._db, data_json, self.patron)

        assert INVALID_ANNOTATION_MOTIVATION == annotation

    def test_parse_jsonld_with_no_loan(self):
        data = self._sample_jsonld()
        data_json = json.dumps(data)

        annotation = AnnotationParser.parse(self._db, data_json, self.patron)

        assert INVALID_ANNOTATION_TARGET == annotation

    def test_parse_jsonld_with_no_target(self):
        data = self._sample_jsonld()
        del data['target']
        data_json = json.dumps(data)

        annotation = AnnotationParser.parse(self._db, data_json, self.patron)

        assert INVALID_ANNOTATION_TARGET == annotation

    def test_parse_updates_existing_annotation(self):
        self.pool.loan_to(self.patron)

        original_annotation, ignore = create(
            self._db, Annotation,
            patron_id=self.patron.id,
            identifier_id=self.identifier.id,
            motivation=Annotation.IDLING,
        )
        original_annotation.active = False
        yesterday = utc_now() - datetime.timedelta(days=1)
        original_annotation.timestamp = yesterday

        data = self._sample_jsonld()
        data = json.dumps(data)

        annotation = AnnotationParser.parse(self._db, data, self.patron)

        assert original_annotation == annotation
        assert True == annotation.active
        assert annotation.timestamp > yesterday

    def test_parse_treats_duplicates_as_interchangeable(self):
        self.pool.loan_to(self.patron)

        # Due to an earlier race condition, two duplicate annotations
        # were put in the database.
        a1, ignore = create(
            self._db, Annotation,
            patron_id=self.patron.id,
            identifier_id=self.identifier.id,
            motivation=Annotation.IDLING,
        )

        a2, ignore = create(
            self._db, Annotation,
            patron_id=self.patron.id,
            identifier_id=self.identifier.id,
            motivation=Annotation.IDLING,
        )

        assert a1 != a2

        # Parsing the annotation again retrieves one or the other
        # of the annotations rather than crashing or creating a third
        # annotation.
        data = self._sample_jsonld()
        data = json.dumps(data)
        annotation = AnnotationParser.parse(self._db, data, self.patron)
        assert annotation in (a1, a2)

    def test_parse_jsonld_with_patron_opt_out(self):
        self.pool.loan_to(self.patron)
        data = self._sample_jsonld()
        data_json = json.dumps(data)

        self.patron.synchronize_annotations=False
        annotation = AnnotationParser.parse(
            self._db, data_json, self.patron
        )
        assert PATRON_NOT_OPTED_IN_TO_ANNOTATION_SYNC == annotation


from django.utils import translation

class TranslatedField(object):
    def __init__(self, en_field, es_field):
        self.en_field = en_field
        self.es_field = es_field

    def __get__(self, instance, owner):
        if translation.get_language() == 'es':
            return getattr(instance, self.es_field)
        else:
            return getattr(instance, self.en_field)


##############################################################################
#
# Copyright (c) 2019 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
TPC protocol state management.

The various states in which a storage instance can find itself during
two-phase commit are complicated. This package presents a set of
objects that encapsulate various possibilities. In this way we can
test independent states...independently, and the state transitions are
explicit.

"""
from __future__ import absolute_import
from __future__ import print_function

import logging

from transaction.interfaces import NoTransaction
from transaction._transaction import rm_key
from transaction import get as get_thread_local_transaction

from perfmetrics import statsd_client

from zope.interface import implementer

from ZODB.POSException import ReadOnlyError
from ZODB.POSException import StorageTransactionError

from ..interfaces import ITPCStateNotInTransaction
from ..interfaces import ITPCStateDatabaseAvailable
from ...adapters.connections import ClosedConnection
from ..._util import Lazy as BaseLazy
from ..._util import get_boolean_from_environ

from .temporary_storage import TemporaryStorage

logger = logging.getLogger(__name__)

_CLOSED_CONNECTION = ClosedConnection()

#: Set the ``RELSTORAGE_LOCK_EARLY`` environment variable if you
#: experience deadlocks or failures to commit (``tpc_finish``). This
#: will cause the commit lock to be taken as part of ``tpc_vote``
#: (similar to RelStorage 2.x) instead of deferring it until
#: ``tpc_finish``.
#:
#: If this is necessary, this is probably a bug in RelStorage; please report
#: it.
LOCK_EARLY = get_boolean_from_environ(
    'RELSTORAGE_LOCK_EARLY',
    False,
    logger=logger,
)

class _LazyResource(BaseLazy):

    # If not None, a callable ``(storage, resource, force)``
    # that aborts the *resource*, possibly forcefully (*force*).
    # The return value will be the new value in the object
    # instance.
    abort_function = None
    # If not None, a callable ``(storage, resource)`` to clean up
    # any use of the *resource* after success.
    release_function = None

    def _stored_value_for_name_in_inst(self, value, name, inst):
        # type: (Any, str, SharedTPCState) -> None
        if name == 'store_connection':
            # Try to do this first
            inst._used_resources.insert(0, self)
        else:
            inst._used_resources.append(self)

    def aborter(self, func):
        assert not isinstance(func, _LazyResource)
        self.abort_function = func
        return self

    def releaser(self, func):
        assert not isinstance(func, _LazyResource)
        self.release_function = func
        return self

    def cleaner(self, func):
        self.abort_function = self.release_function = func
        return self

class SharedTPCState(object):
    """
    Contains attributes marking resources that *might* be used during the commit
    process. If any of them are, then the `abort` method takes care of cleaning them up.

    Accessing a resource implicitly begins it, if needed.
    """

    # pylint:disable=method-hidden

    prepared_txn = None
    transaction = None
    not_in_transaction_state = None
    read_only = False # Or we wouldn't allocate this object.

    def __init__(self, initial_state, storage, transaction):
        self.initial_state = initial_state
        self._storage = storage
        self.transaction = transaction
        self._used_resources = []

    @_LazyResource
    def local_client(self):
        return self._storage._cache.local_client

    @_LazyResource
    def store_connection(self):
        conn = self._storage._store_connection_pool.borrow()
        # Report on the connection we will use.
        # https://github.com/zodb/relstorage/issues/460
        logger.info("Using store connection %s", conn)
        return conn

    @store_connection.aborter
    def store_connection(self, storage, store_connection, force):
        try:
            adapter = storage._adapter
            if store_connection:
                # It's possible that this connection/cursor was
                # already closed if an error happened (which would
                # release the locks). Don't try to re-open it.
                adapter.locker.release_commit_lock(store_connection.cursor)

                # Though, this might re-open it.
                adapter.txncontrol.abort(
                    store_connection,
                    self.prepared_txn)

            if force:
                store_connection.drop()
        finally:
            storage._store_connection_pool.replace(store_connection)
        return _CLOSED_CONNECTION

    @store_connection.releaser
    def store_connection(self, storage, store_connection):
        storage._store_connection_pool.replace(store_connection)
        return _CLOSED_CONNECTION

    @_LazyResource
    def load_connection(self):
        return self._storage._load_connection

    @load_connection.aborter
    def load_connection(self, _storage, load_connection, force):
        if force:
            load_connection.drop()
        else:
            load_connection.rollback_quietly()
        load_connection.exit_critical_phase()
        return _CLOSED_CONNECTION

    @load_connection.releaser
    def load_connection(self, _storage, load_connection):
        load_connection.rollback_quietly()
        load_connection.exit_critical_phase()
        return _CLOSED_CONNECTION

    @_LazyResource
    def blobhelper(self):
        blobhelper = self._storage.blobhelper
        blobhelper.begin()
        return blobhelper

    @blobhelper.aborter
    def blobhelper(self, _storage, blobhelper, _force):
        blobhelper.abort()

    @blobhelper.releaser
    def blobhelper(self, _storage, blobhelper):
        blobhelper.clear_temp()

    def has_blobs(self):
        # pylint:disable=no-member
        return (
            'blobhelper' in self.__dict__
            and self.blobhelper is not None
            and self.blobhelper.txn_has_blobs
        )

    @BaseLazy
    def cache(self):
        return self._storage._cache

    @BaseLazy
    def adapter(self):
        return self._storage._adapter

    @_LazyResource
    def temp_storage(self):
        return TemporaryStorage()

    @temp_storage.cleaner
    def temp_storage(self, _storage, temp_storage, _force=None):
        temp_storage.close()

    def has_temp_data(self):
        return 'temp_storage' in self.__dict__ and self.temp_storage

    @_LazyResource
    def _statsd_buf(self):
        return []

    @_statsd_buf.cleaner
    def _statds_buf(self, _storage, buf, _force=None):
        client = statsd_client()
        if client is not None and buf:
            client.sendbuf(buf)

    def stat_timing(self, stat, value, rate=1):
        """
        Record a timing value.

        For compatibility with the default settings of ``perfmetrics``,
        the stat name should end in ``.t``

        The *value* should be a floating point difference of seconds
        (eg, ``time.time() - time.time()``). This will be converted to an integer
        number of milliseconds (again for consistency with ``perfmetrics``).
        """
        client = statsd_client()
        if client is not None:
            # scale from float seconds to milliseconds
            value = int(value * 1000.0)
            client.timing(stat, value, rate, self._statsd_buf)

    def stat_count(self, stat, value, rate=1):
        client = statsd_client()
        if client is not None:
            client.incr(stat, value, rate, self._statsd_buf)

    def __cleanup(self, method_name, method_args):
        storage = self._storage
        resources = self._used_resources
        self._used_resources = () # No more opening resources.

        exceptions = []

        for resource in resources:
            assert resource.__name__ in vars(self)

            cleaner = getattr(resource, method_name)
            if not cleaner:
                setattr(self, resource.__name__, None)
                continue

            value = getattr(self, resource.__name__)
            new_value = None
            try:
                new_value = cleaner(self, storage, value, *method_args)
            except Exception as ex: # pylint:disable=broad-except
                exceptions.append(ex)
            setattr(self, resource.__name__, new_value)

        if exceptions: # pragma: no cover
            # This usually indicates a bug in RelStorage that should be fixed.
            raise Exception("Failed to close one or more resources: %s" % (exceptions,))

    def abort(self, force=False):
        self.__cleanup('abort_function', (force,))

    def release(self):
        self.__cleanup('release_function', ())

@implementer(ITPCStateDatabaseAvailable)
class AbstractTPCStateDatabaseAvailable(object):

    __slots__ = (
        'shared_state',
    )

    # - store
    # - restore/restoreBlob
    # - deleteObject
    # - undo

    # should raise ReadOnlyError if the storage is read only.

    # - tpc_vote should raise StorageTransactionError

    # Because entering tpc_begin wasn't allowed if the storage was
    # read only, this needs to happen in the "not in transaction"
    # state.

    def __init__(self, shared_state):
        self.shared_state = shared_state # type: SharedTPCState

    @property
    def transaction(self):
        return self.shared_state.transaction

    @property
    def initial_state(self):
        return self.shared_state.initial_state

    @property
    def store_connection(self):
        return self.shared_state.store_connection

    def __repr__(self):
        result = "<%s at 0x%x stored_count=%s %s" % (
            type(self).__name__,
            id(self),
            len(getattr(self, 'temp_storage', ()) or ()),
            self._tpc_state_transaction_data(),
        )

        extra = self._tpc_state_extra_repr_info()
        for k, v in extra.items():
            result += ' %s=%r' % (k, v)
        result += '>'
        return result

    def _tpc_state_extra_repr_info(self):
        return {}

    def _tpc_state_transaction_data(self):
        # Grovels around in the transaction object and tries to find interesting
        # things to include.

        # The ZODB Connection passes us an internal TransactionMetaData
        # object; the real transaction object stores a reference to that in its data,
        # keyed off the connection.
        # We may or may not be able to get the real transaction using transaction.get(),
        # depending on if we are using the global (thread local) transaction manager or not.
        try:
            global_tx = get_thread_local_transaction()
        except NoTransaction:
            # It's in explicit mode and we're not using it.
            return "<no global transaction> tx=%r" % (self.transaction,)

        tx_data = getattr(global_tx, '_data', None)
        if not tx_data:
            # No data stored on the transaction (or the implementation changed!)
            return "<no transaction data> tx=%r" % (self.transaction,)

        for v in tx_data.values():
            if v is self.transaction:
                # Yes, we found the metadata that ZODB uses, so we are
                # joined to this transaction.
                break
        else:
            return "<no transaction meta %r> tx=%r" % (tx_data, self.transaction,)

        resources = sorted(global_tx._resources, key=rm_key)
        return "transaction=%r resources=%r" % (global_tx, resources)

    def tpc_finish(self, storage, transaction, f=None, _time=None): # pylint:disable=unused-argument
        # For the sake of some ZODB tests, we need to implement this everywhere,
        # even if it's not actually usable, and the first thing it needs to
        # do is check the transaction.
        if transaction is not self.transaction:
            raise StorageTransactionError('tpc_finish called with wrong transaction')
        raise NotImplementedError("tpc_finish not allowed in this state.")

    def tpc_begin(self, _storage, transaction):
        # Ditto as for tpc_finish
        raise StorageTransactionError('tpc_begin not allowed in this state', type(self))

    def tpc_abort(self, transaction, force=False):
        if not force:
            if transaction is not self.transaction:
                return self

        self.shared_state.abort(force)
        return self.initial_state

    def no_longer_stale(self):
        return self

    def stale(self, e):
        return Stale(self, e)

    def close(self):
        if self.shared_state is not None:
            self.tpc_abort(None, True)
            self.shared_state = None

@implementer(ITPCStateNotInTransaction)
class NotInTransaction(object):
    # The default state, when the storage is not attached to a
    # transaction.

    __slots__ = (
        'last_committed_tid_int',
        'read_only',
        'begin_factory',
    )

    transaction = None

    def __init__(self, begin_factory, read_only, committed_tid_int=0):
        self.begin_factory = begin_factory
        self.read_only = read_only
        self.last_committed_tid_int = committed_tid_int

    def with_committed_tid_int(self, committed_tid_int):
        return NotInTransaction(
            self.begin_factory,
            self.read_only,
            committed_tid_int
        )

    def tpc_abort(self, *args, **kwargs): # pylint:disable=arguments-differ,unused-argument,signature-differs
        # Nothing to do
        return self

    def _no_transaction(self, *args, **kwargs):
        raise StorageTransactionError("No transaction in progress")

    tpc_finish = tpc_vote = _no_transaction
    checkCurrentSerialInTransaction = _no_transaction

    def store(self, *_args, **_kwargs):
        if self.read_only:
            raise ReadOnlyError()
        self._no_transaction()

    restore = deleteObject = undo = restoreBlob = store

    def tpc_begin(self, storage, transaction): # XXX: Signature needs to change.
        if self.read_only:
            raise ReadOnlyError()
        if transaction is self.transaction: # Also handles None.
            raise StorageTransactionError("Duplicate tpc_begin calls for same transaction.")
        state = SharedTPCState(self, storage, transaction)
        try:
            return self.begin_factory(state)
        except:
            state.abort()
            raise

    @property
    def initial_state(self):
        return self

    # This object appears to be false.
    def __bool__(self):
        return False
    __nonzero__ = __bool__

    def close(self):
        pass

@implementer(ITPCStateNotInTransaction)
class Stale(object):
    """
    An error that lets us know we are stale
    was encountered.

    Just about all accesses to this object result in
    re-raising that error.
    """

    transaction = None
    last_committed_tid_int = 0

    def __init__(self, previous_state, stale_error):
        self.previous_state = previous_state
        self.stale_error = stale_error

    def _stale(self, *args, **kwargs):
        raise self.stale_error

    store = restore = checkCurrentSerialInTransaction = _stale
    undo = deleteObject = restoreBlob = _stale
    tpc_begin = tpc_finish = tpc_vote = _stale

    def tpc_abort(self, *args, **kwargs):
        return self.previous_state.tpc_abort(*args, **kwargs)

    @property
    def initial_state(self):
        return self.previous_state.initial_state

    def no_longer_stale(self):
        return self.previous_state

    def stale(self, _e):
        return self

    def __bool__(self):
        return False
    __nonzero__ = __bool__


from .json_data_provider import *