from __future__ import absolute_import, unicode_literals

from datetime import date

from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TaggedItemBase

from wagtail.utils.pagination import paginate
from wagtail.wagtailadmin.edit_handlers import (
    FieldPanel, InlinePanel, MultiFieldPanel, PageChooserPanel)
from wagtail.wagtailcore.fields import RichTextField
from wagtail.wagtailcore.models import Orderable, Page
from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch import index

# ABSTRACT MODELS
# =============================

class AbstractLinkFields(models.Model):
    link_external = models.URLField("External link", blank=True)
    link_page = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        related_name='+',
        on_delete=models.CASCADE
    )
    link_document = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        related_name='+',
        on_delete=models.CASCADE
    )

    @property
    def link(self):
        if self.link_page:
            return self.link_page.url
        elif self.link_document:
            return self.link_document.url
        else:
            return self.link_external

    api_fields = ('link', )

    panels = [
        FieldPanel('link_external'),
        PageChooserPanel('link_page'),
        DocumentChooserPanel('link_document'),
    ]

    class Meta:
        abstract = True

class AbstractRelatedLink(AbstractLinkFields):
    title = models.CharField(max_length=255, help_text="Link title")

    api_fields = ('title', ) + AbstractLinkFields.api_fields

    panels = [
        FieldPanel('title'),
        MultiFieldPanel(AbstractLinkFields.panels, "Link"),
    ]

    class Meta:
        abstract = True

class AbstractCarouselItem(AbstractLinkFields):
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    embed_url = models.URLField("Embed URL", blank=True)
    caption = models.CharField(max_length=255, blank=True)

    api_fields = (
        'image',
        'embed_url',
        'caption',
    ) + AbstractLinkFields.api_fields

    panels = [
        ImageChooserPanel('image'),
        FieldPanel('embed_url'),
        FieldPanel('caption'),
        MultiFieldPanel(AbstractLinkFields.panels, "Link"),
    ]

    class Meta:
        abstract = True

class ContactFieldsMixin(models.Model):
    telephone = models.CharField(max_length=20, blank=True)
    email = models.EmailField(blank=True)
    address_1 = models.CharField(max_length=255, blank=True)
    address_2 = models.CharField(max_length=255, blank=True)
    city = models.CharField(max_length=255, blank=True)
    country = models.CharField(max_length=255, blank=True)
    post_code = models.CharField(max_length=10, blank=True)

    api_fields = (
        'telephone',
        'email',
        'address_1',
        'address_2',
        'city',
        'country',
        'post_code',
    )

    panels = [
        FieldPanel('telephone'),
        FieldPanel('email'),
        FieldPanel('address_1'),
        FieldPanel('address_2'),
        FieldPanel('city'),
        FieldPanel('country'),
        FieldPanel('post_code'),
    ]

    class Meta:
        abstract = True

# PAGE MODELS
# =============================

# Home page

class HomePage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    body = RichTextField(blank=True)

    api_fields = (
        'body',
        'carousel_items',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]

    class Meta:
        verbose_name = "homepage"

class HomePageCarouselItem(Orderable, AbstractCarouselItem):
    page = ParentalKey('HomePage', related_name='carousel_items', on_delete=models.CASCADE)

class HomePageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey('HomePage', related_name='related_links', on_delete=models.CASCADE)

HomePage.content_panels = Page.content_panels + [
    FieldPanel('body', classname="full"),

    InlinePanel('carousel_items', label="Carousel items"),
    InlinePanel('related_links', label="Related links"),
]

# Standard pages

class StandardPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    intro = RichTextField(blank=True)
    body = RichTextField(blank=True)
    feed_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'intro',
        'body',
        'feed_image',
        'carousel_items',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
    ]

class StandardPageCarouselItem(Orderable, AbstractCarouselItem):
    page = ParentalKey('StandardPage', related_name='carousel_items', on_delete=models.CASCADE)

class StandardPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey('StandardPage', related_name='related_links', on_delete=models.CASCADE)

StandardPage.content_panels = Page.content_panels + [
    FieldPanel('intro', classname="full"),
    InlinePanel('carousel_items', label="Carousel items"),
    FieldPanel('body', classname="full"),
    InlinePanel('related_links', label="Related links"),
]

StandardPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    ImageChooserPanel('feed_image'),
]

class StandardIndexPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    intro = RichTextField(blank=True)
    feed_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'intro',
        'feed_image',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
    ]

class StandardIndexPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey('StandardIndexPage', related_name='related_links', on_delete=models.CASCADE)

StandardIndexPage.content_panels = Page.content_panels + [
    FieldPanel('intro', classname="full"),
    InlinePanel('related_links', label="Related links"),
]

StandardIndexPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    ImageChooserPanel('feed_image'),
]

# Blog pages

class BlogEntryPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    body = RichTextField()
    tags = ClusterTaggableManager(through='BlogEntryPageTag', blank=True)
    date = models.DateField("Post date")
    feed_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'body',
        'tags',
        'date',
        'feed_image',
        'carousel_items',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]

    def get_blog_index(self):
        # Find closest ancestor which is a blog index
        return BlogIndexPage.ancestor_of(self).last()

class BlogEntryPageCarouselItem(Orderable, AbstractCarouselItem):
    page = ParentalKey('BlogEntryPage', related_name='carousel_items', on_delete=models.CASCADE)

class BlogEntryPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey('BlogEntryPage', related_name='related_links', on_delete=models.CASCADE)

class BlogEntryPageTag(TaggedItemBase):
    content_object = ParentalKey('BlogEntryPage', related_name='tagged_items', on_delete=models.CASCADE)

BlogEntryPage.content_panels = Page.content_panels + [
    FieldPanel('date'),
    FieldPanel('body', classname="full"),
    InlinePanel('carousel_items', label="Carousel items"),
    InlinePanel('related_links', label="Related links"),
]

BlogEntryPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    ImageChooserPanel('feed_image'),
    FieldPanel('tags'),
]

class BlogIndexPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    intro = RichTextField(blank=True)

    api_fields = (
        'intro',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
    ]

    def get_blog_entries(self):
        # Get list of live blog pages that are descendants of this page
        entries = BlogEntryPage.objects.descendant_of(self).live()

        # Order by most recent date first
        entries = entries.order_by('-date')

        return entries

    def get_context(self, request):
        # Get blog entries
        entries = self.get_blog_entries()

        # Filter by tag
        tag = request.GET.get('tag')
        if tag:
            entries = entries.filter(tags__name=tag)

        paginator, entries = paginate(request, entries, page_key='page', per_page=10)

        # Update template context
        context = super(BlogIndexPage, self).get_context(request)
        context['entries'] = entries
        return context

class BlogIndexPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey('BlogIndexPage', related_name='related_links', on_delete=models.CASCADE)

BlogIndexPage.content_panels = Page.content_panels + [
    FieldPanel('intro', classname="full"),
    InlinePanel('related_links', label="Related links"),
]

# Events pages

class EventPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    AUDIENCE_CHOICES = (
        ('public', "Public"),
        ('private', "Private"),
    )

    date_from = models.DateField("Start date")
    date_to = models.DateField(
        "End date",
        null=True,
        blank=True,
        help_text="Not required if event is on a single day"
    )
    time_from = models.TimeField("Start time", null=True, blank=True)
    time_to = models.TimeField("End time", null=True, blank=True)
    audience = models.CharField(max_length=255, choices=AUDIENCE_CHOICES)
    location = models.CharField(max_length=255)
    body = RichTextField(blank=True)
    cost = models.CharField(max_length=255)
    signup_link = models.URLField(blank=True)
    feed_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'date_from',
        'date_to',
        'time_from',
        'time_to',
        'audience',
        'location',
        'body',
        'cost',
        'signup_link',
        'feed_image',
        'carousel_items',
        'related_links',
        'speakers',
    )

    search_fields = Page.search_fields + [
        index.SearchField('get_audience_display'),
        index.SearchField('location'),
        index.SearchField('body'),
    ]

    def get_event_index(self):
        # Find closest ancestor which is an event index
        return EventIndexPage.objects.ancester_of(self).last()

class EventPageCarouselItem(Orderable, AbstractCarouselItem):
    page = ParentalKey('EventPage', related_name='carousel_items', on_delete=models.CASCADE)

class EventPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey('EventPage', related_name='related_links', on_delete=models.CASCADE)

