from django.db import models
from django.contrib.gis.db import models
from django.db.models import Count, Max, F
from django.utils.translation import gettext_lazy as _
from djmoney.models.fields import MoneyField
from djmoney.money import Money
from easy_thumbnails.fields import ThumbnailerImageField
from apps.accounts.models import CustomerUser, COUNTRIES
from apps.destinations.fields import DaysCommaField
from apps.destinations.utils import TEMPLATE_DESCRIPTION
from filer.fields.image import FilerImageField
from django.utils import timezone
from django.core.exceptions import ValidationError
import datetime
from filer.fields.file import FilerFileField
from apps.destinations.formatChecker import ContentTypeRestrictedFileField
import os

TEMPLATE_DESCRIPTION = _("""
Who we are

Please enter your text here...

Mission

Please enter your text here...

EXIT / RETURN
Enter the exit here...

EXIT TIME
Enter the time of exit here...

ARRIVAL TIME
Enter the time of arrival here...

NR. OF TOUR FOR RESERVATIONS
Enter the tour number here...

TRANSFER FROM
Enter the transfer here...

""")

class Categorie(models.Model):
    name = models.CharField(
        _('Name'),
        max_length=50,
    )

    alias = models.CharField(
        _('Alias'),
        null=True,
        blank=True,
        max_length=20,
    )

    short_description= models.CharField(
        _('Short Description'),
        max_length=50,
    )

    image =  models.ImageField(
        _('Image'),
        upload_to="gallery/categorie/",
    )

    status = models.BooleanField(
        _('Status'),
        default=True
    )

    class Meta:
        verbose_name_plural = _('Categories')
        verbose_name = _('Category')

    def __str__(self):
        return f'{self.name}'

class Destination(models.Model):
    categorie = models.ManyToManyField(
        Categorie,
        verbose_name=_("Categories"),
        blank=True,
    )

    user = models.ForeignKey(
        CustomerUser,
        on_delete=models.CASCADE,
        verbose_name=_("User"),
    )

    name = models.CharField(
        _('Name'),
        max_length=200,
        blank=True,
        null=True,
    )

    short_description = models.TextField(
        _('Short description'),
        blank=True,
        null=True,
    )

    description = models.TextField(
        _('Description'),
        blank=True,
        null=True,
        default=TEMPLATE_DESCRIPTION,
    )

    departure_date = models.DateField(
        _('Departure Date'),
        blank=True,
        null=True,
    )
    arrival_date = models.DateField(
        _('Arrival Date'),
        blank=True,
        null=True,
    )

    departure_time = models.TimeField(
        _('Departure Time'),
        blank = True,
        null = True,
    )

    arrival_time = models.TimeField(
        _('Arrival Time'),
        blank = True,
        null = True,
    )

    number_of_reservations = models.IntegerField(
        _('Number of Reservations'),
        blank = True,
        null = True,
    )

    transfer_from = models.TextField(
        _('Transfer from'),
        max_length = 120,
        blank = True,
        null = True,
    )

    tour_include = models.TextField(
        _('Tour include'),
        blank = True,
    )

    tour_not_include = models.TextField(
        _('Tour not include'),
        blank = True,
    )

    is_published = models.BooleanField(
        _('is published?'),
        default=False,
        editable=False,
    )

    is_deleted = models.BooleanField(
        _('is deleted?'),
        default=False,
        editable=False,
    )

    has_wifi = models.BooleanField(
        _("Destination has Wifi?"),
        default=False,
    )

    has_breakfast_incluided = models.BooleanField(
        _("Destination has Breakfast included?"),
        default=False,
    )

    has_gym = models.BooleanField(
        _("Destination has Gym?"),
        default=False,
    )

    has_air_conditioning = models.BooleanField(
        _("Destination has Air conditioning?"),
        default=False,
    )

    has_restaurant = models.BooleanField(
        _("Destination has Restaurant?"),
        default=False,
    )

    has_bar = models.BooleanField(
        _("Destination has Bar?"),
        default=False,
    )

    has_housekeeping = models.BooleanField(
        _("Destination has Housekeeping service?"),
        default=False,
    )

    has_room_service = models.BooleanField(
        _("Destination has Room service?"),
        default=False,
    )

    has_business_services = models.BooleanField(
        _("Destination has Business services?"),
        default=False,
    )

    has_hob_tub = models.BooleanField(
        _("Destination has Hob Tub?"),
        default=False,
    )

    has_front_desk = models.BooleanField(
        _("Destination has 25/7 Front Desk?"),
        default=False,
    )

    has_laundry = models.BooleanField(
        _("Destination has Laundry service?"),
        default=False,
    )

    social_network = models.BooleanField(
        _('Social Network'),
        default=False,
        help_text = _("If you check this option it will show the social networks of the destination"),
    )

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = _("Destination")
        verbose_name_plural = _("Destinations")
        ordering = ('user',)
        unique_together = ('user', 'name',)

    def __str__(self):
        return f'{self.name}'

    def get_sku(self):
        return f"{self.details.inventario.sku}".upper()

    get_sku.short_description = _('SKU')

    @property
    def first_pic(self):
        pic = ''
        try:
            pic = Photo.objects.filter(destination=self).first()
            pic = pic.image.url
        except:
            pic = '/static/img/travel.png'
        return pic

    @staticmethod
    def count_categorie(alias):
        if alias == 'all':
            return Destination.objects.filter(is_published=True).count()
        else:
            return Destination.objects.filter(is_published=True, categorie__alias=alias).count()

    @property
    def photos(self):
        try:
            list_photos = Photo.objects.filter(destination=self)
        except BaseException:
            return None
        return list_photos

    @property
    def videos(self):
        try:
            list_videos = Video.objects.filter(destination=self)
        except BaseException:
            return None
        return list_videos

    @property
    def published_date(self):
        try:
            destination_details = GeneralDetail.objects.get(destination_detail__destination=self.pk)
            list_dates = {
                'date_from': destination_details.date_on_sale_from,
                'date_to': destination_details.date_on_sale_to,
            }
        except BaseException:
            return None
        return list_dates

    @property
    def mapa(self):
        mapa = TabData.objects\
        .filter(option_tab__name="Map")\
        .get(tour_data__destination=self.pk)
        return mapa

    @property
    def itinerario(self):
        itine = TabData.objects\
        .filter(option_tab__name="Itinerary")\
        .get(tour_data__destination=self.pk)
        return itine

    @property
    def itinerario2(self):
        it2 =   Itinerary.objects.get(destination=self.pk)
        return it2

    @property
    def social(self):
        sn = SocialNetwork.objects.get(destination=self.pk)
        return sn

    @property
    def customer(self):
        user = CustomerUser.objects.get(destination=self.pk)
        return user

    @property
    def list_prices(self):
        try:
            destination_details = GeneralDetail.objects.get(destination_detail__destination=self.pk)
            list_prices = {
                'regular_price': destination_details.regular_price,
                'sale_price': destination_details.sale_price,
            }
        except BaseException:
            return None
        return list_prices

class DestinationMap(models.Model):
    destination = models.OneToOneField(
        Destination,
        related_name='map',
        on_delete=models.CASCADE,
        verbose_name=_('Map Destination'),
    )
    description_map = models.CharField(_('Description Map'),
        max_length = 50
    )
    map_destinie = models.PointField(_('Destination Map'),
        help_text=_("To generate the map for your location"))

    class Meta:
        verbose_name = _('Destination Map')
        verbose_name_plural = _('Destination Maps')

    def __unicode__(self):
        return self.map_destinie

    def __str__(self):
        return f'{self.destination} {self.description_map}'

class Photo(models.Model):
    destination = models.ForeignKey(
        Destination,
        related_name='gallery',
        on_delete=models.CASCADE,
        verbose_name=_('Destination'),
    )

    name = models.CharField(
        _('Name'),
        max_length=50,
        blank=True,
        null=True,
    )

    sort = models.IntegerField(
        _('Sort'),
        blank=True,
        null=True,
    )

    description = models.TextField(
        _('Description'),
        blank=True,
        null=True,
        default=_("No comment"),
    )

    thumbnail = ThumbnailerImageField(
        _('Thumbnail'),
        upload_to="gallery/thumbnail/",
        blank=True,
        null=True,
    )

    image = models.ImageField(
        _('Image'),
        upload_to="gallery/image/",
        blank=True,
        null=True,
    )

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('sort', 'name')
        verbose_name = _('Photo')
        verbose_name_plural = _('Photos')

    def __str__(self):
        return f"{self.name}"

class Video(models.Model):
    destination = models.ForeignKey(
        Destination,
        related_name='media',
        on_delete=models.CASCADE,
        verbose_name=_('Destination'),
    )

    name = models.CharField(
        _('Name'),
        max_length=50,
        blank=True,
        null=True,
    )

    sort = models.IntegerField(
        _('Sort'),
        blank=True,
        null=True,
    )

    description = models.TextField(
        _('Description'),
        blank=True,
        null=True,
        default=_("No comment"),
    )

    thumbnail_preview = ThumbnailerImageField(
        _('Thumbnail'),
        blank=True,
        null=True,
        upload_to="gallery/thumbnail/",
    )

    video = models.FileField(
        _("Video"),
        blank=True,
        null=True,
        upload_to="gallery/video/",
        # validators=[valid_extension]
    )

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.name}"

    class Meta:
        ordering = ('sort', 'name')
        verbose_name = _('Video')
        verbose_name_plural = _('Videos')

class DestinationRating(models.Model):
    destination = models.ForeignKey(
        Destination,
        on_delete=models.CASCADE,
        verbose_name=_('Destination Rating'),
    )

    class Meta:
        verbose_name = _('Destination Rating')
        verbose_name_plural = _('Destinations Rating')

    def __str__(self):
        return f"{self.destination}"

class Badge(models.Model):
    name = models.CharField(
        _('Name'),
        max_length=50,
    )

    description = models.TextField(
        _('Description'),
    )

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.name}"

    class Meta:
        ordering = ('created_at',)
        verbose_name_plural = _('Badge')
        verbose_name = _('Badges')

class TourData(models.Model):
    destination = models.OneToOneField(
        Destination,
        related_name='tour',
        on_delete=models.CASCADE,
        verbose_name=_('Destination'),
    )

    badge = models.CharField(
        _('Badge'),
        max_length=150,
        blank=True,
        null=True,
        choices=(
            ('1', _('Last minute')),
            ('2', _('Deal of the Month')),
            ('3', _('FamTrip')),
            ('4', _('Congresses')),
            ('5', _('Events')),
            ('6', _('Hotel')),
            ('7', _('Mobility')),
            ('8', _('Leisure +')),
        ),
    )

    def __str__(self):
        return f'{self.destination}'

    class Meta:
        verbose_name = _('Tour')
        verbose_name_plural = _('Tours')

class Itinerary(models.Model):
    """
    Modelo para itinerario de destinos
    """
    destination = models.ForeignKey(
        Destination,
        on_delete = models.CASCADE,
        verbose_name = _("Itinerary")
    )

    short_description = models.CharField(
        _('Short Description'),
        max_length=50,

    )

    detail_itinerary = models.TextField(
        _('Detail Itinerary'),
    )

    def __str__(self):
        return f'{self.destination}-{self.short_description}'

    class Meta:
        verbose_name = _('Itinerary')
        verbose_name_plural = _('Itineraries')

class TabData(models.Model):
    tour_data = models.ForeignKey(
        TourData,
        related_name='tab',
        on_delete=models.CASCADE,
        verbose_name=_('Tour'),
    )

    option_tab = models.ForeignKey(
        'OptionTabData',
        related_name='option_tab',
        on_delete=models.CASCADE,
        verbose_name=_('Option tab'),
    )

    content = models.TextField(
        _('Content'),
        blank=True,
        null=True,
    )

    def __str__(self):
        return f'{self.option_tab}'

    class Meta:
        ordering = ('option_tab',)
        verbose_name_plural = _('Tabs')
        verbose_name = _('Tab')

class OptionTabData(models.Model):
    name = models.CharField(
        _('Name'),
        max_length=150,
    )

    description = models.TextField(
        _('Description'),
        blank=True,
        null=True,
    )

    template = models.TextField(
        _('Template'),
        blank=True,
        null=True,
    )

    created_at = models.DateTimeField(auto_now_add=True)

    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.name}'

    class Meta:
        ordering = ('name',)
        verbose_name_plural = _('Tab options')
        verbose_name = _('Tab option')

class HeaderSection(models.Model):
    destination = models.OneToOneField(
        Destination,
        related_name='header',
        on_delete=models.CASCADE,
        verbose_name=_('Destination'),
    )

    display_mode = models.CharField(
        _('Display mode'),
        max_length=15,
        default='hide',
        choices=(
            ('hide', _('Default')),
            ('banner', _('Image')),
            ('slider', _('Slider')),
            ('from_list', _('From list'))
        ),
    )

    subtitle = models.CharField(
        _('Subtitle'),
        max_length=100,
        blank=True,
        null=True,
    )

    image = models.ImageField(
        _('Image'),
        upload_to='header/images',
        blank=True,
        null=True,
    )

    parallax = models.BooleanField(
        _('Parallax'),
        default=True,
    )

    image_repeat = models.CharField(
        _('Image repeat'),
        max_length=150,
        blank=True,
        null=True,
        choices=(
            ('repeat', _('Repeat')),
            ('no-repeat', _('No repeat')),
            ('repeat-x', _('Repeat horizontally')),
            ('repeat-y', _('Repeat vertically'))
        ),
    )

    mask = models.CharField(
        _('Mask'),
        max_length=120,
        blank=True,
        null=True,
        choices=(
            ('default', _('Default')),
        ),
    )

    class Meta:
        verbose_name_plural = _('Header sections')
        verbose_name = _('Header section')

    def __str__(self):
        return f"{self.subtitle}"

class DestinationDetail(models.Model):
    destination = models.OneToOneField(
        Destination,
        related_name='details',
        on_delete=models.CASCADE,
        verbose_name=_('Destination'),
    )

    var = models.BooleanField(
        _('Variable tour'),
        default=False,
    )

    virtual = models.BooleanField(
        _('Virtual'),
        default=False,
    )

    descargable = models.BooleanField(
        _('Downloadable'),
        default=False,
    )

    class Meta:
        verbose_name_plural = _('Destination details')
        verbose_name = _('Destination detail')

    def __str__(self):
        return f"{self.destination}-details"

class GeneralDetail(models.Model):
    destination_detail = models.OneToOneField(
        DestinationDetail,
        related_name='general',
        on_delete=models.CASCADE,
        verbose_name=_('Destination detail'),
    )

    regular_price = MoneyField(
        _('Normal price'),
        max_digits=19,
        decimal_places=2,
        default_currency='USD',
        default=Money(0, 'USD'),
    )

    sale_price = MoneyField(
        _('Reduced price'),
        max_digits=19,
        decimal_places=2,
        default_currency='USD',
        default=Money(0, 'USD'),
    )

    date_on_sale_from = models.DateField(
        _('Initial discount date'),
        blank=True,
        null=True,
    )

    date_on_sale_to = models.DateField(
        _('Final discount date'),
        blank=True,
        null=True,
    )

    status_imp = models.CharField(
        _('Tax status'),
        max_length=50,
        default='imponible',
        choices=(
            ('imponible', _('Impossible')),
            ('envio', _('Shipping only')),
            ('ninguno', _('None'))
        ),
    )

    class_imp = models.CharField(
        _('Tax type'),
        max_length=50,
        default='estandar',
        choices=(
            ('estandar', _('Standard')),
            ('reduced', _('Reduced Rate')),
            ('zero', _('Zero Rate'))
        ),
    )

    class Meta:
        verbose_name_plural = _('General details')
        verbose_name = _('General detail')

    def __str__(self):
        return f"{self.destination_detail}-general"

class InventarioDetail(models.Model):
    destination_detail = models.OneToOneField(
        DestinationDetail,
        related_name='inventario',
        on_delete=models.CASCADE,
        verbose_name=_('Destination detail'),
    )

    sku = models.CharField(
        _('SKU'),
        max_length=60,
    )

    manager = models.BooleanField(
        _('Inventory management?'),
        default=False,
        help_text=_('Activate inventory management for each product'),
    )

    quantity = models.IntegerField(
        _('Inventory quantity'),
        blank=True,
        null=True,
        default=0,
    )

    reserva = models.CharField(
        _('Allow reservations?'),
        max_length=50,
        default='no',
        choices=(
            ('no', _('Do not allow')),
            ('notify', _('Allow, but the customer will be warned')),
            ('yes', _('Allow'))
        ),
    )

    umb_exist = models.IntegerField(
        _('Low stock threshold'),
        blank=True,
        null=True,
        default=0,
    )

    sold_individually = models.BooleanField(
        _('Sold individually'),
        default=False,
        help_text=_(
            'Activate this to allow only one of these '
            'items to be purchased in each order'),
        )

    class Meta:
        verbose_name_plural = _('Inventory details')
        verbose_name = _('Inventory detail')

    def __str__(self):
        return f"{self.destination_detail}-inventario"

class BookingDetail(models.Model):
    destination_detail = models.ForeignKey(
        DestinationDetail,
        related_name='booking',
        on_delete=models.CASCADE,
        verbose_name=_("Destination detail"),
    )

    start_date = models.DateField(
        _('Start date'),
        blank=True,
        null=True,
    )

    end_date = models.DateField(
        _('End date'),
        blank=True,
        null=True,
    )

    days = DaysCommaField(
        default=[],
        null=True,
        verbose_name=_('Days'),
    )

    number_ticket = models.IntegerField(
        _('Number of tickets per tour'),
        blank=True,
        null=True,
    )

    special_price = MoneyField(
        _('Special price'),
        max_digits=19,
        decimal_places=2,
        default_currency='USD',
        default=Money(0, 'USD'),
        blank=True,
        null=True,
    )

    mode = models.CharField(
        _('Mode'),
        max_length=50,
        default='default',
        choices=(
            ('default', _('Week days')),
            ('exact-dates', _('Exact dates'))
        ),
    )

    is_active = models.CharField(
        _('Is active'),
        max_length=3,
        default='0',
        choices=(
            ('0', _('No')),
            ('1', _('Yes'))
        ),
    )

    class Meta:
        ordering = ('start_date', 'end_date')
        verbose_name_plural = _('Booking details')
        verbose_name = _('Booking detail')

    def __str__(self):
        return f"{self.destination_detail}-booking-{self.pk}"

class SearchLanding(models.Model):
    names = models.CharField(
        _('Names'),
        null=True,
        blank=True,
        max_length=40
    )

    country = models.CharField(
        _('Countries'),
        null=True,
        blank=True,
        max_length=30
    )

    email = models.EmailField(
        _('Email'),
        null=True,
        blank=True,
        max_length=50
    )

    whatsapp = models.CharField(
        _('Whatsapp'),
        null=True,
        blank=True,
        max_length=30
    )

    class Meta:
        ordering = ('email', 'country')
        verbose_name_plural = _('Search Landing')
        verbose_name = _('Search Landing')

    def __str__(self):
        return f"{self.email}-{self.country}"

class Booking(models.Model):
    destination = models.ForeignKey(
        Destination,
        verbose_name = _("Destination"),
        on_delete=False,
    )

    firts_name = models.CharField(
        _('Firts Name'),
        null=False,
        blank=False,
        max_length = 50
    )

    last_name = models.CharField(
        _('Last Name'),
        null=False,
        blank=False,
        max_length = 50
    )

    dni = models.CharField(
        _('Identity number'),
        null=True,
        blank=True,
        max_length = 50
    )

    cellphone = models.CharField(
        _('Cellphone'),
        null=False,
        blank=False,
        max_length = 50
    )

    mail = models.EmailField(
        _('Email'),
        null=True,
        blank=True,
        max_length = 100,
    )

    number_travel = models.CharField(
        _('Number of people travelling'),
        null=True,
        blank=True,
        max_length= 2,
    )

    name_booking = models.CharField(
        _('Booking'),
        null=True,
        blank= True,
        max_length = 50,
    )

    comment = models.TextField(
        _('Comment'),
        blank=True,
        null=True,
    )

    seen_status = models.BooleanField(
        _('seen'),
        default=False,
    )

    process_status = models.BooleanField(
        _('status'),
        default=False,
        help_text=_('Means if receive following or not.'),
    )

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = _("Booking's")
        verbose_name = _('Booking')

    def __str__(self):
        return f"{self.firts_name}-{self.last_name}"

class BookingStats(models.Model):
    booking = models.ForeignKey(
        Booking,
        verbose_name=_("Booking"),
        on_delete=False,
    )

    user = models.ForeignKey(
        CustomerUser,
        on_delete=False,
        verbose_name=_("Users"),
    )

    date = models.DateTimeField(_("Datetime"),auto_now=True)

    class Meta:
        verbose_name_plural = _("Booking Stats")
        verbose_name = _("Booking Stast")

    def __str__(self):
        return f"{self.booking}-{self.user}-{self.date}"

class SocialNetwork(models.Model):
    destination = models.ForeignKey(Destination,
        verbose_name = _("Destination"), on_delete=models.CASCADE)
    social_network = models.BooleanField(
        _('Social Network'),
        default=False,
        help_text = _("Check for use the network from user"),
    )
    facebook = models.CharField(_("Facebook"), max_length=100, null=True, blank=True)
    instagram = models.CharField(_("Instagram"), max_length=100, null=True, blank=True)
    twitter = models.CharField(_("Twitter"), max_length=100, null=True, blank=True)
    linkedin = models.CharField(_("Linkedin"), max_length=100, null=True, blank=True)
    pinterest = models.URLField(_("Pinterest"), max_length=100, null=True, blank=True)
    website = models.URLField(_("Website"), max_length=100, null=True, blank=True)

    class Meta:
        verbose_name_plural = _("Social Networks")
        verbose_name = _("Social Network")

    def __str__(self):
        return f"{self.destination}"

class DestinationVisitor(models.Model):
    destination = models.ForeignKey(Destination,
        verbose_name = _("Destination"), related_name='visitor',
        on_delete = models.CASCADE)
    ip_address = models.CharField(_("IP Address"), max_length = 100)
    dma_code = models.IntegerField(_("Dma Code"), blank = True, null = True)
    country_code = models.CharField(_("Country Code"), max_length = 5)
    country_name = models.CharField(_("Country Name"), max_length = 100)
    date_time = models.DateTimeField(_("Date/time"), auto_now_add = True)

    class Meta:
        verbose_name = _('Destination Visitor')
        verbose_name_plural = _('Destination Visitors')

    def __str__(self):
        return f"{self.destination}-{self.country_code}-{self.country_code}"