class EventPageSpeaker(Orderable, AbstractLinkFields):
    page = ParentalKey('EventPage', related_name='speakers', on_delete=models.CASCADE)
    first_name = models.CharField("Name", max_length=255, blank=True)
    last_name = models.CharField("Surname", max_length=255, blank=True)
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'first_name',
        'last_name',
        'image',
    )

    panels = [
        FieldPanel('first_name'),
        FieldPanel('last_name'),
        ImageChooserPanel('image'),
        MultiFieldPanel(AbstractLinkFields.panels, "Link"),
    ]

EventPage.content_panels = Page.content_panels + [
    FieldPanel('date_from'),
    FieldPanel('date_to'),
    FieldPanel('time_from'),
    FieldPanel('time_to'),
    FieldPanel('location'),
    FieldPanel('audience'),
    FieldPanel('cost'),
    FieldPanel('signup_link'),
    InlinePanel('carousel_items', label="Carousel items"),
    FieldPanel('body', classname="full"),
    InlinePanel('speakers', label="Speakers"),
    InlinePanel('related_links', label="Related links"),
]

EventPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    ImageChooserPanel('feed_image'),
]

class EventIndexPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    intro = RichTextField(blank=True)

    api_fields = (
        'intro',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
    ]

    def get_events(self):
        # Get list of live event pages that are descendants of this page
        events = EventPage.objects.descendant_of(self).live()

        # Filter events list to get ones that are either
        # running now or start in the future
        events = events.filter(date_from__gte=date.today())

        # Order by date
        events = events.order_by('date_from')

        return events

class EventIndexPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey('EventIndexPage', related_name='related_links', on_delete=models.CASCADE)

EventIndexPage.content_panels = Page.content_panels + [
    FieldPanel('intro', classname="full"),
    InlinePanel('related_links', label="Related links"),
]

# Person page

class PersonPage(Page, ContactFieldsMixin):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    intro = RichTextField(blank=True)
    biography = RichTextField(blank=True)
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    feed_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'first_name',
        'last_name',
        'intro',
        'biography',
        'image',
        'feed_image',
        'related_links',
    ) + ContactFieldsMixin.api_fields

    search_fields = Page.search_fields + [
        index.SearchField('first_name'),
        index.SearchField('last_name'),
        index.SearchField('intro'),
        index.SearchField('biography'),
    ]

class PersonPageRelatedLink(Orderable, AbstractRelatedLink):
    page = ParentalKey('PersonPage', related_name='related_links', on_delete=models.CASCADE)

PersonPage.content_panels = Page.content_panels + [
    FieldPanel('first_name'),
    FieldPanel('last_name'),
    FieldPanel('intro', classname="full"),
    FieldPanel('biography', classname="full"),
    ImageChooserPanel('image'),
    MultiFieldPanel(ContactFieldsMixin.panels, "Contact"),
    InlinePanel('related_links', label="Related links"),
]

PersonPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    ImageChooserPanel('feed_image'),
]

# Contact page

class ContactPage(Page, ContactFieldsMixin):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    body = RichTextField(blank=True)
    feed_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'body',
        'feed_image',
    ) + ContactFieldsMixin.api_fields

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]

ContactPage.content_panels = Page.content_panels + [
    FieldPanel('body', classname="full"),
    MultiFieldPanel(ContactFieldsMixin.panels, "Contact"),
]

ContactPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    ImageChooserPanel('feed_image'),
]


"""Collection of tests for unified linear algebra functions."""

# global
import numpy as np
from hypothesis import given, strategies as st

# local
import ivy_tests.test_ivy.helpers as helpers
import ivy.functional.backends.numpy as ivy_np

# vector_to_skew_symmetric_matrix
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
)
def test_vector_to_skew_symmetric_matrix(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "vector_to_skew_symmetric_matrix",
        vector=np.random.uniform(size=(a, 3)).astype(input_dtype[0]),
    )

# matrix_power
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    n=st.integers(-10, 10),
)
def test_matrix_power(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    n,
):
    if fw == "torch" and input_dtype == "float16":
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "matrix_power",
        n=n,
        x=np.random.uniform(size=(a, a)).astype(input_dtype[0]),
    )

# matmul
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 2),
    as_variable=helpers.list_of_length(st.booleans(), 2),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=helpers.list_of_length(st.booleans(), 2),
    container=helpers.list_of_length(st.booleans(), 2),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
    seed=st.integers(0, 2**16 - 1),
)
def test_matmul(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
    seed,
):
    np.random.seed(seed)
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "matmul",
        rtol=5e-02,
        atol=5e-02,
        x1=np.random.uniform(size=(a, b)).astype(input_dtype[0]),
        x2=np.random.uniform(size=(b, c)).astype(input_dtype[1]),
    )

# det
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
)
def test_det(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "det",
        x=np.random.uniform(size=(b, a, a)).astype(input_dtype[0]),
    )

# eigh
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
)
def test_eigh(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "eigh",
        x=np.random.uniform(size=(b, a, a)).astype(input_dtype[0]),
    )

# eigvalsh
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
)
def test_eigvalsh(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "eigvalsh",
        x=np.random.uniform(size=(b, a, a)).astype(input_dtype[0]),
    )

# inv
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
)
def test_inv(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "inv",
        x=np.random.uniform(size=(b, a, a)).astype(input_dtype[0]),
    )

# matrix_transpose
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
)
def test_matrix_transpose(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "matrix_transpose",
        x=np.random.uniform(size=(a, b)).astype(input_dtype[0]),
    )

# outer
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 2),
    as_variable=helpers.list_of_length(st.booleans(), 2),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=helpers.list_of_length(st.booleans(), 2),
    container=helpers.list_of_length(st.booleans(), 2),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
)
def test_outer(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "outer",
        x1=np.random.uniform(size=a).astype(input_dtype[0]),
        x2=np.random.uniform(size=b).astype(input_dtype[1]),
    )

# slogdet
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
)
def test_slogdet(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "slogdet",
        x=np.random.uniform(size=(a, a)).astype(input_dtype[0]),
    )

# solve
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 2),
    as_variable=helpers.list_of_length(st.booleans(), 2),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=helpers.list_of_length(st.booleans(), 2),
    container=helpers.list_of_length(st.booleans(), 2),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
)
def test_solve(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "solve",
        x1=np.random.uniform(size=(a, a)).astype(input_dtype[0]),
        x2=np.random.uniform(size=(a, 1)).astype(input_dtype[1]),
    )

# svdvals
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
)
def test_svdvals(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "svdvals",
        x=np.random.uniform(size=(a, b)).astype(input_dtype[0]),
    )

# tensordot
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 2),
    as_variable=helpers.list_of_length(st.booleans(), 2),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=helpers.list_of_length(st.booleans(), 2),
    container=helpers.list_of_length(st.booleans(), 2),
    instance_method=st.booleans(),
    a=st.integers(1, 50) | st.tuples(st.lists(st.integers()), st.lists(st.integers())),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
    d=st.integers(1, 50),
)
def test_tensordot(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
    d,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "tensordot",
        axes=a,
        x1=np.random.uniform(size=(b, c)).astype(input_dtype[0]),
        x2=np.random.uniform(size=(c, d)).astype(input_dtype[1]),
    )

# trace
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
    offset=st.integers(-10, 10),
)
def test_trace(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
    offset,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "trace",
        offset=offset,
        x=np.random.uniform(size=(a, b, c)).astype(input_dtype[0]),
    )

# vecdot
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 2),
    as_variable=helpers.list_of_length(st.booleans(), 2),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=helpers.list_of_length(st.booleans(), 2),
    container=helpers.list_of_length(st.booleans(), 2),
    instance_method=st.booleans(),
    a=st.integers(-1, 50),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
)
def test_vecdot(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "vecdot",
        axes=a,
        x1=np.random.uniform(size=(b, c)).astype(input_dtype[0]),
        x2=np.random.uniform(size=(b, b)).astype(input_dtype[1]),
    )

# vector_norm
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    axis=st.integers(-10, 10) | st.tuples(st.lists(st.integers())),
    kd=st.booleans(),
    ord=st.integers() | st.floats(),
)
def test_vector_norm(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    axis,
    kd,
    ord,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "vector_norm",
        axis=axis,
        keepdims=kd,
        ord=ord,
        x=np.random.uniform(size=(a, b)).astype(input_dtype[0]),
    )