class Advertising(models.Model):

    POSITION = (
        (1, _('Up')),
        (2, _('Middle')),
        (2, _('Down')),
    )

    name = models.CharField(_('Name'), max_length=30, blank=True)
    company = models.CharField(_('Company'), max_length=30, blank=True)
    imagen_desktop = FilerImageField(related_name='desktop',
                                    null=True,
                                    blank=True,
                                    on_delete=False,
                                    verbose_name=_("Desktop Image"),
                                    help_text=_('Image to be displayed in desktop resolutions'))
    imagen_mobile = FilerImageField(related_name='mobile',
                                    null=True,
                                    blank=True,
                                    on_delete=False,
                                    verbose_name=_("Mobile Image"),
                                    help_text=_('Image to be displayed in mobile resolution'))
    position = models.IntegerField(_('Position'), choices=POSITION, default=1)
    from_date = models.DateField(_('Start of advertising'),
                             default=datetime.date.today)
    to_date = models.DateField(_('End of advertising'), default=datetime.date.today)
    status = models.BooleanField(_('Active'), default=False, help_text=(
        _('Indicate Advertising Status')))
    url = models.URLField(_('URL'), max_length=200, blank=False)
    created_on = models.DateTimeField(_('Created'), auto_now_add=True)

    class Meta:
        db_table='Advertising'
        verbose_name_plural = _('Ads')
        verbose_name = _('Advertising')

    def __str__(self):
        return self.name

# A private directmessage on the dashboard
class MessageDashboard(models.Model):
    subject = models.CharField(
        _('Subject'), blank=False, null=False, max_length=1000)
    content = models.TextField(_('Content'))
    sender = models.ForeignKey(
        CustomerUser, related_name='dash_sender', verbose_name=_("Sender"), on_delete=models.CASCADE)
    # the variable recipient will be active when we'll work that the group
    recipient = models.ForeignKey(CustomerUser, related_name='dash_recipient', verbose_name=_("Recipient"), on_delete=models.CASCADE)
    sent_at = models.DateTimeField(_("sent at"), null=True, blank=True)
    read_at = models.DateTimeField(_("read at"), null=True, blank=True)

    class Meta:
        verbose_name_plural = _('Messages Dashboard')
        verbose_name = _('Message Dashboard')

    @property
    def unread(self):
        """returns whether the message was read or not"""
        if self.read_at is not None:
            return False
        return True

    def __str__(self):
        return self.content

    def save(self, **kwargs):
        if self.sender == self.recipient:
            raise ValidationError(_("You can't send messages to yourself"))

        if not self.id:
            self.sent_at = timezone.now()
        super(MessageDashboard, self).save(**kwargs)

# Petitions
class Request(models.Model):
    MANAGER = 'MGR'
    AGENCY = 'AGC'
    TYPES = (
            (MANAGER, 'Manager'),
            (AGENCY, 'Agency'),
            )
    PENDING = 'PEND'
    APPROVED = 'APPR'
    REJECTED = 'REJE'
    STATUS = (
            (PENDING, 'Pending'),
            (APPROVED, 'Approved'),
            (REJECTED, 'Rejected'),
            )
    user = models.ForeignKey(
            CustomerUser,
            on_delete=models.CASCADE,
            verbose_name=_("User"),
    )

    country = models.CharField(
        'Countries',
        choices=COUNTRIES,
        max_length=3
    )

    type = models.CharField(
            max_length=3,
            default=AGENCY,
            choices=TYPES)

    status = models.CharField(
            default=PENDING,
            max_length=4,
            choices=STATUS)
    created_at = models.DateTimeField(default=timezone.now)

    class Meta:
        unique_together = ('user', 'country')

    def __str__(self):
        return '%s %s' % (self.country, self.get_type_display())

class BookingQuestion(models.Model):
    question_text = models.CharField(_("Question text"), max_length=200)
    pub_date = models.DateTimeField(_("date published"))

    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

    def __str__(self):
            return self.question_text

    class Meta:
        verbose_name = _("Booking Question")
        verbose_name_plural = _("Booking Questions")

class BookingChoice(models.Model):
    question = models.ForeignKey(BookingQuestion, verbose_name=_("Question"), on_delete=models.CASCADE)
    choice_text = models.CharField(_("Choice text"), max_length=200)
    votes = models.IntegerField(_("Votes"), default=0)

    def __str__(self):
        return self.choice_text

    class Meta:
        verbose_name = _("Booking Choice")
        verbose_name_plural = _("Booking Choices")

class File(models.Model):
    user = models.ForeignKey(
        CustomerUser,
        on_delete=False,
        verbose_name=_("Users"),
    )
    name = models.CharField(_('Name'), max_length=200)
    description = models.CharField(_("Description"), max_length=50)
    image = ContentTypeRestrictedFileField(
        upload_to='file_image/',
        content_types=[
            'video/x-msvideo',
            'video/mp4',
            'video/webm',
            'video/ogg',
            'video/x-msvideo',
            'audio/mpeg',
            'audio/ogg',
            'audio/x-wav',
            'application/x-7z-compressed',
            'application/pdf',
            'application/pdf',
            'application/msword',
            'application/vnd.ms-powerpoint',
            'application/x-rar-compressed',
            'application/x-tar',
            'application/vnd.ms-excel',
            'application/zip',
            'image/jpeg',
            'image/gif',
            'image/png',
            'image/vnd.adobe.photoshop',
            'image/psd',
            ],
        max_upload_size=5242880,
        blank=True,
        null=True)
    created_on = models.DateTimeField(_('Created'), auto_now_add=True)
    status = models.BooleanField(_('Active'), default=True, help_text=(
                                    _('Indicate Status')))

    class Meta:
        verbose_name = _("File")
        verbose_name_plural = _("Files")

    def __str__(self):
        return self.name

    def extension(self):
        name, extension = os.path.splitext(self.image.name)
        return extension


from flask import *
from get_values import vol_response, get_volunteer_details, get_collections, enter_book
from datetime import datetime, date
from forms import *

volunteer_ui = Blueprint('volunteer_ui', __name__)

@volunteer_ui.route("/volunteer/<username>")
def volunteer_page(username):
    user_id = request.args.get('user_id')
    if session.get('logged_in') and session.get('role') == 'volunteer':
        details = get_volunteer_details(user_id)
        return render_template('roles/volunteer/volunteer_page.html', vol_details = details)
    else:
        return redirect(url_for('ui.error_page'))

@volunteer_ui.route("/volunteer/get_donors", methods=['POST'])
def get_donor_post():
    # number_of_donors = int(request.form['number_of_donors'])
    user_id = request.form['volunteer_id']
    from_date = request.form['from_date'].split('-')
    from_date = date(year=int(from_date[0]), month=int(from_date[1]), day=int(from_date[2]))
    to_date = request.form['to_date'].split('-')
    to_date = date(year=int(to_date[0]), month=int(to_date[1]), day=int(to_date[2]))

    # print(user_id, from_date, to_date)
    data = get_collections(user_id)
    ret_data = []
    for x in data:
        if from_date < x['start_date'] < to_date or from_date < x['end_date'] < to_date:
            x['start_date'] = str(x['start_date'])
            x['end_date'] = str(x['end_date'])
            ret_data.append(x)
    # print(data,'\n', ret_data)
    return json.dumps({
        'data': ret_data,
    })

@volunteer_ui.route('/volunteer/confirm_donation', methods=['GET', 'POST'])
def confirm_donation():
    volunteer_id = request.args.get('volunteer_id')
    if session['logged_in'] and session['role']=='volunteer':
        if request.method=='GET':
            return render_template('roles/volunteer/confirm_donation.html')
        elif request.method=='POST':
            donor_id = request.form.get('donor_id')
            book_name = request.form.get('book_name')
            isbn = request.form.get('isbn')
            enter_book(donor_id, book_name, isbn, volunteer_id)
            return "Success"
    else:
        return redirect(url_for('ui.error_page'))


# Import required libraries
import speech_recognition as sr
import pyttsx3
import os

class TriggerAssistant:
    def __init__(self):
        # Initialize the speech recognizer and text-to-speech engine
        self.recognizer = sr.Recognizer()
        self.engine = pyttsx3.init()

        # Set the voice rate and volume
        self.engine.setProperty('rate', 150)
        self.engine.setProperty('volume', 1)

        # Start listening
        self.listen()

    def listen(self):
        with sr.Microphone() as source:
            print("Listening...")
            self.recognizer.pause_threshold = 1
            audio = self.recognizer.listen(source)

            try:
                # Use the speech recognizer to recognize speech from the audio
                text = self.recognizer.recognize_google(audio)
                print(f"You said: {text}")

                # write speech to an environment variable so it can be used by the GPT module
                os.environ['query'] = text

                # Continue listening even if some other word is spoken
                self.listen()

            except sr.UnknownValueError:
                print("Audio could not be recognized")
                self.listen()

    def say(self, text):
        print(f"Chat Assistant: {text}")

        # Use the text-to-speech engine to speak the response
        self.engine.say(text)
        self.engine.runAndWait()

# # Create a ChatAssistant object
# assistant = TriggerAssistant()


__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"

from django import forms
from django.utils.safestring import mark_safe
from django.template.loader import get_template

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Div
from crispy_bootstrap5.bootstrap5 import FloatingField

from .models import Commentary
from .constants import COMMENTARY_PUBLISHED, COMMENTARY_PREPRINT

from comments.forms import CommentForm
from scipost.services import DOICaller, ArxivCaller

import strings

class DOIToQueryForm(forms.Form):
    VALID_DOI_REGEXP = r"(?i)10.\d{4,9}/[-._;()/:A-Z0-9]+$"
    doi = forms.RegexField(
        regex=VALID_DOI_REGEXP,
        strip=True,
        help_text=strings.doi_query_help_text,
        error_messages={"invalid": strings.doi_query_invalid},
        widget=forms.TextInput(
            {"label": "DOI", "placeholder": strings.doi_query_placeholder}
        ),
    )

    def clean_doi(self):
        input_doi = self.cleaned_data["doi"]

        commentary = Commentary.objects.filter(pub_DOI=input_doi)
        if commentary.exists():
            error_message = get_template(
                "commentaries/_doi_query_commentary_exists.html"
            ).render({"arxiv_or_DOI_string": commentary[0].arxiv_or_DOI_string})
            raise forms.ValidationError(mark_safe(error_message))

        caller = DOICaller(input_doi)
        if caller.is_valid:
            self.crossref_data = DOICaller(input_doi).data
        else:
            error_message = "Could not find a resource for that DOI."
            raise forms.ValidationError(error_message)

        return input_doi

    def request_published_article_form_prefill_data(self):
        additional_form_data = {"pub_DOI": self.cleaned_data["doi"]}
        return {**self.crossref_data, **additional_form_data}