# pinv
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
    seed=st.integers(0, 2**4 - 1),
)
def test_pinv(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
    seed,
):
    if "float16" in input_dtype:
        return
    np.random.seed(seed)
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "pinv",
        rtol=5e-02,
        x=np.random.uniform(size=(a, b, c)).astype(input_dtype[0]),
    )

# qr
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
    mode=st.sampled_from(("reduced", "complete")),
)
def test_qr(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
    mode,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "qr",
        mode=mode,
        x=np.random.uniform(size=(a, b, c)).astype(input_dtype[0]),
    )

# svd
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
    fm=st.booleans(),
)
def test_svd(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
    fm,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "svd",
        full_matrices=fm,
        x=np.random.uniform(size=(a, b, c)).astype(input_dtype[0]),
    )

# matrix_norm
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
    kd=st.booleans(),
    ord=st.integers(1, 10)
    | st.floats(1, 10)
    | st.sampled_from(("fro", "nuc", "float('inf')", "-float('inf')")),
)
def test_matrix_norm(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
    kd,
    ord,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "matrix_norm",
        keepdims=kd,
        ord=ord,
        x=np.random.uniform(size=(a, b, c)).astype(input_dtype[0]),
    )

# matrix_rank
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    c=st.integers(1, 50),
)
def test_matrix_rank(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    c,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "matrix_rank",
        rtol=5e-02,
        x=np.random.uniform(size=(a, b, c)).astype(input_dtype[0]),
    )

# cholesky
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_float_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    upper=st.booleans(),
)
def test_cholesky(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    upper,
):
    if "float16" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "cholesky",
        upper=upper,
        x=np.random.uniform(size=(a, a)).astype(input_dtype[0]),
    )

# cross
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 2),
    as_variable=helpers.list_of_length(st.booleans(), 2),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=helpers.list_of_length(st.booleans(), 2),
    container=helpers.list_of_length(st.booleans(), 2),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    axis=st.integers(-1, 50),
)
def test_cross(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    axis,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "cross",
        axis=axis,
        x1=np.random.uniform(size=(a, b)).astype(input_dtype[0]),
        x2=np.random.uniform(size=(a, b)).astype(input_dtype[1]),
    )

# diagonal
@given(
    input_dtype=helpers.list_of_length(st.sampled_from(ivy_np.valid_numeric_dtypes), 1),
    as_variable=st.booleans(),
    with_out=st.booleans(),
    num_positional_args=st.integers(0, 1),
    native_array=st.booleans(),
    container=st.booleans(),
    instance_method=st.booleans(),
    a=st.integers(1, 50),
    b=st.integers(1, 50),
    offset=st.integers(-10, 50),
    axes=st.lists(st.integers(-2, 50), min_size=2, max_size=2, unique=True),
)
def test_diagonal(
    input_dtype,
    as_variable,
    with_out,
    num_positional_args,
    native_array,
    container,
    instance_method,
    fw,
    a,
    b,
    offset,
    axes,
):
    if "float16" or "int8" in input_dtype:
        return
    helpers.test_array_function(
        input_dtype,
        as_variable,
        with_out,
        num_positional_args,
        native_array,
        container,
        instance_method,
        fw,
        "diagonal",
        offset=offset,
        axis1=axes[0],
        axis2=axes[1],
        x=np.random.uniform(size=(a, b)).astype(input_dtype[0]),
    )


# Copyright 2014 National Research Foundation (South African Radio Astronomy Observatory)
# BSD license - see LICENSE for details
"""A high-level abstract interface to KATCP clients, sensors and requests."""

from __future__ import absolute_import, division, print_function
from future import standard_library
standard_library.install_aliases()  # noqa: E402

import abc
import collections
import logging
import sys

from builtins import object

import tornado

from future.utils import with_metaclass, PY2
from past.builtins import basestring
from tornado.concurrent import Future
from tornado.gen import Return, with_timeout

from katcp import Message, Sensor
from katcp.core import hashable_identity
from katcp.compat import ensure_native_str

logger = logging.getLogger(__name__)

class KATCPResourceError(Exception):
    """Error raised for resource-related errors"""

class KATCPResourceInactive(KATCPResourceError):
    """Raised when a request is made to an inactive resource"""

class KATCPSensorError(KATCPResourceError):
    """Raised if a problem occurred dealing with as KATCPSensor operation"""

class SensorResultTuple(collections.namedtuple(
        'SensorResultTuple',
        'object name python_identifier description type units reading')):
    """Per-sensor result of list_sensors() method

    Attributes
    ----------
    object : KATCPSensor instance
    name : str
        KATCP (i.e. unescaped) name of the sensor
    python_identifier : str
        Python-identifier name of the sensor.
    description : str
        KATCP description of the sensor
    type : str
        KATCP type of the sensor
    units : str
        KATCP units of the sensor
    reading : KATCPSensorReading instance
        Most recently received sensor reading
    """
    __slots__ = []  # Prevent dynamic attributes from being possible

def normalize_strategy_parameters(params):
    """Normalize strategy parameters to be a list of strings.

    Parameters
    ----------
    params : (space-delimited) string or sequence of strings/numbers Parameters
        expected by :class:`SampleStrategy` object, in various forms, where the first
        parameter is the name of the strategy.

    Returns
    -------
    params : tuple of strings
        Strategy parameters as a list of strings

    """
    def fixup_numbers(val):
        try:
            # See if it is a number
            return str(float(val))
        except ValueError:
            # ok, it is not a number we know of, perhaps a string
            return str(val)
    if isinstance(params, basestring):
        params = params.split(' ')
    # No number
    return tuple(fixup_numbers(p) for p in params)

def escape_name(name):
    """Escape sensor and request names to be valid Python identifiers."""
    return name.replace('.', '_').replace('-', '_')

class KATCPResource(with_metaclass(abc.ABCMeta, object)):

    """Base class to serve as the definition of the KATCPResource API.

    A class `C` implementing the KATCPResource API should register itself using
    KATCPResource.register(C) or subclass KATCPResource directly. A complication
    involved with subclassing is that all the abstract properties must be
    implemented as properties; normal instance attributes cannot be used.

    Attributes
    ----------
    Apart from the abstract properties described below

    TODO Describe how hierarchies are implemented. Also all other descriptions
    here so that the sphinx doc can be auto-generated from here.

    """

    def __init__(self):
        self._active = True

    @abc.abstractproperty
    def name(self):
        """Name of this KATCP resource."""

    @abc.abstractproperty
    def description(self):
        """Description of this KATCP resource."""

    @abc.abstractproperty
    def address(self):
        """Address of the underlying client/device.

        Type: tuple(host, port) or None, with host a string and port an integer.

        If this KATCPResource is not associated with a specific KATCP device
        (e.g. it is only a top-level container for a hierarchy of KATCP
        resources), the address should be None.

        """

    @abc.abstractproperty
    def is_connected(self):
        """Indicate whether the underlying client/device is connected or not."""

    @abc.abstractproperty
    def req(self):
        """Attribute root/container for all KATCP request wrappers.

        Each KATCP request that is exposed on a KATCP device should have a
        corresponding :class:`KATCPRequest` object so that calling

          `resource.req.request_name(arg1, arg2, ...)`

        sends a '?request-name arg1 arg2 ...' message to the KATCP device and
        waits for the associated inform-reply and reply messages.

        For a :class:`KATCPResource` object that exposes a hierarchical device
        it can choose to include lower-level request handlers here such that
        `resource.req.dev_request()` maps to `resource.dev.req.request()`.

        """

    @abc.abstractproperty
    def sensor(self):
        """Attribute root/container for all KATCP sensor wrappers.

        Each KATCP sensor that is exposed on a KATCP device should have a
        corresponding :class:`KATCPSensor` object so that

          `resource.sensor.sensor_name`

        corresponds to a sensor named e.g. 'sensor-name', where the object or
        attribute name is an escaped/Pythonised version of the original sensor
        name (see :func:`escape_name` for the escape mechanism). Hopefully the
        device is not crazy enough to have multiple sensors that map to the
        same Python identifier.

        A :class:`KATCPResource` object that exposes a hierarchical device can
        choose to include lower-level sensors here such that
        `resource.sensor.dev_sensorname` maps to
        `resource.dev.sensor.sensorname`.

        """

    @abc.abstractproperty
    def parent(self):
        """Parent KATCPResource object of this subordinate resource, or None."""

    @abc.abstractproperty
    def children(self):
        """AttrDict of subordinate KATCPResource objects keyed by their names."""

    @tornado.gen.coroutine
    def wait(self, sensor_name, condition_or_value, timeout=5):
        """Wait for a sensor in this resource to satisfy a condition.

        Parameters
        ----------
        sensor_name : string
            The name of the sensor to check
        condition_or_value : obj or callable, or seq of objs or callables
            If obj, sensor.value is compared with obj. If callable,
            condition_or_value(reading) is called, and must return True if its
            condition is satisfied. Since the reading is passed in, the value,
            status, timestamp or received_timestamp attributes can all be used
            in the check.
        timeout : float or None
            The timeout in seconds (None means wait forever)

        Returns
        -------
        This command returns a tornado Future that resolves with True when the
        sensor value satisfies the condition, or False if the condition is
        still not satisfied after a given timeout period.

        Raises
        ------
        :class:`KATCPSensorError`
            If the sensor does not have a strategy set, or if the named sensor
            is not present

        """
        sensor_name = escape_name(sensor_name)
        sensor = self.sensor[sensor_name]
        try:
            yield sensor.wait(condition_or_value, timeout)
        except tornado.gen.TimeoutError:
            raise tornado.gen.Return(False)
        else:
            raise tornado.gen.Return(True)

    @abc.abstractmethod
    def list_sensors(self, filter="", strategy=False, status="",
                     use_python_identifiers=True, tuple=False, refresh=False):
        """List sensors available on this resource matching certain criteria.

        Parameters
        ----------
        filter : string, optional
            Filter each returned sensor's name against this regexp if specified.
            To ease the dichotomy between Python identifier names and actual
            sensor names, the default is to search on Python identifier names
            rather than KATCP sensor names, unless `use_python_identifiers`
            below is set to False. Note that the sensors of subordinate
            KATCPResource instances may have inconsistent names and Python
            identifiers, better to always search on Python identifiers in this
            case.
        strategy : {False, True}, optional
            Only list sensors with a set strategy if True
        status : string, optional
            Filter each returned sensor's status against this regexp if given
        use_python_identifiers : {True, False}, optional
            Match on python identifiers even the the KATCP name is available.
        tuple : {True, False}, optional, Default: False
            Return backwards compatible tuple instead of SensorResultTuples
        refresh : {True, False}, optional, Default: False
            If set the sensor values will be refreshed with get_value before
            returning the results.

        Returns
        -------
        sensors : list of SensorResultTuples, or list of tuples
            List of matching sensors presented as named tuples. The `object`
            field is the :class:`KATCPSensor` object associated with the sensor.
            Note that the name of the object may not match `name` if it
            originates from a subordinate device.
        """

    @tornado.gen.coroutine
    def set_sampling_strategies(self, filter, strategy_and_params):
        """Set a sampling strategy for all sensors that match the specified filter.

        Parameters
        ----------
        filter : string
            The regular expression filter to use to select the sensors to which
            to apply the specified strategy.  Use "" to match all sensors. Is
            matched using :meth:`list_sensors`.
        strategy_and_params : seq of str or str
            As tuple contains (<strat_name>, [<strat_parm1>, ...]) where the strategy
            names and parameters are as defined by the KATCP spec. As str contains the
            same elements in space-separated form.
        **list_sensor_args : keyword arguments
            Passed to the :meth:`list_sensors` call as kwargs

        Returns
        -------
        sensors_strategies : tornado Future
           resolves with a dict with the Python identifier names of the sensors
           as keys and the value a tuple:

           (success, info) with

           success : bool
              True if setting succeeded for this sensor, else False
           info : tuple
               normalised sensor strategy and parameters as tuple if success == True
               else, sys.exc_info() tuple for the error that occurred.
        """
        sensors_strategies = {}
        sensor_results = yield self.list_sensors(filter)
        for sensor_reslt in sensor_results:
            norm_name = sensor_reslt.object.normalised_name
            try:
                sensor_strat = yield self.set_sampling_strategy(norm_name, strategy_and_params)
                sensors_strategies[norm_name] = sensor_strat[norm_name]
            except Exception:
                sensors_strategies[norm_name] = (
                    False, sys.exc_info())
        raise tornado.gen.Return(sensors_strategies)

    @tornado.gen.coroutine
    def set_sampling_strategy(self, sensor_name, strategy_and_params):
        """Set a sampling strategy for a specific sensor.

        Parameters
        ----------
        sensor_name : string
            The specific sensor.
        strategy_and_params : seq of str or str
            As tuple contains (<strat_name>, [<strat_parm1>, ...]) where the strategy
            names and parameters are as defined by the KATCP spec. As str contains the
            same elements in space-separated form.

        Returns
        -------
        sensors_strategies : tornado Future
           resolves with a dict with the Python identifier names of the sensors
           as keys and the value a tuple:

           (success, info) with

           success : bool
              True if setting succeeded for this sensor, else False
           info : tuple
               normalised sensor strategy and parameters as tuple if success == True
               else, sys.exc_info() tuple for the error that occurred.
        """
        sensors_strategies = {}
        try:
            sensor_obj = self.sensor.get(sensor_name)
            yield sensor_obj.set_sampling_strategy(strategy_and_params)
            sensors_strategies[sensor_obj.normalised_name] = (
                    True, sensor_obj.sampling_strategy)
        except Exception:
            sensors_strategies[sensor_obj.normalised_name] = (
                    False, sys.exc_info())
        raise tornado.gen.Return(sensors_strategies)

    def set_active(self, active):
        self._active = bool(active)
        for child in dict.values(self.children):
            child.set_active(active)

    def is_active(self):
        return self._active

class KATCPRequest(with_metaclass(abc.ABCMeta, object)):

    """Abstract Base class to serve as the definition of the KATCPRequest API.

    Wrapper around a specific KATCP request to a given KATCP device.  Each
    available KATCP request for a particular device has an associated
    :class:`KATCPRequest` object in the object hierarchy. This wrapper is mainly
    for interactive convenience. It provides the KATCP request help string as a
    docstring and pretty-prints the result of the request.

    """

    def __init__(self, request_description, is_active=lambda: True):
        """Initialize request with given description and network client

        Parameters
        ----------
        request_description : dict
           name : str
            KATCP name of the request
           description : str
            KATCP request description (as returned by ?help <name>)
           timeout_hint : float or None
            Request timeout suggested by device or None if not provided
        is_active : callable, optional
            Returns True if this request is active, else False

        """
        for required_description_key in ('name', 'description', 'timeout_hint'):
            if required_description_key not in request_description:
                raise ValueError(
                    'Required request_description key {!r} not present'
                    .format(required_description_key))
        self._request_description = dict(request_description)
        self.__doc__ = '\n'.join(('KATCP Documentation',
                                  '===================',
                                  self.description,
                                  'KATCPRequest Documentation',
                                  '==========================',
                                  self.__doc__ or ''))
        self._is_active = is_active

    @property
    def name(self):
        """Name of the KATCP request."""
        return self._request_description['name']

    @property
    def description(self):
        """Description of KATCP request as obtained from the ?help request."""
        return self._request_description['description']

    @property
    def timeout_hint(self):
        """Request timeout suggested by device or None if not provided"""
        return self._request_description['timeout_hint']

    def __call__(self, *args, **kwargs):
        """Execute the KATCP request described by this object.

        All positional arguments of this function are converted to KATCP string
        representations and passed on as space-separated parameters to the KATCP
        device.

        Keyword Arguments
        -----------------
        timeout : None or float, optional
            Timeout in seconds for the request. If None, use request timeout
            hint received from server or default for the :class:`KATCPResource`
            instance that contains the request if no hint is available.
        mid : None or int, optional
            Message identifier to use for the request message. If None, use
            either auto-incrementing value or no mid depending on the KATCP
            protocol version (mid's were only introduced with KATCP v5) and the
            default of the containing :class:`KATCPResource` instance.

        Returns
        -------
        reply : tornado future resolving with :class:`KATCPReply` object
            KATCP request reply wrapped in KATCPReply object

        Raises
        ------
        :class:`ResourceInactive` if the resource is inactive when the request is made.
        """
        if self.is_active():
            return self.issue_request(*args, **kwargs)
        else:
            raise KATCPResourceInactive(
                "Can't make ?{} request; resource is inactive".format(self.name))

    @abc.abstractmethod
    def issue_request(self, *args, **kwargs):
        """Signature as for __call__

        Do the request immediately without checking active state.
        """

    def is_active(self):
        """True if resource for this request is active"""
        return self._is_active()