class ArxivQueryForm(forms.Form):
    IDENTIFIER_PATTERN_NEW = r"^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$"
    IDENTIFIER_PATTERN_OLD = r"^[-.a-z]+/[0-9]{7,}v[0-9]{1,2}$"
    VALID_ARXIV_IDENTIFIER_REGEX = "(?:{})|(?:{})".format(
        IDENTIFIER_PATTERN_NEW, IDENTIFIER_PATTERN_OLD
    )

    identifier = forms.RegexField(
        regex=VALID_ARXIV_IDENTIFIER_REGEX,
        strip=True,
        help_text=strings.arxiv_query_help_text,
        error_messages={"invalid": strings.arxiv_query_invalid},
        widget=forms.TextInput({"placeholder": strings.arxiv_query_placeholder}),
    )

    def clean_identifier(self):
        identifier = self.cleaned_data["identifier"]

        commentary = Commentary.objects.filter(arxiv_identifier=identifier)
        if commentary.exists():
            error_message = get_template(
                "commentaries/_doi_query_commentary_exists.html"
            ).render({"arxiv_or_DOI_string": commentary[0].arxiv_or_DOI_string})
            raise forms.ValidationError(mark_safe(error_message))

        caller = ArxivCaller(identifier)
        if caller.is_valid:
            self.arxiv_data = ArxivCaller(identifier).data
        else:
            error_message = "Could not find a resource for that arXiv identifier."
            raise forms.ValidationError(error_message)

        return identifier

    def request_arxiv_preprint_form_prefill_data(self):
        additional_form_data = {"arxiv_identifier": self.cleaned_data["identifier"]}
        return {**self.arxiv_data, **additional_form_data}

class RequestCommentaryForm(forms.ModelForm):
    class Meta:
        model = Commentary
        fields = [
            "acad_field",
            "specialties",
            "approaches",
            "title",
            "author_list",
            "pub_date",
            "pub_abstract",
        ]
        placeholders = {"pub_date": "Format: YYYY-MM-DD"}

    def __init__(self, *args, **kwargs):
        self.requested_by = kwargs.pop("requested_by", None)
        super().__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        self.instance.parse_links_into_urls(commit=False)
        if self.requested_by:
            self.instance.requested_by = self.requested_by
        return super().save(*args, **kwargs)

class RequestArxivPreprintForm(RequestCommentaryForm):
    class Meta(RequestCommentaryForm.Meta):
        model = Commentary
        fields = RequestCommentaryForm.Meta.fields + ["arxiv_identifier"]

    def __init__(self, *args, **kwargs):
        super(RequestArxivPreprintForm, self).__init__(*args, **kwargs)
        # We want arxiv_identifier to be a required field.
        # Since it can be blank on the model, we have to override this property here.
        self.fields["arxiv_identifier"].required = True

    # TODO: add regex here?
    def clean_arxiv_identifier(self):
        arxiv_identifier = self.cleaned_data["arxiv_identifier"]

        commentary = Commentary.objects.filter(arxiv_identifier=arxiv_identifier)
        if commentary.exists():
            error_message = get_template(
                "commentaries/_doi_query_commentary_exists.html"
            ).render({"arxiv_or_DOI_string": commentary[0].arxiv_or_DOI_string})
            raise forms.ValidationError(mark_safe(error_message))

        return arxiv_identifier

    def save(self, *args, **kwargs):
        self.instance.type = COMMENTARY_PREPRINT
        return super().save(*args, **kwargs)

class RequestPublishedArticleForm(RequestCommentaryForm):
    class Meta(RequestCommentaryForm.Meta):
        fields = RequestCommentaryForm.Meta.fields + [
            "journal",
            "volume",
            "pages",
            "pub_DOI",
        ]
        placeholders = {
            **RequestCommentaryForm.Meta.placeholders,
            **{"pub_DOI": "ex.: 10.21468/00.000.000000"},
        }

    def __init__(self, *args, **kwargs):
        super(RequestPublishedArticleForm, self).__init__(*args, **kwargs)
        # We want pub_DOI to be a required field.
        # Since it can be blank on the model, we have to override this property here.
        self.fields["pub_DOI"].required = True

    def clean_pub_DOI(self):
        input_doi = self.cleaned_data["pub_DOI"]

        commentary = Commentary.objects.filter(pub_DOI=input_doi)
        if commentary.exists():
            error_message = get_template(
                "commentaries/_doi_query_commentary_exists.html"
            ).render({"arxiv_or_DOI_string": commentary[0].arxiv_or_DOI_string})
            raise forms.ValidationError(mark_safe(error_message))

        return input_doi

    def save(self, *args, **kwargs):
        self.instance.type = COMMENTARY_PUBLISHED
        return super().save(*args, **kwargs)

class VetCommentaryForm(forms.Form):
    """Process an unvetted Commentary request.

    This form will provide fields to let the user
    process a Commentary that is unvetted. On success,
    the Commentary is either accepted or deleted from
    the database.

    Keyword arguments:

    * commentary_id -- the Commentary.id to process (required)
    * user -- User instance of the vetting user (required)

    """

    ACTION_MODIFY = 0
    ACTION_ACCEPT = 1
    ACTION_REFUSE = 2
    COMMENTARY_ACTION_CHOICES = (
        (ACTION_MODIFY, "modify"),
        (ACTION_ACCEPT, "accept"),
        (ACTION_REFUSE, "refuse (give reason below)"),
    )
    REFUSAL_EMPTY = 0
    REFUSAL_PAPER_EXISTS = -1
    REFUSAL_UNTRACEBLE = -2
    REFUSAL_ARXIV_EXISTS = -3
    REFUSAL_OUT_OF_SCOPE = -4
    COMMENTARY_REFUSAL_CHOICES = (
        (REFUSAL_EMPTY, "-"),
        (REFUSAL_PAPER_EXISTS, "a commentary on this paper already exists"),
        (REFUSAL_UNTRACEBLE, "this paper cannot be traced"),
        (
            REFUSAL_ARXIV_EXISTS,
            "there exists a more revent version of this arXiv preprint",
        ),
        (
            REFUSAL_OUT_OF_SCOPE,
            "this paper is outside the scope of SciPost",
        ),
    )
    COMMENTARY_REFUSAL_DICT = dict(COMMENTARY_REFUSAL_CHOICES)

    action_option = forms.ChoiceField(
        widget=forms.RadioSelect,
        choices=COMMENTARY_ACTION_CHOICES,
        required=True,
        label="Action",
    )
    refusal_reason = forms.ChoiceField(
        choices=COMMENTARY_REFUSAL_CHOICES, required=False
    )
    email_response_field = forms.CharField(
        widget=forms.Textarea(attrs={"rows": 5, "cols": 40}),
        label="Justification (optional)",
        required=False,
    )

    def __init__(self, *args, **kwargs):
        """Pop and save keyword arguments if set, return form instance"""
        self.commentary_id = kwargs.pop("commentary_id", None)
        self.user = kwargs.pop("user", None)
        self.is_cleaned = False
        return super(VetCommentaryForm, self).__init__(*args, **kwargs)

    def clean(self, *args, **kwargs):
        """Check valid form and keyword arguments given"""
        cleaned_data = super(VetCommentaryForm, self).clean(*args, **kwargs)

        # Check valid `commentary_id`
        if not self.commentary_id:
            self.add_error(None, "No `commentary_id` provided")
            return cleaned_data
        else:
            self.commentary = Commentary.objects.select_related(
                "requested_by__user"
            ).get(pk=self.commentary_id)

        # Check valid `user`
        if not self.user:
            self.add_error(None, "No `user` provided")
            return cleaned_data

        self.is_cleaned = True
        return cleaned_data

    def _form_is_cleaned(self):
        """Raise ValueError if form isn't validated"""
        if not self.is_cleaned:
            raise ValueError(
                (
                    "VetCommentaryForm could not be processed "
                    "because the data didn't validate"
                )
            )

    def clean_refusal_reason(self):
        """`refusal_reason` field is required if action==refuse."""
        if self.commentary_is_refused():
            if int(self.cleaned_data["refusal_reason"]) == self.REFUSAL_EMPTY:
                self.add_error(
                    "refusal_reason", "Please, choose a reason for rejection."
                )
        return self.cleaned_data["refusal_reason"]

    def get_commentary(self):
        """Return Commentary if available"""
        self._form_is_cleaned()
        return self.commentary

    def get_refusal_reason(self):
        """Return refusal reason"""
        if self.commentary_is_refused():
            return self.COMMENTARY_REFUSAL_DICT[
                int(self.cleaned_data["refusal_reason"])
            ]

    def commentary_is_accepted(self):
        return int(self.cleaned_data["action_option"]) == self.ACTION_ACCEPT

    def commentary_is_modified(self):
        return int(self.cleaned_data["action_option"]) == self.ACTION_MODIFY

    def commentary_is_refused(self):
        return int(self.cleaned_data["action_option"]) == self.ACTION_REFUSE

    def process_commentary(self):
        """Vet the commentary or delete it from the database"""
        # Modified actions are not doing anything. Users are redirected to an edit page instead.
        if self.commentary_is_accepted():
            self.commentary.vetted = True
            self.commentary.vetted_by = self.user.contributor
            self.commentary.save()
            return self.commentary
        elif self.commentary_is_refused():
            self.commentary.delete()
            return None

class CommentarySearchForm(forms.Form):
    author = forms.CharField(
        max_length=100, required=False, label="On publication with author(s)"
    )
    title = forms.CharField(
        max_length=100, required=False, label="On publication with title"
    )
    abstract = forms.CharField(
        max_length=1000, required=False, label="On publication with abstract"
    )

    def __init__(self, *args, **kwargs):
        self.acad_field_slug = kwargs.pop("acad_field_slug")
        self.specialty_slug = kwargs.pop("specialty_slug")
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Div(
                FloatingField("author"),
                FloatingField("title"),
                FloatingField("abstract"),
            ),
        )

    def search_results(self):
        """Return all Commentary objects according to search"""
        commentaries = Commentary.objects.vetted()
        if self.acad_field_slug and self.acad_field_slug != "all":
            commentaries = commentaries.filter(acad_field__slug=self.acad_field_slug)
            if self.specialty_slug and self.specialty_slug != "all":
                commentaries = commentaries.filter(
                    specialties__slug=self.specialty_slug
                )
        if hasattr(self, "cleaned_data"):
            if "title" in self.cleaned_data:
                commentaries = commentaries.filter(
                    title__icontains=self.cleaned_data["title"],
                )
            if "abstract" in self.cleaned_data:
                commentaries = commentaries.filter(
                    pub_abstract__icontains=self.cleaned_data["abstract"],
                )
            if "author" in self.cleaned_data:
                commentaries = commentaries.filter(
                    author_list__icontains=self.cleaned_data["author"],
                )
        return commentaries.order_by("-pub_date")

class CommentaryListSearchForm(forms.Form):
    """Search for Commentary specified by user (for old CommentaryListView)"""

    author = forms.CharField(max_length=100, required=False, label="Author(s)")
    title = forms.CharField(max_length=100, required=False, label="Title")
    abstract = forms.CharField(max_length=1000, required=False, label="Abstract")

    def search_results(self):
        """Return all Commentary objects according to search"""
        return Commentary.objects.vetted(
            title__icontains=self.cleaned_data["title"],
            pub_abstract__icontains=self.cleaned_data["abstract"],
            author_list__icontains=self.cleaned_data["author"],
        ).order_by("-pub_date")