class KATCPDummyRequest(KATCPRequest):
    """Dummy counterpart to KATCPRequest that always returns a successful reply"""
    def issue_request(self, *args, **kwargs):
        reply_msg = Message.reply('fake', 'ok')
        reply = KATCPReply(reply_msg, [])
        fut = Future()
        fut.set_result(reply)
        return fut

class KATCPSensorReading(collections.namedtuple(
        'KATCPSensorReading', 'received_timestamp timestamp istatus value')):

    """Sensor reading as a (received_timestamp, timestamp, istatus, value) tuple.

    Attributes
    ----------
    received_timestamp : float
       Time (in seconds since UTC epoch) at which the sensor value was received.
    timestamp : float
       Time (in seconds since UTC epoch) at which the sensor value was determined.
    istatus : int Sensor status constant
       Whether the value represents an error condition or not, as in class:`katcp.Sensor`
       The status is stored as an int, but output as a string, eg 'nominal'.
    value : object
        The value of the sensor (the type will be appropriate to the
        sensor's type).
    """

    __slots__ = []              # Prevent dynamic attributes

    @property
    def status(self):
        " Returns the string representation of sensor status, eg 'nominal'"
        try:
            return Sensor.STATUSES[int(self.istatus)]
        except TypeError:
            return 'unknown'

class KATCPSensorsManager(with_metaclass(abc.ABCMeta, object)):

    """Sensor management class used by KATCPSensor. Abstracts communications details.

    This class should arrange:

    1. A mechanism for setting sensor strategies
    2. A mechanism for polling a sensor value
    3. Keeping track of- and reapplying sensor strategies after reconnect, etc.
    4. Providing local time. This is doing to avoid direct calls to time.time, allowing
       accelerated time testing / simulation / dry-running
    """

    @abc.abstractmethod
    def time(self):
        """Returns the current time (in seconds since UTC epoch)"""

    @abc.abstractmethod
    def get_sampling_strategy(self, sensor_name):
        """Get the current sampling strategy for the named sensor

        Parameters
        ----------

        sensor_name : str
            Name of the sensor (normal or escaped form)

        Returns
        -------

        strategy : tornado Future that resolves with tuple of str
            contains (<strat_name>, [<strat_parm1>, ...]) where the strategy names and
            parameters are as defined by the KATCP spec
        """

    @abc.abstractmethod
    def set_sampling_strategy(self, sensor_name, strategy_and_parms):
        """Set the sampling strategy for the named sensor

        Parameters
        ----------

        sensor_name : str
            Name of the sensor
        strategy : seq of str or str
            As tuple contains (<strat_name>, [<strat_parm1>, ...]) where the strategy
            names and parameters are as defined by the KATCP spec. As str contains the
            same elements in space-separated form.

        Returns
        -------

        done : tornado Future that resolves when done or raises KATCPSensorError

        Notes
        -----

        It is recommended that implementations use :func:`normalize_strategy_parameters`
        to process the strategy_and_parms parameter, since it will deal with both string
        and list versions and makes sure that numbers are represented as strings in a
        consistent format.

        This method should arrange for the strategy to be set on the underlying network
        device or whatever other implementation is used. This strategy should also be
        automatically re-set if the device is reconnected, etc. If a strategy is set for a
        non-existing sensor, it should still cache the strategy and ensure that is applied
        whenever said sensor comes into existence. This allows an applications to pre-set
        strategies for sensors before synced / connected to a device.

        """

    @abc.abstractmethod
    def drop_sampling_strategy(self, sensor_name):
        """Drop the sampling strategy for the named sensor from the cache

        Calling :meth:`set_sampling_strategy` requires the sensor manager to
        memorise the requested strategy so that it can automatically be reapplied.
        If the client is no longer interested in the sensor, or knows the sensor
        may be removed from the server, then it can use this method to ensure the
        manager forgets about the strategy.  This method will not change the current
        strategy.  No error is raised if there is no strategy to drop.

        Parameters
        ----------

        sensor_name : str
            Name of the sensor (normal or escaped form)

        """

    @abc.abstractmethod
    def poll_sensor(self, sensor_name):
        """Poll sensor and arrange for sensor object to be updated

        Returns
        -------

        done_future : tornado Future
            Resolves when the poll is complete, or raises KATCPSensorError
        """
        # TODO NM 2015-02-03 Might want to add a timeout parameter here, and to all the
        # other code that calls this

    @abc.abstractmethod
    def reapply_sampling_strategies(self):
        """Reapply all sensor strategies using cached values

        Would typically be called when a connection is re-established. Should
        not raise errors when resetting strategies for sensors that no longer
        exist on the KATCP resource.
        """