class CommentSciPostPublication(CommentForm):
    """
    This Form will let authors of a SciPost publication comment on their Publication
    using the Commentary functionalities. It will create a Commentary page if it does not
    exist yet.

    It inherits from ModelForm: CommentForm and thus will, by default, return a Comment!
    """

    def __init__(self, *args, **kwargs):
        self.publication = kwargs.pop("publication")
        self.current_user = kwargs.pop("current_user")
        super().__init__(*args, **kwargs)

    def save(self, commit=True):
        """
        Create (vetted) Commentary page if not exist and do save actions as
        per original CommentForm.
        """
        if not commit:
            raise AssertionError(
                "CommentSciPostPublication can only be used with commit=True"
            )

        try:
            commentary = self.publication.commentary
        except Commentary.DoesNotExist:
            submission = self.publication.accepted_submission
            commentary = Commentary(
                **{
                    # 'vetted_by': None,
                    "requested_by": self.current_user.contributor,
                    "vetted": True,
                    "type": COMMENTARY_PUBLISHED,
                    "acad_field": self.publication.acad_field,
                    "specialties": self.publication.specialties,
                    "approaches": self.publication.approaches,
                    "title": self.publication.title,
                    "arxiv_identifier": submission.preprint.identifier_w_vn_nr,
                    "arxiv_link": submission.preprint.url,
                    "pub_DOI": self.publication.doi_string,
                    "metadata": self.publication.metadata,
                    "scipost_publication": self.publication,
                    "author_list": self.publication.author_list,
                    "journal": self.publication.in_issue.in_volume.in_journal.name,
                    "pages": self.publication.in_issue.number,
                    "volume": self.publication.in_issue.in_volume.number,
                    "pub_date": self.publication.publication_date,
                    "pub_abstract": self.publication.abstract,
                }
            )
            commentary.parse_links_into_urls(commit=False)
            commentary.save()
            commentary.authors.add(*self.publication.authors.all())

        # Original saving steps
        comment = super().save(commit=False)
        comment.author = self.current_user.contributor
        comment.is_author_reply = True
        comment.content_object = commentary
        comment.save()
        comment.grant_permissions()
        return comment


import torch
from torch import nn
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
from my_functions import *

class BasicBlock(nn.Module):
    expansion = 1
    def __init__(self, in_channels, inner_channels, stride = 1, projection = None):
        super().__init__()

        self.residual = nn.Sequential(nn.Conv2d(in_channels, inner_channels, 3, stride=stride, padding=1, bias=False),
                                      nn.BatchNorm2d(inner_channels),
                                      nn.ReLU(inplace=True),
                                      nn.Conv2d(inner_channels, inner_channels * self.expansion, 3, padding=1, bias=False),
                                      nn.BatchNorm2d(inner_channels)
                                      )
        self.projection = projection
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):

        residual = self.residual(x)

        if self.projection is not None:
            shortcut = self.projection(x)
        else:
            shortcut = x

        out = self.relu(residual + shortcut)
        return out

class Bottleneck(nn.Module):

    expansion = 4
    def __init__(self, in_channels, inner_channels, stride = 1, projection = None):
        super().__init__()

        self.residual = nn.Sequential(nn.Conv2d(in_channels, inner_channels, 1, bias=False),
                                      nn.BatchNorm2d(inner_channels),
                                      nn.ReLU(inplace=True),
                                      nn.Conv2d(inner_channels, inner_channels, 3, stride=stride, padding=1, bias=False),
                                      nn.BatchNorm2d(inner_channels),
                                      nn.ReLU(inplace=True),
                                      nn.Conv2d(inner_channels, inner_channels * self.expansion, 1, bias=False),
                                      nn.BatchNorm2d(inner_channels * self.expansion))

        self.projection = projection
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        residual = self.residual(x)

        if self.projection is not None:
            shortcut = self.projection(x)
        else:
            shortcut = x

        out = self.relu(residual + shortcut)
        return out

class ResNet(nn.Module):
    def __init__(self, block, num_block_list, num_classes = 120, zero_init_residual = True):
        super().__init__()

        self.in_channels = 64

        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True) # 좀더 메모리 효율적
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self.make_layers(block, 64, num_block_list[0], stride=1)
        self.layer2 = self.make_layers(block, 128, num_block_list[1], stride=2)
        self.layer3 = self.make_layers(block, 256, num_block_list[2], stride=2)
        self.layer4 = self.make_layers(block, 512, num_block_list[3], stride=2)
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu")

        # Zero-initialize the last BN in each residual branch,
        # so that the residual branch starts with zeros, and each residual block behaves like an identity.
        # This improves the model by 0.2~0.3%p according to https://arxiv.org/abs/1706.02677
        if zero_init_residual:
            for m in self.modules():
                if isinstance(m, block):
                    nn.init.constant_(m.residual[-1].weight, 0)

    def make_layers(self, block, inner_channels, num_blocks, stride = 1):

        if stride != 1 or self.in_channels != inner_channels * block.expansion:
            # stride = 1 이여도 채널 수가 다르면 (layer1의 첫번째 BottleNeck) projection 해야함 (이 때는 resoltion은 그대로, 채널 수만 늘어남)
            projection = nn.Sequential(
                nn.Conv2d(self.in_channels, inner_channels * block.expansion, 1, stride=stride, bias=False),
                nn.BatchNorm2d(inner_channels * block.expansion)) # 점선 connection 임
        else:
            projection = None

        layers = []
        layers += [block(self.in_channels, inner_channels, stride, projection)] # projection은 첫 block에서만
        self.in_channels = inner_channels * block.expansion
        for _ in range(1, num_blocks):
            layers += [block(self.in_channels, inner_channels)]

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.fc(x)
        return x

def ResNet50():
    return ResNet(Bottleneck, [3,4,6,3])

def ResNet101():
    return ResNet(Bottleneck, [3, 4, 23, 3])

def ResNet152(**kwargs):
    return ResNet(Bottleneck, [3, 8, 36, 3])

# class PretrainedResNet(nn.Module):

#     def __init__(self, pretrained_resnet, n_classes, freeze=True):
#         super().__init__()
#         self.pretrained_resnet = pretrained_resnet
#         if freeze: # freeze
#             for p in pretrained_resnet.parameters():
#                 p.requires_grad = False
#         out_features = self.pretrained_resnet.fc.out_features
#         # 학습시킬 파라미터
#         self.fc1 = nn.Linear(out_features, 1024)
#         self.fc2 = nn.Linear(1024, n_classes)

#     def forward(self, x):
#         # 전처리된 이미지를 pretrained 모델에 통과시키기
#         x = self.pretrained_resnet(x)
#         # 추가적인 레이어에 통과시켜서 최종 클래스 예측을 만들기
#         x = nn.functional.relu(self.fc1(x))
#         x = self.fc2(x)
#         return x

class PretrainedResNet(nn.Module):

    def __init__(self, pretrained_resnet, n_classes, freeze=True):
        super().__init__()
        self.pretrained_resnet = pretrained_resnet
        if freeze: # freeze
            for name, child in self.pretrained_resnet.named_children():
                for param in child.parameters():
                    if name not in ['fc']:
                        param.requires_grad = False
        out_features = self.pretrained_resnet.fc.out_features
        # 학습시킬 파라미터
        self.fc1 = nn.Linear(out_features, 1024)
        self.fc2 = nn.Linear(1024, n_classes)

    def forward(self, x):
        # 전처리된 이미지를 pretrained 모델에 통과시키기
        x = self.pretrained_resnet(x)
        # 추가적인 레이어에 통과시켜서 최종 클래스 예측을 만들기
        x = nn.functional.relu(self.fc1(x))
        x = self.fc2(x)
        return x

class PretrainedResNetFreeze(nn.Module):

    def __init__(self, pretrained_resnet, n_classes, freeze=True):
        super().__init__()
        self.pretrained_resnet = pretrained_resnet
        if freeze: # freeze
            for name, child in pretrained_resnet.named_children():
                if name in ['conv1', 'bn1', 'relu']:
                    for param in child.parameters():
                        param.requires_grad = False
        out_features = self.pretrained_resnet.fc.in_features
        # 학습시킬 파라미터
        self.fc1 = nn.Linear(out_features, 1024)
        self.fc2 = nn.Linear(1024, n_classes)

    def forward(self, x):
        # 전처리된 이미지를 pretrained 모델에 통과시키기
        x = self.pretrained_resnet.conv1(x)
        x = self.pretrained_resnet.bn1(x)
        x = self.pretrained_resnet.relu(x)

        # 추가적인 레이어에 통과시켜서 최종 클래스 예측을 만들기
        x = self.pretrained_resnet.maxpool(x)
        x = self.pretrained_resnet.layer1(x)
        x = self.pretrained_resnet.layer2(x)
        x = self.pretrained_resnet.layer3(x)
        x = self.pretrained_resnet.layer4(x)
        x = self.pretrained_resnet.avgpool(x)

        x = torch.flatten(x ,1)
        x = nn.functional.relu(self.fc1(x))
        x = self.fc2(x)
        return x

class CustomDenseNet169(nn.Module):
    def __init__(self, models, num_classes=120):
        super(CustomDenseNet169, self).__init__()
        self.densenet = models.densenet169(pretrained=True)
        num_features = self.densenet.classifier.in_features
        self.densenet.classifier = nn.Sequential(
            nn.Linear(num_features, num_classes)
        )

    def forward(self, x):
        return self.densenet(x)

# model = CustomDenseNet169().to(DEVICE)


import numpy as np
from PIL import Image

def createImg():
    """
    Create 3 images of the same size: load two different images of the same
    size from files, and create a third image of noise,
    and then mix the 3 images.
    The images are centered and normalized, so
    store the means and standard deviations of the images for display later.
    """
    d = 3;
    #for each image, convert to 1d array  for mixing
    #need to center to zero for mixing

    #first image from file
    prfimg = Image.open('profilepic2.jpg')
    prfimg = prfimg.convert('L')
    #convert to centered normalized 2d array
    prfarr = np.asarray(prfimg)
    prfarr = prfarr - 127.0
    #get image size
    n1,n2 = prfarr.shape
    n = n1*n2

    #second image from file
    astimg = Image.open('ashitaka.jpeg')
    astimg = astimg.convert('L')
    #convert to centered normalized 2d array
    astarr = np.asarray(astimg)
    astarr = astarr - 127.0
    n1tmp, n2tmp = astarr.shape

    #third image of noise, make a centered normalized arra
    noisarr = 120*np.sqrt(np.abs(np.random.randn(n1,n2)))
    noisarr = noisarr - 127.0

    #for storing 1-d arrays for mixing
    s = np.zeros((d,n))
    x = np.zeros((d,n))

    assert n1 == n1tmp
    assert n2 == n2tmp

    #reshape to 1-d arrays and store
    s[0,:] = np.reshape(prfarr,(1,n))
    s[1,:] = np.reshape(astarr,(1,n))
    s[2,:] = np.reshape(noisarr,(1,n))

    #mixing array
    a = np.array([[1, 0.4, 0.4], [-0.8, 0.8, 0.4],[0.7, -0.5, 0.8]])

    #mix signals
    x = np.dot(a,s)

    #create lists of original and mixed images
    orig_images = []
    mixed_images = []
    for k in range(3):
        sarr = np.reshape(s[k,:], (n1,n2)) + 127.0
        orig_images.append(Image.fromarray(sarr))
        xarr = np.reshape(x[k,:], (n1,n2)) + 127.0
        mixed_images.append(Image.fromarray(xarr))

    return (orig_images,mixed_images)

def createSig():
    """
    create 2 1-d signals: one a modulated sinusoid, the other filtered noise,
    and mix them
    """
    n=1000
    s = np.zeros((2,n))
    t = np.arange(n)
    s[0,:] = np.sin(2*np.pi*t/50)*np.sin(2*np.pi*t/600);
    h = 1.75*np.convolve(np.ones(30)/30.0,np.ones(20)/20,mode='full')
    s[1,:] = np.convolve(np.random.randn(n),h,mode='same')

    #mixing array
    a = np.array([[1, 0.75], [1.75, -2]])
    x = np.dot(a,s)

    #create lists of original and mixed signals (1d numpy arrays)
    orig_signals = []
    mixed_signals = []
    for k in range(2):
        orig_signals.append(s[k,:])
        mixed_signals.append(x[k,:])

    return (orig_signals, mixed_signals)


import logging
from collections import namedtuple
from math import floor

log = logging.getLogger(__name__)

#########
### https://github.com/EzraBC/pgsreader/blob/master/pgsreader.py
### http://blog.thescorpius.com/index.php/2017/07/15/presentation-graphic-stream-sup-files-bluray-subtitle-format/
#########

# Constants for Segments
PDS = int('0x14', 16)
ODS = int('0x15', 16)
PCS = int('0x16', 16)
WDS = int('0x17', 16)
END = int('0x80', 16)

# Named tuple access for static PDS palettes
Palette = namedtuple('Palette', "Y Cr Cb Alpha")

class InvalidSegmentError(Exception):
    '''Raised when a segment does not match PGS specification'''

class InvalidSegmentType(InvalidSegmentError):
    pass

def mseg_srt(msc):
    sec = floor(msc / 1000)
    mnt = floor(sec / 60)
    hou = floor(mnt / 60)
    msc = int(msc % 1000)
    sec = sec % 60
    mnt = mnt % 60
    stt = "{:02d}:{:02d}:{:02d},{:03d}".format(hou, mnt, sec, msc)
    return stt

class PGSReader:

    def __init__(self, filepath):
        self.file = filepath
        with open(self.file, 'rb') as f:
            self.bytes = f.read()

    def make_segment(self, bytes_):
        try:
            cls = SEGMENT_TYPE[bytes_[10]]
        except KeyError:
            raise InvalidSegmentType("{} {} not in SEGMENT_TYPE".format(self.file, bytes_[10]))
        return cls(bytes_)

    def iter_segments(self):
        bytes_ = self.bytes[:]
        while bytes_:
            size = 13 + int(bytes_[11:13].hex(), 16)
            yield self.make_segment(bytes_[:size])
            bytes_ = bytes_[size:]

    def iter_displaysets(self):
        ds = []
        for s in self.iter_segments():
            ds.append(s)
            if s.type == 'END':
                yield DisplaySet(ds)
                ds = []

    @property
    def segments(self):
        if not hasattr(self, '_segments'):
            self._segments = list(self.iter_segments())
        return self._segments

    @property
    def displaysets(self):
        if not hasattr(self, '_displaysets'):
            self._displaysets = list(self.iter_displaysets())
        return self._displaysets

    def get_times(self):
        seg = list()
        for i, ds in enumerate(self.displaysets):
            sg = ds.segments
            if ds.ods:
                sg = ds.ods
            t = min(s.presentation_timestamp for s in sg)
            seg.append((t, ds.has_image))
        seg = sorted(set(seg))
        tms = []
        for i, (t, ods) in enumerate(seg):
            if ods:
                tms.append((t, seg[i+1][0]-1))
        tms = tuple(sorted(set(tms)))
        return tms

    def fake_srt(self, file=None):
        if file is None:
            file = self.file.rsplit(".", 1)[0]+".srt"
        with open(file, "w") as f:
            for (i, (s, e)) in enumerate(self.get_times()):
                f.write("{}\n".format(i+1))
                f.write("{} --> {}\n".format(mseg_srt(s), mseg_srt(e)))
                f.write("Line {}\n\n".format(i))
        return file

class BaseSegment:
    SEGMENT = {
        PDS: 'PDS',
        ODS: 'ODS',
        PCS: 'PCS',
        WDS: 'WDS',
        END: 'END'
    }

    def __init__(self, bytes_):
        self.bytes = bytes_
        if bytes_[:2] != b'PG':
            raise InvalidSegmentError
        self.pts = int(bytes_[2:6].hex(), base=16) / 90
        self.dts = int(bytes_[6:10].hex(), base=16) / 90
        self.type = self.SEGMENT[bytes_[10]]
        self.size = int(bytes_[11:13].hex(), base=16)
        self.data = bytes_[13:]

    def __len__(self):
        return self.size

    @property
    def presentation_timestamp(self): return self.pts

    @property
    def decoding_timestamp(self): return self.dts

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

class PresentationCompositionSegment(BaseSegment):
    class CompositionObject:

        def __init__(self, bytes_):
            self.bytes = bytes_
            self.object_id = int(bytes_[0:2].hex(), base=16)
            self.window_id = bytes_[2]
            self.cropped = bool(bytes_[3])
            self.x_offset = int(bytes_[4:6].hex(), base=16)
            self.y_offset = int(bytes_[6:8].hex(), base=16)
            if self.cropped:
                self.crop_x_offset = int(bytes_[8:10].hex(), base=16)
                self.crop_y_offset = int(bytes_[10:12].hex(), base=16)
                self.crop_width = int(bytes_[12:14].hex(), base=16)
                self.crop_height = int(bytes_[14:16].hex(), base=16)

    STATE = {
        int('0x00', base=16): 'Normal',
        int('0x40', base=16): 'Acquisition Point',
        int('0x80', base=16): 'Epoch Start'
    }

    def __init__(self, bytes_):
        BaseSegment.__init__(self, bytes_)
        self.width = int(self.data[0:2].hex(), base=16)
        self.height = int(self.data[2:4].hex(), base=16)
        self.frame_rate = self.data[4]
        self._num = int(self.data[5:7].hex(), base=16)
        self._state = self.STATE[self.data[7]]
        self.palette_update = bool(self.data[8])
        self.palette_id = self.data[9]
        self._num_comps = self.data[10]

    @property
    def composition_number(self):
        return self._num

    @property
    def composition_state(self):
        return self._state

    @property
    def composition_objects(self):
        if not hasattr(self, '_composition_objects'):
            self._composition_objects = self.get_composition_objects()
            if len(self._composition_objects) != self._num_comps:
                log.warning('Number of composition objects asserted does not match the amount found.')
        return self._composition_objects

    def get_composition_objects(self):
        bytes_ = self.data[11:]
        comps = []
        while bytes_:
            length = 8 * (1 + bool(bytes_[3]))
            comps.append(self.CompositionObject(bytes_[:length]))
            bytes_ = bytes_[length:]
        return comps

class WindowDefinitionSegment(BaseSegment):

    def __init__(self, bytes_):
        BaseSegment.__init__(self, bytes_)
        self.num_windows = self.data[0]
        self.window_id = self.data[1]
        self.x_offset = int(self.data[2:4].hex(), base=16)
        self.y_offset = int(self.data[4:6].hex(), base=16)
        self.width = int(self.data[6:8].hex(), base=16)
        self.height = int(self.data[8:10].hex(), base=16)