class KATCPSensor(with_metaclass(abc.ABCMeta, object)):
    """Wrapper around a specific KATCP sensor on a given KATCP device.

    Each available KATCP sensor for a particular device has an associated
    :class:`KATCPSensor` object in the object hierarchy. This wrapper is mainly
    for interactive convenience. It provides the KATCP request help string as a
    docstring and registers listeners. Subclasses need to call the base class
    version of __init__().
    """

    def __init__(self, sensor_description, sensor_manager):
        """Subclasses must arrange to call this in their __init__().

        Parameters
        ----------
        sensor_description : dict
           Description of the KATCP sensor, with keys same as the parameters of
           :class:`katcp.Sensor`
        sensor_manager : :class:`KATCPSensorsManager` instance
           Manages sensor strategies, allows sensor polling, and provides time
        """
        self._manager = sensor_manager
        self.clear_listeners()
        self._reading = KATCPSensorReading(0, 0, Sensor.UNKNOWN, None)
        # We'll be abusing a katcp.Sensor object slightly to make use of its
        # parsing and formatting functionality
        self._sensor = Sensor(**sensor_description)
        self._name = self._sensor.name
        # Overide the katpc.Sensor's set method with ours
        self._sensor.set = self.set
        # Steal the the katcp.Sensor's set_formatted method. Since we overrode
        # its set() method with ours, calling set_formatted will result in this
        # KATCPSensor object's value being set.
        self.set_formatted = self._sensor.set_formatted

    @property
    def parent_name(self):
        """Name of the parent of this KATCPSensor"""
        return self._manager.resource_name

    @property
    def name(self):
        """Name of this KATCPSensor"""
        return self._name

    @property
    def normalised_name(self):
        """Normalised name of this KATCPSensor that can be used as a python identifier"""
        return escape_name(self._name)

    @property
    def reading(self):
        """Most recently received sensor reading as KATCPSensorReading instance"""
        return self._reading

    @property
    def value(self):
        return self._reading.value

    @property
    def status(self):
        return self._reading.status

    @property
    def sampling_strategy(self):
        """Current sampling strategy"""
        return self._manager.get_sampling_strategy(self.name)

    @property
    def description(self):
        return self._sensor.description

    @property
    def units(self):
        return self._sensor.units

    @property
    def type(self):
        return self._sensor.type

    def parse_value(self, s_value):
        """Parse a value from a string.

        Parameters
        ----------
        s_value : str
            A string value to attempt to convert to a value for
            the sensor.

        Returns
        -------
        value : object
            A value of a type appropriate to the sensor.

        """
        return self._sensor.parse_value(s_value)

    def set_strategy(self, strategy, params=None):
        """Set current sampling strategy for sensor.
        Add this footprint for backwards compatibility.

        Parameters
        ----------

        strategy : seq of str or str
            As tuple contains (<strat_name>, [<strat_parm1>, ...]) where the strategy
            names and parameters are as defined by the KATCP spec. As str contains the
            same elements in space-separated form.
        params : seq of str or str
            (<strat_name>, [<strat_parm1>, ...])

        Returns
        -------
        done : tornado Future that resolves when done or raises KATCPSensorError

        """
        if not params:
            param_args = []
        elif isinstance(params, basestring):
            param_args = [str(p) for p in params.split(' ')]
        else:
            if not isinstance(params, collections.Iterable):
                params = (params,)
            param_args = [str(p) for p in params]
        samp_strategy = " ".join([strategy] + param_args)
        return self._manager.set_sampling_strategy(self.name, samp_strategy)

    def set_sampling_strategy(self, strategy):
        """Set current sampling strategy for sensor

        Parameters
        ----------

        strategy : seq of str or str
            As tuple contains (<strat_name>, [<strat_parm1>, ...]) where the strategy
            names and parameters are as defined by the KATCP spec. As str contains the
            same elements in space-separated form.

        Returns
        -------
        done : tornado Future that resolves when done or raises KATCPSensorError

        """
        return self._manager.set_sampling_strategy(self.name, strategy)

    def drop_sampling_strategy(self):
        """Drop memorised sampling strategy for sensor, if any

        Calling this method ensures that the sensor manager does not attempt
        to reapply a sampling strategy.  It will not raise an error if no strategy
        has been set.  Use :meth:`set_sampling_strategy` to memorise a strategy again.
        """
        self._manager.drop_sampling_strategy(self.name)

    def register_listener(self, listener, reading=False):
        """Add a callback function that is called when sensor value is updated.

        The callback footprint is received_timestamp, timestamp, status, value.

        Parameters
        ----------
        listener : function
            Callback signature: if reading listener(katcp_sensor, reading) where
            `katcp_sensor` is this KATCPSensor instance `reading` is an instance of
            :class:`KATCPSensorReading`.

            Callback signature: default, if not reading listener(received_timestamp,
            timestamp, status, value)
        """
        listener_id = hashable_identity(listener)
        self._listeners[listener_id] = (listener, reading)
        logger.debug(
                    'Register listener for {}'
                    .format(self.name))

    def unregister_listener(self, listener):
        """Remove a listener callback added with register_listener().

        Parameters
        ----------
        listener : function
            Reference to the callback function that should be removed

        """
        listener_id = hashable_identity(listener)
        self._listeners.pop(listener_id, None)

    def is_listener(self, listener):
        listener_id = hashable_identity(listener)
        return listener_id in self._listeners

    def clear_listeners(self):
        """Clear any registered listeners to updates from this sensor."""
        self._listeners = {}

    def call_listeners(self, reading):
        logger.debug(
                    'Calling listeners {}'
                    .format(self.name))
        for listener, use_reading in list(self._listeners.values()):
            try:
                if use_reading:
                    listener(self, reading)
                else:
                    listener(reading.received_timestamp, reading.timestamp,
                             reading.status, reading.value)
            except Exception:
                logger.exception(
                    'Unhandled exception calling KATCPSensor callback {0!r}'
                    .format(listener))

    def set(self, timestamp, status, value):
        """Set sensor with a given received value, matches :meth:`katcp.Sensor.set`"""
        received_timestamp = self._manager.time()
        reading = KATCPSensorReading(received_timestamp, timestamp, status, value)
        self._reading = reading
        self.call_listeners(reading)

    def set_value(self, value, status=Sensor.NOMINAL, timestamp=None):
        """Set sensor value with optinal specification of status and timestamp"""
        if timestamp is None:
            timestamp = self._manager.time()
        self.set(timestamp, status, value)

    def set_formatted(self, raw_timestamp, raw_status, raw_value, major):
        """Set sensor using KATCP string formatted inputs

        Mirrors :meth:`katcp.Sensor.set_formatted`.

        This implementation is empty. Will, during instantiation, be overridden by the
        set_formatted() method of a katcp.Sensor object.
        """

    @tornado.gen.coroutine
    def get_reading(self):
        """Get a fresh sensor reading from the KATCP resource

        Returns
        -------
        reply : tornado Future resolving with  :class:`KATCPSensorReading` object

        Notes
        -----

        As a side-effect this will update the reading stored in this object, and result in
        registered listeners being called.
        """
        yield self._manager.poll_sensor(self._name)
        # By now the sensor manager should have set the reading
        raise Return(self._reading)

    @tornado.gen.coroutine
    def get_value(self):
        """Get a fresh sensor value from the KATCP resource

        Returns
        -------
        reply : tornado Future resolving with  :class:`KATCPSensorReading` object

        Notes
        -----

        As a side-effect this will update the reading stored in this object, and result in
        registered listeners being called.
        """
        yield self._manager.poll_sensor(self._name)
        # By now the sensor manager should have set the reading
        raise Return(self._reading.value)

    @tornado.gen.coroutine
    def get_status(self):
        """Get a fresh sensor status from the KATCP resource

        Returns
        -------
        reply : tornado Future resolving with  :class:`KATCPSensorReading` object

        Notes
        -----

        As a side-effect this will update the reading stored in this object, and result in
        registered listeners being called.
        """
        yield self._manager.poll_sensor(self._name)
        # By now the sensor manager should have set the reading
        raise Return(self._reading.status)

    def wait(self, condition_or_value, timeout=None):
        """Wait for the sensor to satisfy a condition.

        Parameters
        ----------
        condition_or_value : obj or callable, or seq of objs or callables
            If obj, sensor.value is compared with obj. If callable,
            condition_or_value(reading) is called, and must return True if its
            condition is satisfied. Since the reading is passed in, the value,
            status, timestamp or received_timestamp attributes can all be used
            in the check.
            TODO: Sequences of conditions (use SensorTransitionWaiter thingum?)
        timeout : float or None
            The timeout in seconds (None means wait forever)

        Returns
        -------
        This command returns a tornado Future that resolves with True when the
        sensor value satisfies the condition. It will never resolve with False;
        if a timeout is given a TimeoutError happens instead.

        Raises
        ------
        :class:`KATCPSensorError`
            If the sensor does not have a strategy set
        :class:`tornado.gen.TimeoutError`
            If the sensor condition still fails after a stated timeout period

        """
        if (isinstance(condition_or_value, collections.Sequence) and not
                isinstance(condition_or_value, basestring)):
            raise NotImplementedError(
                'Currently only single conditions are supported')
        condition_test = (condition_or_value if callable(condition_or_value)
                          else lambda s: s.value == condition_or_value)

        ioloop = tornado.ioloop.IOLoop.current()
        f = Future()
        if self.sampling_strategy == ('none', ):
            raise KATCPSensorError(
                'Cannot wait on a sensor that does not have a strategy set')

        def handle_update(sensor, reading):
            # This handler is called whenever a sensor update is received
            try:
                assert sensor is self
                if condition_test(reading):
                    self.unregister_listener(handle_update)
                    # Try and be idempotent if called multiple times after the
                    # condition is matched. This should not happen unless the
                    # sensor object is being updated in a thread outside of the
                    # ioloop.
                    if not f.done():
                        ioloop.add_callback(f.set_result, True)
            except Exception:
                f.set_exc_info(sys.exc_info())
                self.unregister_listener(handle_update)

        self.register_listener(handle_update, reading=True)
        # Handle case where sensor is already at the desired value
        ioloop.add_callback(handle_update, self, self._reading)

        if timeout:
            to = ioloop.time() + timeout
            timeout_f = with_timeout(to, f)
            # Make sure we stop listening if the wait times out to prevent a
            # buildup of listeners
            timeout_f.add_done_callback(
                lambda f: self.unregister_listener(handle_update))
            return timeout_f
        else:
            return f

_KATCPReplyTuple = collections.namedtuple('_KATCPReplyTuple', 'reply informs')

class KATCPReply(_KATCPReplyTuple):
    """Container for return messages of KATCP request (reply and informs).

    This is based on a named tuple with 'reply' and 'informs' fields so that
    the :class:`KATCPReply` object can still be unpacked into a normal tuple.

    Parameters
    ----------
    reply : :class:`katcp.Message` object
        Reply message returned by katcp request
    informs : list of :class:`katcp.Message` objects
        List of inform messages returned by KATCP request

    Attributes
    ----------
    messages: list of :class:`katcp.Message` objects
        List of all messages returned by KATCP request, reply first
    reply:  :class:`katcp.Message` object
        Reply message returned by KATCP request
    informs: list of :class:`katcp.Message` objects
        List of inform messages returned by KATCP request

    The instance evaluates to nonzero (i.e. truthy) if the request succeeded.

    """

    __slots__ = []  # Prevent dynamic attributes from being possible

    def __repr__(self):
        """String representation for pretty-printing in IPython."""
        return "\n".join(
            "%s%s %s"
            % (
                ensure_native_str(Message.TYPE_SYMBOLS[m.mtype]),
                m.name,
                " ".join([ensure_native_str(arg) for arg in m.arguments]),
            )
            for m in self.messages
        )

    def __str__(self):
        """String representation using KATCP wire format"""
        return '\n'.join(str(m) for m in self.messages)

    def __bool__(self):
        """True if request succeeded (i.e. first reply argument is 'ok')."""
        return self.messages[0].reply_ok()

    if PY2:
        __nonzero__ = __bool__

    @property
    def messages(self):
        """List of all messages returned by KATCP request, reply first."""
        return [self.reply] + self.informs

    @property
    def succeeded(self):
        """True if request succeeded (i.e. first reply argument is 'ok')."""
        return bool(self)