class PaletteDefinitionSegment(BaseSegment):

    def __init__(self, bytes_):
        BaseSegment.__init__(self, bytes_)
        self.palette_id = self.data[0]
        self.version = self.data[1]
        self.palette = [Palette(0, 0, 0, 0)] * 256
        # Slice from byte 2 til end of segment. Divide by 5 to determine number of palette entries
        # Iterate entries. Explode the 5 bytes into namedtuple Palette. Must be exploded
        for entry in range(len(self.data[2:]) // 5):
            i = 2 + entry * 5
            self.palette[self.data[i]] = Palette(*self.data[i + 1:i + 5])

class ObjectDefinitionSegment(BaseSegment):
    SEQUENCE = {
        int('0x40', base=16): 'Last',
        int('0x80', base=16): 'First',
        int('0xc0', base=16): 'First and last'
    }

    def __init__(self, bytes_):
        BaseSegment.__init__(self, bytes_)
        self.id = int(self.data[0:2].hex(), base=16)
        self.version = self.data[2]
        self.in_sequence = self.SEQUENCE[self.data[3]]
        self.data_len = int(self.data[4:7].hex(), base=16)
        self.width = int(self.data[7:9].hex(), base=16)
        self.height = int(self.data[9:11].hex(), base=16)
        self.img_data = self.data[11:]
        if len(self.img_data) != self.data_len - 4:
            log.warning('Image data length asserted does not match the length found.')

class EndSegment(BaseSegment):

    @property
    def is_end(self): return True

SEGMENT_TYPE = {
    PDS: PaletteDefinitionSegment,
    ODS: ObjectDefinitionSegment,
    PCS: PresentationCompositionSegment,
    WDS: WindowDefinitionSegment,
    END: EndSegment
}

class DisplaySet:

    def __init__(self, segments):
        self.segments = segments
        self.segment_types = [s.type for s in segments]
        self.has_image = 'ODS' in self.segment_types

def segment_by_type_getter(type_):
    def f(self):
        return [s for s in self.segments if s.type == type_]

    return f

for type_ in BaseSegment.SEGMENT.values():
    setattr(DisplaySet, type_.lower(), property(segment_by_type_getter(type_)))


from django.test import TestCase
from users.models import CustomUser
from reviews.models import Review, Feedback

class ReviewTest(TestCase):
    """
    Test module for Review model
    """

    def setUp(self):
        user = CustomUser.objects.create(
            username='testuser',
            is_admin=True,
            full_name='Test User'
        )
        Review.objects.create(
            employee=user,
        )

    def test_create_review(self):
        review = Review.objects.latest('created_at')
        self.assertEqual(review.employee.username, 'testuser')

class FeedbackTest(TestCase):
    """
    Test module for Feedback model
    """

    def setUp(self):
        user = CustomUser.objects.create(
            username='testuser',
            is_admin=True,
            full_name='Test User'
        )
        user_to_assign_1 = CustomUser.objects.create(
            username='testuser2',
            is_admin=False,
            full_name='Test User'
        )
        user_to_assign_2 = CustomUser.objects.create(
            username='testuser3',
            is_admin=False,
            full_name='Test User'
        )
        review = Review.objects.create(
            employee=user,
        )
        Feedback.objects.create(
            review=review,
            assignee=user_to_assign_1,
        )
        Feedback.objects.create(
            review=review,
            assignee=user_to_assign_2,
        )

    def test_create_review(self):
        review = Review.objects.latest('created_at')
        feedbacks = Feedback.objects.filter(
            review__id=review.id
        )
        self.assertEqual(feedbacks[0].review.employee.username, 'testuser')
        self.assertEqual(feedbacks[1].review.employee.username, 'testuser')


from flask import Flask, make_response, request, current_app
from flask_cors import CORS
from flask import json, jsonify
from base import conn, engine, session
from datetime import date, datetime
from sqlalchemy import Column, Integer, DateTime, Date
from scipy import stats
import pandas as pd

app = Flask(__name__)
CORS(app)

PathA = "/Users/rachelmei/Documents/WaterIslandCapital/src/data/FILES A/"
PathB = "/Users/rachelmei/Documents/WaterIslandCapital/src/data/FILES B/"
PathC = "/Users/rachelmei/Documents/WaterIslandCapital/src/data/FILES C/"

@app.route('/uploadCsv', methods = ['POST'])
def uploadCsv():
    res = []
    tick = request.json["firm"]
    for record in request.json["data"]:
        res.append((datetime.strptime(record["Date"], "%m/%d/%y").date(), tick, record["PX_LAST"], record["PX_VOLUME"]))

    try:
        conn.commit()
    except:
        conn.rollback()

    return "JSON Message: " + json.dumps(request.json)

@app.route('/resetAll', methods= ['PUT'])
def reset():
    print("reset")
    session.execute('''TRUNCATE TABLE MarketData''')
    session.execute('''TRUNCATE TABLE CompanyOverview''')
    session.execute('''TRUNCATE TABLE Trading''')
    session.commit()
    session.close()
    return "Success"

@app.route('/uploadAll', methods = ['PUT'])
def uploadAll():
    print("upload")
    uploadDescription()
    uploadEquity()
    uploadTrading()
    return "Success"

def uploadEquity():
    firmList = getFirmList()
    frames = []

    for firm in firmList:
        ticker = firm
        csv = PathA + ticker + ".csv"
        df = pd.read_csv(csv)
        df['Ticker'] = ticker
        frames.append(df)

    result = pd.concat(frames)
    result['Date'] = pd.to_datetime(result['Date'], format="%m/%d/%Y")
    print("done equity")
    with engine.connect() as conn, conn.begin():
        result.to_sql('MarketData', conn, if_exists='replace', dtype={"Date": DateTime})

def uploadDescription():
    result = pd.read_csv(PathB + "Description.csv")
    result['Ticker'] = result['Ticker'].apply(lambda x: x.split(" ")[0])
    print("done des")
    with engine.connect() as conn, conn.begin():
        result.to_sql('CompanyOverview', conn, if_exists='replace')

# def uploadTrading():
#     result = pd.read_csv(PathC + "Trading.csv")
#     result['Date'] = pd.to_datetime(result['Date'], format="%m/%d/%Y")
#     sLength = len(result['Date'])
#     result['PnL'] = pd.Series(0, index=result.index)
#     result['AccountBalance'] = pd.Series(0, index=result.index)
#
#     print("done trading")
#     with engine.connect() as conn, conn.begin():
#         result.to_sql('Trading', conn, if_exists='replace', dtype={"Date": DateTime})

def uploadTrading():
    result = pd.read_csv(PathC + "Trading.csv")
    result['Date'] = pd.to_datetime(result['Date'], format="%m/%d/%Y")

    print("done trading")
    with engine.connect() as conn, conn.begin():
        result.to_sql('Trading', conn, if_exists='replace', dtype={"Date": DateTime})

def getFirmList():
    data = pd.read_sql('SELECT Ticker FROM CompanyOverview', conn)
    return [x.split(" ")[0] for x in data['Ticker'].tolist()]

@app.route('/getFirms', methods = ['GET'])
def getFirms():
    firms = {
        'firms': getFirmList()
    }

    return jsonify(firms)

@app.route('/firm_overview/<firm>', methods = ['GET'])
def getOverView(firm):

    df = pd.read_sql("SELECT * FROM CompanyOverview where Ticker = \"%s\"" %firm, conn)
    data = {
        'overview': df.to_json(orient='records')

    }
    return jsonify(data)

@app.route('/stats', methods = ['POST'])
def pairStats():
    form = request.form.to_dict()
    tick1 = form['ticker1'].split(" ")[0]
    tick2 = form['ticker2'].split(" ")[0]

    df = pd.read_sql('SELECT * FROM MarketData', conn)

    a = df[df['Ticker'] == tick1]['PX_LAST'].tolist()
    b = df[df['Ticker'] == tick2]['PX_LAST'].tolist()

    slope, intercept, r_value, p_value, std_err = stats.linregress(a, b)
    corr = stats.pearsonr(a, b)[0]
    beta = slope

    data = {
        'corr' : corr,
        'beta' : beta
    }

    return jsonify(data)

@app.route('/timeseries/<firm>')
def getTS(firm):
    df = pd.read_sql("SELECT * FROM MarketData where Ticker= \"%s\"" %firm, conn)
    data = {
        'ts': df.to_json(orient='records')
    }
    return jsonify(data)

@app.route('/add_portfolio', methods = ['POST'])
def addPortfolio():
    form = request.form.to_dict()
    data = json.loads(form["jf"])
    weights = data["weights"]
    ids = data["ids"]
    d = {'Ticker': ids, 'Weight': weights}
    df = pd.DataFrame(data=d)
    df.to_sql('Portfolio', conn, if_exists='replace')
    return jsonify(data)

if __name__ == '__main__':

    # pairStats()
    # data = pd.read_sql('SELECT * FROM MarketData', conn)
    # df.to_sql(con=conn, name='CompanyOverview', if_exists='replace', flavor='mysql')
    #
    # with engine.connect() as conn, conn.begin():
    #     df.to_sql('CompanyOverview', conn, if_exists='replace')

    # print(list(df))

    app.run(host="localhost", port=5000,  threaded=True)

    # uploadAll()
    # reset()
    # uploadDescription()


from configparser import ConfigParser
from multiprocessing import Process, Queue

import pygame

from core.data_retriever import DataRetriever
from core.data_presenter import DataPresenter
from core.logging_filters import create_logger
from scripts.create_db import create_db_if_not_present

CONFIG_FILE = 'config.ini'

pygame.init()

if __name__ == '__main__':
    config = ConfigParser()
    config.read(CONFIG_FILE)
    logger = create_logger(config['general']['logging_path'], __name__)

    logger.info("Starting main application")

    create_db_if_not_present(CONFIG_FILE)

    queue_retriever = Queue()
    queue_presenter = Queue()

    data_retriever = DataRetriever(CONFIG_FILE, queue_retriever)
    data_presenter = DataPresenter(CONFIG_FILE, queue_presenter)
    p_retrieve = Process(target=data_retriever.run)
    p_present = Process(target=data_presenter.run)
    p_retrieve.start()
    p_present.start()

    obj = queue_presenter.get()
    logger.info("Received halting signal from presenter")
    logger.info("halting retriever")
    queue_retriever.put("halt")

    logger.info("Closing queues")
    queue_retriever.close()
    queue_presenter.close()
    queue_retriever.join_thread()
    queue_presenter.join_thread()

    logger.info("Joining processes")
    p_present.join()
    p_retrieve.join()

    logger.info("Quitting pygame")
    pygame.quit()


import torch
import torch.nn as nn

from unip.core.node import InOutNode, OutOutNode, CustomNode
from unip.utils.data_type import DIM_IN, DIM_OUT, IDX_IN, IDX_OUT

def save_model(model, path):
    assert isinstance(
        model, nn.Module
    ), f"model must be a nn.Module, but got {type(model)}"
    torch.save(model, path)

def load_model(path):
    model = torch.load(path)
    assert isinstance(
        model, nn.Module
    ), f"model must be a nn.Module, but got {type(model)}"
    return model

def save_model_dict(model, key2node, path):
    prune_dict = {}
    for key, node in key2node.items():
        if isinstance(node, (InOutNode, OutOutNode, CustomNode)):
            assert hasattr(
                node, "get_attr"
            ), f"{node} does not have get_attr() method, please use save_model() instead."
            prune_dict[key] = node.get_attr()
    torch.save(prune_dict, path)

def load_model_dict(model, path):
    prune_dict = torch.load(path)
    for key, attrs in prune_dict.items():
        module = _getattr(model, key)
        for attr, value in attrs.items():
            instance, attr = _getattr_prev(module, attr)
            setattr(instance, attr, value)
    return model

def _getattr(src, attr):
    attr_list = attr.split(".")
    if len(attr_list) == 1:
        return getattr(src, attr)
    src = getattr(src, attr_list[0])
    return _getattr(src, ".".join(attr_list[1:]))

def _getattr_prev(src, attr):
    attr_list = attr.split(".")
    if len(attr_list) == 1:
        return src, attr
    src = getattr(src, attr_list[0])
    return _getattr_prev(src, ".".join(attr_list[1:]))


from __future__ import annotations

import json
from pathlib import Path
from typing import Dict, List

from click.testing import CliRunner, Result

from cdv.cmds.cli import cli

EMPTY_SIG = "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"  # noqa

class TestInspectCommands:
    def test_any(self):
        runner = CliRunner()

        # Try to inspect a program
        result: Result = runner.invoke(cli, ["inspect", "any", "ff0101"])
        assert result.exit_code == 0
        assert "guess" not in result.output

        # Try to inspect a private key
        result = runner.invoke(
            cli,
            [
                "inspect",
                "any",
                "05ec9428fc2841a79e96631a633b154b57a45311c0602269a6500732093a52cd",
            ],
        )
        assert result.exit_code == 0
        assert "guess" not in result.output

        # Try to inspect a public key
        result = runner.invoke(
            cli,
            [
                "inspect",
                "any",
                "b364a088d9df9423e54bff4c62e4bd854445fb8f5b8f6d80dea06773a4f828734a3a75318b180364ca8468836f0742db",
            ],
        )
        assert result.exit_code == 0
        assert "guess" not in result.output

        # Try to inspect an aggsig
        result = runner.invoke(
            cli,
            [
                "inspect",
                "any",
                EMPTY_SIG,
            ],
        )
        assert result.exit_code == 0
        assert "guess" not in result.output

        for class_type in ["coinrecord", "coin", "spendbundle", "spend"]:
            valid_json_path = Path(__file__).parent.joinpath(f"object_files/{class_type}s/{class_type}.json")
            invalid_json_path = Path(__file__).parent.joinpath(f"object_files/{class_type}s/{class_type}_invalid.json")
            metadata_path = Path(__file__).parent.joinpath(f"object_files/{class_type}s/{class_type}_metadata.json")
            valid_json: Dict = json.loads(open(valid_json_path, "r").read())
            metadata_json: Dict = json.loads(open(metadata_path, "r").read())

            # Try to load the invalid and make sure it fails
            result = runner.invoke(cli, ["inspect", "any", str(invalid_json_path)])
            assert result.exit_code == 0
            # If this succeeds, there should be no file name, if it fails, the file name should be output as info
            assert str(invalid_json_path) in result.output

            # Try to load the valid json
            result = runner.invoke(cli, ["inspect", "any", json.dumps(valid_json)])
            assert result.exit_code == 0
            for key in valid_json.keys():
                key_type = type(valid_json[key])
                if (key_type is not dict) and (key_type is not list):
                    assert (str(valid_json[key]) in result.output) or (str(valid_json[key]).lower() in result.output)

            # Try to load bytes
            if class_type != "coin":
                valid_hex_path = Path(__file__).parent.joinpath(f"object_files/{class_type}s/{class_type}.hex")

                # From a file
                result = runner.invoke(cli, ["inspect", "--json", "any", str(valid_hex_path)])
                assert result.exit_code == 0
                assert '"coin":' in result.output

                # From a string
                valid_hex: str = open(valid_hex_path, "r").read()
                result = runner.invoke(cli, ["inspect", "--json", "any", valid_hex])
                assert result.exit_code == 0
                assert '"coin":' in result.output

            # Make sure the ID calculation is correct
            result = runner.invoke(cli, ["inspect", "--id", "any", str(valid_json_path)])
            assert result.exit_code == 0
            assert metadata_json["id"] in result.output

            # Make sure the bytes encoding is correct
            result = runner.invoke(cli, ["inspect", "--bytes", "any", str(valid_json_path)])
            assert result.exit_code == 0
            assert metadata_json["bytes"] in result.output

            # Make sure the type guessing is correct
            result = runner.invoke(cli, ["inspect", "--type", "any", str(valid_json_path)])
            assert result.exit_code == 0
            assert metadata_json["type"] in result.output

    def test_coins(self):
        pid: str = "0x0000000000000000000000000000000000000000000000000000000000000000"
        ph: str = "0000000000000000000000000000000000000000000000000000000000000000"
        amount: str = "0"
        id: str = "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"

        runner = CliRunner()
        result: Result = runner.invoke(cli, ["inspect", "--id", "coins", "-pid", pid, "-ph", ph, "-a", amount])
        assert result.exit_code == 0
        assert id in result.output

    def test_spends(self):
        coin_path = Path(__file__).parent.joinpath("object_files/coins/coin.json")
        pid: str = "0x0000000000000000000000000000000000000000000000000000000000000000"
        ph: str = "0000000000000000000000000000000000000000000000000000000000000000"
        amount: str = "0"
        id: str = "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"
        puzzle_reveal: str = "01"
        solution: str = "80"
        cost: str = "569056"
        modified_cost: str = "557056"

        runner = CliRunner()

        # Specify the coin file
        result: Result = runner.invoke(
            cli,
            [
                "inspect",
                "--id",
                "spends",
                "-c",
                str(coin_path),
                "-pr",
                puzzle_reveal,
                "-s",
                solution,
            ],
        )
        assert result.exit_code == 0
        assert id in result.output

        # Specify all of the arguments
        base_command: List[str] = [
            "inspect",
            "--id",
            "spends",
            "-pid",
            pid,
            "-ph",
            ph,
            "-a",
            amount,
            "-pr",
            puzzle_reveal,
            "-s",
            solution,
        ]
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert id in result.output

        # Ask for the cost
        base_command.append("-ec")
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert id in result.output
        assert cost in result.output

        # Change the cost per byte
        base_command.append("--ignore-byte-cost")
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert id in result.output
        assert modified_cost in result.output

    def test_spendbundles(self):
        spend_path = Path(__file__).parent.joinpath("object_files/spends/spend.json")
        spend_path_2 = Path(__file__).parent.joinpath("object_files/spends/spend_2.json")
        pubkey: str = "80df54b2a616f5c79baaed254134ae5dfc6e24e2d8e1165b251601ceb67b1886db50aacf946eb20f00adc303e7534dd0"
        signable_data: str = "24f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4bccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb"  # noqa
        agg_sig: str = "b83fe374efbc5776735df7cbfb7e27ede5079b41cd282091450e4de21c4b772e254ce906508834b0c2dcd3d58c47a96914c782f0baf8eaff7ece3b070d2035cd878f744deadcd6c6625c1d0a1b418437ee3f25c2df08ffe08bdfe06b8a83b514"  # noqa
        id_no_sig: str = "3fc441c1048a4e0b9fd1648d7647fdebd220cf7dd51b6967dcaf76f7043e83d6"
        id_with_sig: str = "7d6f0da915deed117ad5589aa8bd6bf99beb69f48724b14b2134f6f8af6d8afc"
        network_modifier: str = "testnet7"
        modified_signable_data: str = "24f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b117816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015af"  # noqa
        cost: str = "6692283"
        modified_cost: str = "6668283"

        runner = CliRunner()

        # Build with only the spends
        result: Result = runner.invoke(
            cli,
            [
                "inspect",
                "--id",
                "spendbundles",
                "-s",
                str(spend_path),
                "-s",
                str(spend_path_2),
            ],
        )
        assert result.exit_code == 0
        assert id_no_sig in result.output

        # Build with the aggsig as well
        base_command: List[str] = [
            "inspect",
            "--id",
            "spendbundles",
            "-s",
            str(spend_path),
            "-s",
            str(spend_path_2),
            "-as",
            agg_sig,
        ]
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert id_with_sig in result.output

        # Test that debugging info comes up
        base_command.append("-db")
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert "Debugging Information" in result.output

        # Make sure our signable data comes out
        base_command.append("-sd")
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert pubkey in result.output
        assert result.output.count(signable_data) == 2

        # Try a different network for different signable data
        base_command.append("-n")
        base_command.append(network_modifier)
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert modified_signable_data in result.output

        # Output the execution cost
        base_command.append("-ec")
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert cost in result.output

        # Try a new cost per bytes
        base_command.append("--ignore-byte-cost")
        result = runner.invoke(cli, base_command)
        assert result.exit_code == 0
        assert modified_cost in result.output

        # Try to use it the programmatic way (like cdv rpc pushtx does)
        from cdv.cmds.chia_inspect import do_inspect_spend_bundle_cmd
        from cdv.cmds.util import fake_context

        bundle_path = Path(__file__).parent.joinpath("object_files/spendbundles/spendbundle.json")
        assert len(do_inspect_spend_bundle_cmd(fake_context(), [str(bundle_path)], print_results=False)) > 0

    def test_coinrecords(self):
        coin_path = Path(__file__).parent.joinpath("object_files/coins/coin.json")
        pid: str = "0x0000000000000000000000000000000000000000000000000000000000000000"
        ph: str = "0000000000000000000000000000000000000000000000000000000000000000"
        amount: str = "0"
        id: str = "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"
        coinbase: str = "False"
        confirmed_block_index: str = "500"
        spent_block_index: str = "501"
        timestamp: str = "909469800"

        runner = CliRunner()

        # Try to load it from a file
        record_path = Path(__file__).parent.joinpath("object_files/coinrecords/coinrecord.json")
        result: Result = runner.invoke(cli, ["inspect", "coinrecords", str(record_path)])
        assert result.exit_code == 0
        assert '"coin":' in result.output

        # Specify the coin file
        result = runner.invoke(
            cli,
            [
                "inspect",
                "--id",
                "coinrecords",
                "-c",
                str(coin_path),
                "-cb",
                coinbase,
                "-ci",
                confirmed_block_index,
                "-si",
                spent_block_index,
                "-t",
                timestamp,
            ],
        )
        assert result.exit_code == 0
        assert id in result.output

        # Specify all of the arguments
        result = runner.invoke(
            cli,
            [
                "inspect",
                "--id",
                "coinrecords",
                "-pid",
                pid,
                "-ph",
                ph,
                "-a",
                amount,
                "-cb",
                coinbase,
                "-ci",
                confirmed_block_index,
                "-si",
                spent_block_index,
                "-t",
                timestamp,
            ],
        )
        assert result.exit_code == 0
        assert id in result.output

    def test_programs(self):
        program: str = "ff0101"
        id: str = "69ae360134b1fae04326e5546f25dc794a19192a1f22a44a46d038e7f0d1ecbb"

        runner = CliRunner()

        result: Result = runner.invoke(cli, ["inspect", "--id", "programs", program])
        assert result.exit_code == 0
        assert id in result.output

    def test_keys(self):
        mnemonic: str = "chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia chia"  # noqa
        sk: str = "6e66bc91c04127f1178cb725faf82b55214b5f215b10bd53bf2eb1ee9e76d2cc"
        pk: str = "8777e5c4f5b21840eb69d9cc09433655b044a8e80048cd9583bab9a90f4774d2d57c1b19196ae132c9502db5902f3e5d"
        hd_modifier: str = "m/12381/8444/0/0"
        type_modifier: str = "farmer"  # Should be same as above HD Path
        farmer_sk: str = "1d4a4c2f2059ac889fe066d1116a0fe5dc3d6aa016645306698968c78e8df916"
        synthetic_sk: str = "6963ce75cd541a0b49cc6feb15185675beb513bd5808e744d475ec50cf75b2c0"
        ph_modifier: str = "69ae360134b1fae04326e5546f25dc794a19192a1f22a44a46d038e7f0d1ecbb"
        modified_synthetic_sk: str = "2dc18bb076849fce06d9c36e59821ff43e8b81068d33fcc5ada3568fb9dcdb7f"

        runner = CliRunner()

        # Build the key from secret key
        result: Result = runner.invoke(cli, ["inspect", "keys", "-sk", sk])
        assert result.exit_code == 0
        assert sk in result.output
        assert pk in result.output
        key_output: str = result.output

        # Build the key from mnemonic
        result = runner.invoke(cli, ["inspect", "keys", "-m", mnemonic])
        assert result.exit_code == 0
        assert result.output == key_output

        # Use only the public key
        result = runner.invoke(cli, ["inspect", "keys", "-pk", pk])
        assert result.exit_code == 0
        assert result.output in key_output

        # Generate a random one
        result = runner.invoke(cli, ["inspect", "keys", "--random"])
        assert result.exit_code == 0
        assert "Secret Key" in result.output
        assert "Public Key" in result.output

        # Check the HD derivation is working
        result = runner.invoke(cli, ["inspect", "keys", "-sk", sk, "-hd", hd_modifier])
        assert result.exit_code == 0
        assert farmer_sk in result.output

        # Check the type derivation is working
        result = runner.invoke(cli, ["inspect", "keys", "-sk", sk, "-t", type_modifier])
        assert result.exit_code == 0
        assert farmer_sk in result.output

        # Check that synthetic calculation is working
        result = runner.invoke(cli, ["inspect", "keys", "-sk", sk, "-sy"])
        assert result.exit_code == 0
        assert synthetic_sk in result.output

        # Check that using a non default puzzle hash is working
        result = runner.invoke(cli, ["inspect", "keys", "-sk", sk, "-sy", "-ph", ph_modifier])
        assert result.exit_code == 0
        assert modified_synthetic_sk in result.output

    def test_signatures(self):
        secret_key_1: str = "70432627e84c13c1a6e6007bf6d9a7a0342018fdef7fc911757aad5a6929d20a"
        secret_key_2: str = "0f01f7f68935f8594548bca3892fec419c6b2aa7cff54c3353a2e9b1011f09c7"
        text_message: str = "cafe food"
        bytes_message: str = "0xcafef00d"
        extra_signature: str = "b5d4e653ec9a737d19abe9af7050d37b0f464f9570ec66a8457fbdabdceb50a77c6610eb442ed1e4ace39d9ecc6d40560de239c1c8f7a115e052438385d594be7394df9287cf30c3254d39f0ae21daefc38d3d07ba3e373628bf8ed73f074a80"  # noqa
        final_signature: str = "b7a6ab2c825068eb40298acab665f95c13779e828d900b8056215b54e47d8b8314e8b61fbb9c98a23ef8a134155a35b109ba284bd5f1f90f96e0d41427132b3ca6a83faae0806daa632ee6b1602a0b4bad92f2743fdeb452822f0599dfa147c0"  # noqa

        runner = CliRunner()

        # Test that an empty command returns an empty signature
        result = runner.invoke(cli, ["inspect", "signatures"])
        assert result.exit_code == 0
        assert EMPTY_SIG in result.output

        # Test a complex signature calculation
        result = runner.invoke(
            cli,
            [
                "inspect",
                "signatures",
                "-sk",
                secret_key_1,
                "-t",
                text_message,
                "-sk",
                secret_key_2,
                "-b",
                bytes_message,
                "-sig",
                extra_signature,
            ],
        )
        assert result.exit_code == 0
        assert final_signature in result.output