#!/usr/bin/env python
from os import path

import setuptools

def parse_requirements(filename):
    """ load requirements from a pip requirements file """
    lineiter = (line.strip() for line in open(filename))
    return [line for line in lineiter if line and not line.startswith("#")]

from metaappscriptsdk import info

here = path.abspath(path.dirname(__file__))

# Get the long description from the README file
with open(path.join(here, 'README.rst')) as f:
    long_description = f.read()

packages = [
    'metaappscriptsdk',
    'metaappscriptsdk.logger',
    'metaappscriptsdk.services',
    'metaappscriptsdk.schedule',
    'metaappscriptsdk.feed',
]

install_reqs = parse_requirements('requirements.txt')
reqs = install_reqs

setuptools.setup(
    name=info.__package_name__,
    version=info.__version__,

    description='Meta App Scripts SDK',
    long_description=long_description,

    url='https://github.com/rw-meta/meta-app-script-py-sdk',

    author='Artur Geraschenko',
    author_email='arturgspb@gmail.com',

    license='MIT',

    classifiers=[
        'Programming Language :: Python :: 3'
    ],
    install_requires=reqs,
    packages=packages,
    package_data={'': ['LICENSE']},
    package_dir={'metaappscriptsdk': 'metaappscriptsdk'},
    include_package_data=True,
)


"""
Module description:

"""

__version__ = '0.1'
__author__ = 'Vito Walter Anelli, Claudio Pomo'
__email__ = 'vitowalter.anelli@poliba.it, claudio.pomo@poliba.it'

import numpy as np
from ast import literal_eval as make_tuple
from tqdm import tqdm

from elliot.dataset.samplers import pointwise_pos_neg_sampler as pws
from elliot.recommender.neural.NeuMF.neural_matrix_factorization_model import NeuralMatrixFactorizationModel
from elliot.recommender.recommender_utils_mixin import RecMixin
from elliot.utils.write import store_recommendation

from elliot.recommender.base_recommender_model import BaseRecommenderModel
from elliot.recommender.base_recommender_model import init_charger

np.random.seed(42)

class NeuMF(RecMixin, BaseRecommenderModel):
    r"""
    Neural Collaborative Filtering

    For further details, please refer to the `paper <https://arxiv.org/abs/1708.05031>`_

    Args:
        mf_factors: Number of MF latent factors
        mlp_factors: Number of MLP latent factors
        mlp_hidden_size: List of units for each layer
        lr: Learning rate
        dropout: Dropout rate
        is_mf_train: Whether to train the MF embeddings
        is_mlp_train: Whether to train the MLP layers

    To include the recommendation model, add it to the config file adopting the following pattern:

    .. code:: yaml

      models:
        NeuMF:
          meta:
            save_recs: True
          epochs: 10
          mf_factors: 10
          mlp_factors: 10
          mlp_hidden_size: (64,32)
          lr: 0.001
          dropout: 0.0
          is_mf_train: True
          is_mlp_train: True
    """
    @init_charger
    def __init__(self, data, config, params, *args, **kwargs):

        self._random = np.random

        self._sampler = pws.Sampler(self._data.i_train_dict)

        self._params_list = [
            ("_learning_rate", "lr", "lr", 0.001, None, None),
            ("_mf_factors", "mf_factors", "mffactors", 10, int, None),
            ("_mlp_factors", "mlp_factors", "mlpfactors", 10, int, None),
            ("_mlp_hidden_size", "mlp_hidden_size", "mlpunits", "(64,32)", lambda x: list(make_tuple(str(x))), lambda x: self._batch_remove(str(x), " []").replace(",", "-")),
            ("_dropout", "dropout", "drop", 0, None, None),
            ("_is_mf_train", "is_mf_train", "mftrain", True, None, None),
            ("_is_mlp_train", "is_mlp_train", "mlptrain", True, None, None),
        ]
        self.autoset_params()

        if self._batch_size < 1:
            self._batch_size = self._data.transactions

        self._ratings = self._data.train_dict
        self._sp_i_train = self._data.sp_i_train
        self._i_items_set = list(range(self._num_items))

        self._model = NeuralMatrixFactorizationModel(self._num_users, self._num_items, self._mf_factors,
                                                     self._mlp_factors, self._mlp_hidden_size,
                                                     self._dropout, self._is_mf_train, self._is_mlp_train,
                                                     self._learning_rate)

    @property
    def name(self):
        return "NeuMF"\
               + "_e:" + str(self._epochs) \
               + "_bs:" + str(self._batch_size) \
               + f"_{self.get_params_shortcut()}"

    def train(self):
        if self._restore:
            return self.restore_weights()

        best_metric_value = 0

        for it in range(self._epochs):
            loss = 0
            steps = 0
            with tqdm(total=int(self._data.transactions // self._batch_size), disable=not self._verbose) as t:
                for batch in self._sampler.step(self._data.transactions, self._batch_size):
                    steps += 1
                    loss += self._model.train_step(batch)
                    t.set_postfix({'loss': f'{loss.numpy() / steps:.5f}'})
                    t.update()

            if not (it + 1) % self._validation_rate:
                recs = self.get_recommendations(self.evaluator.get_needed_recommendations())
                result_dict = self.evaluator.eval(recs)
                self._results.append(result_dict)

                print(f'Epoch {(it + 1)}/{self._epochs} loss {loss/steps:.5f}')

                if self._results[-1][self._validation_k]["val_results"][self._validation_metric] > best_metric_value:
                    print("******************************************")
                    best_metric_value = self._results[-1][self._validation_k]["val_results"][self._validation_metric]
                    if self._save_weights:
                        self._model.save_weights(self._saving_filepath)
                    if self._save_recs:
                        store_recommendation(recs, self._config.path_output_rec_result + f"{self.name}-it:{it + 1}.tsv")

    def get_recommendations(self, k: int = 100):
        predictions_top_k = {}
        for index, offset in enumerate(range(0, self._num_users, self._batch_size)):
            offset_stop = min(offset + self._batch_size, self._num_users)
            predictions = self._model.get_recs(
                (
                    np.repeat(np.array(list(range(offset, offset_stop)))[:, None], repeats=self._num_items, axis=1),
                    np.array([self._i_items_set for _ in range(offset, offset_stop)])
                 )
            )
            v, i = self._model.get_top_k(predictions, self.get_train_mask(offset, offset_stop), k=k)
            items_ratings_pair = [list(zip(map(self._data.private_items.get, u_list[0]), u_list[1]))
                                  for u_list in list(zip(i.numpy(), v.numpy()))]
            predictions_top_k.update(dict(zip(map(self._data.private_users.get,
                                                  range(offset, offset_stop)), items_ratings_pair)))
        return predictions_top_k


from django.apps import apps

def get_current_site(request):
    """
    Check if contrib.sites is installed and return either the current
    ``Site`` object or a ``RequestSite`` object based on the request.
    """
    # Imports are inside the function because its point is to avoid importing
    # the Site models when django.contrib.sites isn't installed.
    if apps.is_installed('django.contrib.sites'):
        from .models import Site
        return Site.objects.get_current(request)
    else:
        from .requests import RequestSite
        return RequestSite(request)


class BuySellStock:
  # @param prices, a list of stock prices
  # @return index of buy and sell price
  def choiceStocks(self, prices):
    n = len(prices)
    if n == 0:
      return None, None
    if n == 1:
      return 0, 0
    maxPrice = prices[n - 1]
    mpIndex = n - 1
    maxProfit = 0
    for price in range(n):
      currPrice = prices[n - price - 1]
      if currPrice > maxPrice:
        maxPrice = currPrice
        mpIndex = n - price - 1
      currProfit = maxPrice - currPrice
      if currProfit > maxProfit:
        maxProfit = currProfit
        bpIndex = n - price - 1
    return bpIndex, mpIndex

# Driver code to test the program
run = BuySellStock()
print(run.choiceStocks([5,6,7,8,10,3,8,7,11,1,2,11]))


from typing import Union, List, Optional

from pyspark.sql.types import StructType, StructField, StringType, ArrayType, DataType

# This file is auto-generated by generate_schema so do not edit it manually
# noinspection PyPep8Naming
class SubstanceNucleicAcid_SugarSchema:
    """
    Nucleic acids are defined by three distinct elements: the base, sugar and
    linkage. Individual substance/moiety IDs will be created for each of these
    elements. The nucleotide sequence will be always entered in the 5’-3’
    direction.
    """

    # noinspection PyDefaultArgument
    @staticmethod
    def get_schema(
        max_nesting_depth: Optional[int] = 6,
        nesting_depth: int = 0,
        nesting_list: List[str] = [],
        max_recursion_limit: Optional[int] = 2,
        include_extension: Optional[bool] = False,
        extension_fields: Optional[List[str]] = None,
        extension_depth: int = 0,
        max_extension_depth: Optional[int] = 2,
        include_modifierExtension: Optional[bool] = False,
        use_date_for: Optional[List[str]] = None,
        parent_path: Optional[str] = "",
    ) -> Union[StructType, DataType]:
        """
        Nucleic acids are defined by three distinct elements: the base, sugar and
        linkage. Individual substance/moiety IDs will be created for each of these
        elements. The nucleotide sequence will be always entered in the 5’-3’
        direction.

        id: Unique id for the element within a resource (for internal references). This
            may be any string value that does not contain spaces.

        extension: May be used to represent additional information that is not part of the basic
            definition of the element. To make the use of extensions safe and manageable,
            there is a strict set of governance  applied to the definition and use of
            extensions. Though any implementer can define an extension, there is a set of
            requirements that SHALL be met as part of the definition of the extension.

        modifierExtension: May be used to represent additional information that is not part of the basic
            definition of the element and that modifies the understanding of the element
            in which it is contained and/or the understanding of the containing element's
            descendants. Usually modifier elements provide negation or qualification. To
            make the use of extensions safe and manageable, there is a strict set of
            governance applied to the definition and use of extensions. Though any
            implementer can define an extension, there is a set of requirements that SHALL
            be met as part of the definition of the extension. Applications processing a
            resource are required to check for modifier extensions.

            Modifier extensions SHALL NOT change the meaning of any elements on Resource
            or DomainResource (including cannot change the meaning of modifierExtension
            itself).

        identifier: The Substance ID of the sugar or sugar-like component that make up the
            nucleotide.

        name: The name of the sugar or sugar-like component that make up the nucleotide.

        residueSite: The residues that contain a given sugar will be captured. The order of given
            residues will be captured in the 5‘-3‘direction consistent with the base
            sequences listed above.

        """
        if extension_fields is None:
            extension_fields = [
                "valueBoolean",
                "valueCode",
                "valueDate",
                "valueDateTime",
                "valueDecimal",
                "valueId",
                "valueInteger",
                "valuePositiveInt",
                "valueString",
                "valueTime",
                "valueUnsignedInt",
                "valueUri",
                "valueUrl",
                "valueReference",
                "valueCodeableConcept",
                "valueAddress",
            ]
        from spark_fhir_schemas.r4.complex_types.extension import ExtensionSchema
        from spark_fhir_schemas.r4.complex_types.identifier import IdentifierSchema

        if (
            max_recursion_limit
            and nesting_list.count("SubstanceNucleicAcid_Sugar") >= max_recursion_limit
        ) or (max_nesting_depth and nesting_depth >= max_nesting_depth):
            return StructType([StructField("id", StringType(), True)])
        # add my name to recursion list for later
        my_nesting_list: List[str] = nesting_list + ["SubstanceNucleicAcid_Sugar"]
        my_parent_path = (
            parent_path + ".substancenucleicacid_sugar"
            if parent_path
            else "substancenucleicacid_sugar"
        )
        schema = StructType(
            [
                # Unique id for the element within a resource (for internal references). This
                # may be any string value that does not contain spaces.
                StructField("id", StringType(), True),
                # May be used to represent additional information that is not part of the basic
                # definition of the element. To make the use of extensions safe and manageable,
                # there is a strict set of governance  applied to the definition and use of
                # extensions. Though any implementer can define an extension, there is a set of
                # requirements that SHALL be met as part of the definition of the extension.
                StructField(
                    "extension",
                    ArrayType(
                        ExtensionSchema.get_schema(
                            max_nesting_depth=max_nesting_depth,
                            nesting_depth=nesting_depth + 1,
                            nesting_list=my_nesting_list,
                            max_recursion_limit=max_recursion_limit,
                            include_extension=include_extension,
                            extension_fields=extension_fields,
                            extension_depth=extension_depth,
                            max_extension_depth=max_extension_depth,
                            include_modifierExtension=include_modifierExtension,
                            use_date_for=use_date_for,
                            parent_path=my_parent_path,
                        )
                    ),
                    True,
                ),
                # May be used to represent additional information that is not part of the basic
                # definition of the element and that modifies the understanding of the element
                # in which it is contained and/or the understanding of the containing element's
                # descendants. Usually modifier elements provide negation or qualification. To
                # make the use of extensions safe and manageable, there is a strict set of
                # governance applied to the definition and use of extensions. Though any
                # implementer can define an extension, there is a set of requirements that SHALL
                # be met as part of the definition of the extension. Applications processing a
                # resource are required to check for modifier extensions.
                #
                # Modifier extensions SHALL NOT change the meaning of any elements on Resource
                # or DomainResource (including cannot change the meaning of modifierExtension
                # itself).
                StructField(
                    "modifierExtension",
                    ArrayType(
                        ExtensionSchema.get_schema(
                            max_nesting_depth=max_nesting_depth,
                            nesting_depth=nesting_depth + 1,
                            nesting_list=my_nesting_list,
                            max_recursion_limit=max_recursion_limit,
                            include_extension=include_extension,
                            extension_fields=extension_fields,
                            extension_depth=extension_depth,
                            max_extension_depth=max_extension_depth,
                            include_modifierExtension=include_modifierExtension,
                            use_date_for=use_date_for,
                            parent_path=my_parent_path,
                        )
                    ),
                    True,
                ),
                # The Substance ID of the sugar or sugar-like component that make up the
                # nucleotide.
                StructField(
                    "identifier",
                    IdentifierSchema.get_schema(
                        max_nesting_depth=max_nesting_depth,
                        nesting_depth=nesting_depth + 1,
                        nesting_list=my_nesting_list,
                        max_recursion_limit=max_recursion_limit,
                        include_extension=include_extension,
                        extension_fields=extension_fields,
                        extension_depth=extension_depth + 1,
                        max_extension_depth=max_extension_depth,
                        include_modifierExtension=include_modifierExtension,
                        use_date_for=use_date_for,
                        parent_path=my_parent_path,
                    ),
                    True,
                ),
                # The name of the sugar or sugar-like component that make up the nucleotide.
                StructField("name", StringType(), True),
                # The residues that contain a given sugar will be captured. The order of given
                # residues will be captured in the 5‘-3‘direction consistent with the base
                # sequences listed above.
                StructField("residueSite", StringType(), True),
            ]
        )
        if not include_extension:
            schema.fields = [
                c
                if c.name != "extension"
                else StructField("extension", StringType(), True)
                for c in schema.fields
            ]

        if not include_modifierExtension:
            schema.fields = [
                c
                if c.name != "modifierExtension"
                else StructField("modifierExtension", StringType(), True)
                for c in schema.fields
            ]

        return schema


m = h = mu = 0
while True:
    print(25*'-')
    print('   CADASTRE UMA PESSOA')
    print(25*'-')
    i = int(input('Idade: '))
    if i > 17:
        m+=1
    while True:
        s = input('Sexo: [M/F] ').strip().upper()[0]
        if s in 'MF':
            break
    print(25*'-')
    if s == 'M':
        h+=1
    if s == 'F' and i < 21:
        mu+=1
    while True:
        q = input('Quer continuar? [S/N] ').strip().upper()[0]
        if q in 'SN':
            break
    if q == 'N':
        break
print(f'====== FIM DO PROGRAMA ======\nTotal de pessoas com mais de 18 anos: {m}\nAo todo temos {h} homens cadastrados.\nE temos {mu} mulheres com menos de 20 anos.')