# -*- coding: utf-8 -*-

from ambry.metadata.schema import Top

from ambry.orm import Config

from test.factories import DatasetFactory
from test.proto import TestBase
import unittest

class DatabaseConfigUpdateTest(TestBase):
    """ Tests db update after property tree change. """
    def setUp(self):
        super(DatabaseConfigUpdateTest, self).setUp()
        self.db = self.library().database
        DatasetFactory._meta.sqlalchemy_session = self.db.session
        self.dataset = DatasetFactory()

    def test_create_group_and_config_in_the_db(self):
        """ Setting empty property tree key creates group and config in the db. """
        db = self.db
        dataset = self.dataset

        top = Top()

        # bind tree to the database
        top.link_config(db.session, dataset)

        # change some fields
        top.names.vid = dataset.vid
        top.about.access = 'public'

        # testing
        query = db.session.query(Config)
        top_config = query.filter_by(d_vid=dataset.vid, type='metadata', parent=None).first()

        # top config does not have key or value
        self.assertIsNone(top_config.key, '')
        self.assertEqual(top_config.value, {})

        #
        # names and about groups does not have values
        #
        names_group = query.filter_by(d_vid=dataset.vid, type='metadata', group='names').first()
        self.assertEqual(names_group.key, 'names')
        self.assertEqual(names_group.value, {})
        self.assertEqual(names_group.parent, top_config)

        about_group = query.filter_by(d_vid=dataset.vid, type='metadata', group='about').first()
        self.assertEqual(about_group.key, 'about')
        self.assertEqual(about_group.value, {})
        self.assertEqual(about_group.parent, top_config)

        #
        # Configs have proper parents and values.
        #
        vid_config = query.filter_by(d_vid=dataset.vid, type='metadata', key='vid').first()
        self.assertEqual(vid_config.key, 'vid')
        self.assertEqual(vid_config.value, dataset.vid)
        self.assertEqual(vid_config.parent, names_group)

    @unittest.skip('Broken')
    def test_change_database_values(self):
        """ Changing existing property tree key value changes config instance. """
        db = self.db
        dataset = self.dataset

        top = Top()

        # bind tree to the database
        top.link_config(db.session, dataset)

        # change some fields
        top.names.vid = dataset.vid

        # testing
        query = db.session.query(Config)
        vid_config = query.filter_by(d_vid=dataset.vid, type='metadata', key='vid').first()
        self.assertEqual(vid_config.value, dataset.vid)

        top.names.vid = 'vid-1'
        vid_config = query.filter_by(type='metadata', key='vid').first()
        self.assertEqual(vid_config.value, 'vid-1')

class BuildPropertyTreeFromDatabaseTest(TestBase):

    def setUp(self):
        super(self.__class__, self).setUp()
        self.db = self.library().database
        DatasetFactory._meta.sqlalchemy_session = self.db.session
        self.dataset = DatasetFactory()

    # helpers
    def _create_db_tree(self):
        """ Creates tree in the database.

            .names.vid = dataset.vid
        """
        db = self.db
        dataset = self.dataset
        top_config = Config(d_vid=dataset.vid, parent=None, type='metadata', sequence_id=1)
        db.session.add(top_config)
        db.session.commit()
        names_config = Config(
            d_vid=dataset.vid, key='names', group='names',
            parent=top_config, type='metadata', sequence_id=2)
        db.session.add(names_config)
        db.session.commit()

        vid_value_config = Config(
            d_vid=dataset.vid, key='vid', value=dataset.vid,
            parent=names_config, type='metadata', sequence_id=3)
        db.session.add(vid_value_config)
        db.session.commit()

    # tests
    def test_build_tree_from_database(self):
        """ Populates property tree with values from database. """

        # create appropriate tree in the database
        self._create_db_tree()

        # build from db
        top = Top()
        top.build_from_db(self.dataset)
        self.assertEqual(top.names.vid, self.dataset.vid)

    @unittest.skip("broken")
    def test_change_tree_build_from_database(self):

        # create appropriate tree in the database
        self._create_db_tree()

        # build from db
        top = Top()
        top.build_from_db(self.dataset)

        # change and test
        assert top.is_bound()
        vid_value_config = self.db.session.query(Config).filter_by(key='vid', type='metadata').one()
        self.assertNotEqual(vid_value_config.value, 'vid-2')
        top.names.vid = 'vid-2'
        vid_value_config = self.db.session.query(Config).filter_by(key='vid', type='metadata').one()
        self.assertEqual(vid_value_config.value, 'vid-2')


from __future__ import (nested_scopes, generators, division, absolute_import, with_statement,
                        print_function, unicode_literals)

from pants_test.pants_run_integration_test import PantsRunIntegrationTest

class AntlrIntegrationTest(PantsRunIntegrationTest):

  def test_run_antlr3(self):
    stdout_data = self.bundle_and_run('examples/src/java/com/pants/examples/antlr3', 'antlr3', args=['7*8'])
    self.assertEquals('56.0', stdout_data.rstrip(), msg="got output:{0}".format(stdout_data))

  def test_run_antlr4(self):
    stdout_data = self.bundle_and_run('examples/src/java/com/pants/examples/antlr4', 'antlr4', args=['7*8'])
    self.assertEquals('56.0', stdout_data.rstrip(), msg="got output:{0}".format(stdout_data))


#!/usr/bin/env python3

from ttproto.ts_coap.proto_specific import CoAPTestcase
from ttproto.ts_coap.proto_templates import *

class TD_COAP_CORE_01 (CoAPTestcase):
    """Identifier:
TD_COAP_CORE_01
Objective:
Perform GET transaction (CON mode)
Configuration:
CoAP_CFG_BASIC
References:
[COAP] 5.8.1,1.2,2.1,2.2,3.1

Pre-test
conditions:
•	Server offers the resource /test with resource content is not empty that handles GET with an arbitrary payload

Test Sequence:
Step
Type
Description

1
Stimulus
Client is requested to send a GET request with:
•	Type = 0(CON)
•	Code = 1(GET)

2
Check
The request sent by the client contains:
•	Type=0 and Code=1
•	Client-generated Message ID (➔ CMID)
•	Client-generated Token (➔ CTOK)
•	Uri-Path option "test"

3
Check
Server sends response containing:
•	Code = 69(2.05 Content)
•	Message ID = CMID, Token = CTOK
•	Content-format option
•	Non-empty Paload

4
Verify
Client displays the received information
    """

    def run (self):
        self.match_coap ("client", CoAP (type="con", code="get",
                        opt = self.uri ("/test")))
        CMID = self.frame.coap["mid"]
        CTOK = self.frame.coap["tok"]

        self.next()

        if self.match_coap ("server", CoAP (
                        code = 2.05,
                        mid = CMID,
                        tok =CTOK,
                        pl = Not(b""),
                    )):
            self.match_coap ("server", CoAP (
                        opt = Opt (CoAPOptionContentFormat()),
                    ), "fail")


from collections import OrderedDict as OD
from numpy import exp, log as ln
from styles import mark_styles

def KWW(t, tau, beta):
    "Kohlrausch-Williams-Watts compressed (beta>1) exponential function."
    return 1 - exp(-(t/tau)**beta)
KWW.title = 'Non-normalized Kohlrausch-Williams-Watts compressed (beta>1) exponential function'
KWW.title_lowercase = 'non-normalized Kohlrausch-Williams-Watts compressed (beta>1) exponential function'
KWW.equation = '1 - exp(-(t/tau)^beta)'

def KWW_inverse(KWW_output, tau, beta):
    return tau * (-ln(1 - KWW_output))**(1/beta)
def KWW_prime(t, tau, beta):
    return (beta/tau) * (t/tau)**(beta-1) * exp(-(t/tau)**beta)
def KWW_doubleprime(t, tau, beta):
    return -(beta/tau**2) * (t/tau)**(beta-2) * exp(-(t/tau)**beta) * (1 + beta*((t/tau)**beta - 1))
def KWW_inflection(tau, beta):
    "Returns inflection time."
    return tau * ((beta-1)/beta)**(1/beta)
def KWW_lagtime(tau, beta, t_inflection, leadtime = False):
    _KWW = KWW(t_inflection, tau, beta)
    _KWW_prime = KWW_prime(t_inflection, tau, beta)
    return ((leadtime - _KWW)/_KWW_prime) + t_inflection

def KWW_scaled(t, tau, slowbeta, scale, t0):
    fastbeta = slowbeta**4
    _KWW = lambda t: KWW(t, tau, fastbeta)
    return ( scale * _KWW((t-t0)/scale) )
KWW_scaled.title = 'Scaled KWW'
KWW_scaled.title_lowercase = 'scaled KWW'
KWW_scaled.equation = '( scale * KWW((t-t0)/scale, tau, slowbeta^4) )'
KWW_scaled.marks = {'t_5%', 't_10%', 't_90%', 't_95%', 'lagtime'}
KWW_scaled.styles = mark_styles

def KWW_descaler(tau, slowbeta, scale = None, t0 = None):
    "Converts KWW_scaled parameters to KWW parameters."
    fastbeta = slowbeta**4
    return tau, fastbeta
def KWW_timeinfo(tau, beta, t_inflection):
    t_percent = lambda percent: KWW_inverse(percent, tau, beta)
    return OD({
        't_5%': t_percent(0.05),
        't_10%': t_percent(0.1),
        't_90%': t_percent(0.9),
        't_95%': t_percent(0.95),
        'lagtime': KWW_lagtime(tau, beta, t_inflection) })
def KWW_timescaler(t, scale, t0):
    "Takes a time on an unscaled KWW curve and returns the corresponding time on the scaled curve."
    return scale * t + t0

if __name__ == '__main__':
    scaled_args = { 'tau': 1.2, 'slowbeta': 1.2, 'scale': 2.9, 't0': 0.4 }
    descaled_args = KWW_descaler(**scaled_args)

    t_inflection = KWW_inflection(*descaled_args)
    rate_max = KWW_prime(t_inflection, *descaled_args)

    print(*(f"{value['title']}: {value['value']}" for key, value in KWW_timeinfo(*descaled_args, t_inflection).items()), sep = '\n')


#!/usr/local/bin/ python3
# -*- coding:utf-8 -*-
# __author__ = "zenmeder"

# Definition for a binary tree node.
class TreeNode(object):
	def __init__(self, x):
		self.val = x
		self.left = None
		self.right = None

class Solution(object):
	def zigzagLevelOrder(self, root):
		"""
		:type root: TreeNode
		:rtype: List[List[int]]
		"""
		orientation = 1 #left-to-right
		nodes = [root]
		res = []
		if not root:
			return res
		while nodes:
			res.append([n.val for n in nodes])
			copy = []
			if not orientation:
				for node in nodes[::-1]:
					if node.left:
						copy.append(node.left)
					if node.right:
						copy.append(node.right)
			else:
				for node in nodes[::-1]:
					if node.right:
						copy.append(node.right)
					if node.left:
						copy.append(node.left)
			orientation = 1 - orientation
			nodes = copy[:]

		return res

a = TreeNode(3)
b = TreeNode(9)
c = TreeNode(20)
d = TreeNode(15)
e = TreeNode(7)
a.left = b
a.right = c
c.left = d
c.right = e

print(Solution().zigzagLevelOrder(a))


from typing import MutableSequence
def fsort(a: MutableSequence, max:int)->None:
    # 원소의값은 0이상 max이하
    n = len(a)
    f = [0]*(max+1)
    b = [0]*n
    # 도수 분포표  : 해당 값을 인덱스로 가지는 배열을 만들어서 count하면 해당 값이 몇번? 나왔는지 알수있음
    for i in range(n): f[a[i]] += 1 # 1 step
    # 누적 도수 분포표 : 0 ~ n까지 몇개의 데이터가 있는지 누적된 값을 나타냄
    for i in range(1,max+1) : f[i] += f[i-1]
    # 작업용 배열 : 원래배열의 원소값과 누적도수 분포표를 대조해서 정렬을 완료한 배열
    for i in range(n-1, -1, -1) : f[a[i]] -=1 ; b[f[a[i]]] = a[i]
    for i in range(n) : a[i] = b[i]

def countingSort(a:MutableSequence)->None:
    fsort(a, max(a))

def main()->None:
    from random import sample
    data = sample(range(1000),10)
    print(f"original : {data}")
    countingSort(data)
    print(f"sorted : {data}")

if __name__=="__main__":
    main()


import curses

##########################################################################
## Este código 'movimenta' o X no terminal ao pressionar as teclas W,S,A,D
##########################################################################

def main(stdscr):
    # Configurações iniciais do terminal
    stdscr.clear()
    curses.curs_set(0)  # Desativa o cursor

    # Tamanho da tela
    height, width = stdscr.getmaxyx()

    # Posição inicial do jogador
    player_y, player_x = height // 2, width // 2

    # Loop principal do jogo
    while True:
        stdscr.nodelay(True)

        # Obtém o input do usuário (não bloqueante)
        key = stdscr.getch()

        stdscr.addch(player_y, player_x, ' ')

        # Atualiza a posição do jogador com base na tecla pressionada
        if key == ord('w'):
            player_y -= 1
        elif key == ord('s'):
            player_y += 1
        elif key == ord('a'):
            player_x -= 1
        elif key == ord('d'):
            player_x += 1

        # Limita a posição do jogador dentro dos limites da tela
        player_y = max(0, min(player_y, height - 1))
        player_x = max(0, min(player_x, width - 1))

        # Desenha o jogador na tela
        stdscr.addch(player_y, player_x, 'X')

        # Atualiza a tela
        stdscr.refresh()

if __name__ == "__main__":
    curses.wrapper(main)


from django.db import models, connection
from django.db.models import Q, Max, Case, Value, When, Exists, OuterRef, \
    UniqueConstraint, Subquery
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.core.exceptions import FieldError, ValidationError
from django.template import Template, loader
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.urls import reverse
from polymorphic.models import PolymorphicModel
from polymorphic.managers import PolymorphicManager
import uuid
from itertools import groupby
from pathlib import Path
from datetime import timedelta
import os

from ..stock import StockWidget
from ..filetype import FileType
from ..utils import create_model, remove_p, send_email, submission_link, \
    thumbnail_path, MarkdownFormatter
from .ranked import RankedModel, UnderscoredRankedModel
from .automatic import AutoSlugModel

markdown = MarkdownFormatter()

class ProgramManager(models.Manager):
    def get_by_natural_key(self, slug):
        return self.get(slug=slug)

class Program(AutoSlugModel):
    class Meta:
        ordering = ['created']

    sites = models.ManyToManyField('Site', blank=True, related_name='programs',
                                   related_query_name='program')
    name = models.CharField(max_length=64)
    slug = models.SlugField(max_length=30, unique=True, allow_unicode=True,
                            verbose_name='identifier')
    db_slug = models.SlugField(max_length=30, unique=True, allow_unicode=True,
                               editable=False)
    description = models.CharField(max_length=250, blank=True)
    options = models.JSONField(default=dict, blank=True)
    hidden = models.BooleanField(default=False)
    created = models.DateTimeField(auto_now_add=True)

    objects = ProgramManager()

    def __str__(self):
        return self.name

    def natural_key(self):
        return (self.slug,)

    def validate_unique(self, exclude=None):
        super().validate_unique(exclude=exclude)
        if not self._state.adding: return

        if Program.objects.filter(db_slug=self.slug.replace('-', '')).exists():
            msg = 'Identifier (with hyphens removed) must be unique.'
            raise ValidationError(msg)

    def visible_forms(self):
        pub = self.forms.exclude(status=Form.Status.DRAFT)
        return pub.exclude(options__hidden__isnull=False)

    def home_url(self):
        if 'home_url' in self.options: return self.options['home_url']
        return None

class FormManager(models.Manager):
    def get_by_natural_key(self, program_slug, slug):
        return self.get(program__slug=program_slug, slug=slug)

class Form(AutoSlugModel):
    class Meta:
        constraints = [
            UniqueConstraint(fields=['program', 'slug'], name='unique_slug'),
            UniqueConstraint(fields=['program', 'db_slug'],
                             name='unique_db_slug')
        ]

    class Status(models.TextChoices):
        DRAFT = 'draft', _('unpublished')
        DISABLED = 'disabled', _('submissions disabled')
        ENABLED = 'enabled', _('published/enabled')
        COMPLETED = 'completed', _('completed')

    class Validation(models.TextChoices):
        # currently, validation to create a submission is always by email
        EMAIL = 'email', _('email address')

    program = models.ForeignKey(Program, models.CASCADE,
                                related_name='forms', related_query_name='form')
    name = models.CharField(max_length=64)
    slug = models.SlugField(max_length=30, allow_unicode=True,
                            verbose_name='identifier')
    db_slug = models.SlugField(max_length=30, allow_unicode=True,
                               editable=False)
    status = models.CharField(max_length=16, default=Status.DRAFT,
                              choices=Status.choices)
    options = models.JSONField(default=dict, blank=True)
    created = models.DateTimeField(auto_now_add=True)
    # this is also the published date, for enabled and completed forms
    modified = models.DateTimeField(default=timezone.now, editable=False)
    completed = models.DateTimeField(null=True, blank=True, editable=False)
    validation_type = models.CharField(max_length=16, editable=False,
                                       default=Validation.EMAIL,
                                       choices=Validation.choices)
    objects = FormManager()

    def __str__(self):
        return self.name

    def natural_key(self):
        return self.program.natural_key() + (self.slug,)
    natural_key.dependencies = ['formative.program']

    def validate_unique(self, exclude=None):
        super().validate_unique(exclude=exclude)
        if not self._state.adding: return

        if Form.objects.filter(program=self.program,
                               db_slug=self.slug.replace('-', '')).exists():
            msg = 'Identifier (with hyphens removed) must be unique ' \
                  'within this program.'
            raise ValidationError(msg)

    @cached_property
    def model(self):
        if self.status == self.Status.DRAFT: return None

        fields = []
        for block in self.blocks.exclude(page=0, _rank__gt=1):
            fields += block.fields()

        name = self.program.db_slug + '_' + self.db_slug
        class Meta:
            verbose_name = self.slug + ' submission'
            verbose_name_plural = self.slug + ' submissions'
        return create_model(name, fields, program=self.program.db_slug,
                            base_class=Submission, meta=Meta)

    @cached_property
    def item_model(self):
        if self.status == self.Status.DRAFT: return None

        collections = self.collections()
        if not collections: return None

        names = []
        for c in collections:
            for field in c.collection_fields():
                if field not in names: names.append(field)

        fields = [
            # the first column links submission items to the submission
            ('_submission', models.ForeignKey(self.model, models.CASCADE,
                                              related_name='_items',
                                              related_query_name='_item'))
        ]

        field_blocks = { b.name: b for b in self.collection_field_blocks() }
        for n in names:
            # look for a CustomBlock with the same name on page 0 (hidden)
            if n in field_blocks: block = field_blocks[n]
            # otherwise, use the default text CustomBlock
            else: block = CustomBlock.text_create()
            fields.append((n, block.field()))

        name = self.program.db_slug + '_' + self.db_slug + '_i'
        class Meta:
            constraints = [
                UniqueConstraint(
                    fields=['_submission', '_collection', '_block', '_rank'],
                    name=self.program.db_slug+'_'+self.db_slug+'_u'
                )
            ]
            verbose_name = self.slug + ' item'
            verbose_name_plural = self.slug + ' items'
        return create_model(name, fields, program=self.program.db_slug,
                            base_class=SubmissionItem, meta=Meta)

    def cache_dirty(self):
        version = cache.get('models_version')
        if version is None: cache.set('models_version', 1, timeout=None)
        else: cache.incr('models_version')

    def publish_model(self, model, admin=None):
        from ..signals import all_forms_publish

        with connection.schema_editor() as editor:
            editor.create_model(model)
        ctype = ContentType(app_label=model._meta.app_label,
                            model=model.__name__)
        ctype.save()
        ContentType.objects.clear_cache()

        all_forms_publish.send(self, content_type=ctype)
        self.cache_dirty()

    def unpublish_model(self, model):
        from ..signals import all_forms_unpublish

        self.cache_dirty()
        ctype = ContentType.objects.get_for_model(model)
        all_forms_unpublish.send(self, content_type=ctype)

        ctype.delete()
        ContentType.objects.clear_cache()
        with connection.schema_editor() as editor:
            editor.delete_model(model)

    def publish(self):
        if self.status != self.Status.DRAFT: return

        self.status = self.Status.ENABLED
        if 'model' in self.__dict__: del self.model
        if 'item_model' in self.__dict__: del self.item_model

        from ..admin import SubmissionAdmin, SubmissionItemAdmin
        self.publish_model(self.model, admin=SubmissionAdmin)
        if self.item_model:
            self.publish_model(self.item_model, admin=SubmissionItemAdmin)

        self.modified = timezone.now()
        self.save()

    def unpublish(self):
        if self.status == self.Status.DRAFT: return

        rec_type = SubmissionRecord.RecordType.SUBMISSION
        recs = SubmissionRecord.objects.filter(program=self.program,
                                               form=self.slug, type=rec_type)
        recs.update(deleted=True)

        self.unpublish_model(self.model)
        if self.item_model: self.unpublish_model(self.item_model)

        self.status = self.Status.DRAFT
        if 'model' in self.__dict__: del self.model
        if 'item_model' in self.__dict__: del self.item_model

        self.modified, self.completed = timezone.now(), None
        self.save()

    def get_available_plugins(self):
        from ..plugins import get_available_plugins
        return get_available_plugins(self)

    def get_plugins(self):
        if 'plugins' in self.options: return self.options['plugins']
        return []

    def add_plugins(self, plugins):
        available = self.get_available_plugins()

        enabled = self.get_plugins()
        enable = [ p for p in plugins if p in available and p not in enabled ]
        for plugin in enable:
            if hasattr(available[plugin].app, 'installed'):
                getattr(available[plugin].app, 'installed')(self)

        if 'plugins' not in self.options: self.options['plugins'] = []
        self.options['plugins'] += plugins

    def remove_plugins(self, plugins):
        available = self.get_available_plugins()

        enabled = self.get_plugins()
        for plugin in [ p for p in plugins if p in enabled ]:
            if hasattr(available[plugin].app, 'uninstalled'):
                getattr(available[plugin].app, 'uninstalled')(self)

        new_plugins = [ p for p in enabled if p not in plugins ]
        self.options['plugins'] = new_plugins

    def default_text_label_style(self):
        if 'default_text_label_style' in self.options:
            return self.options['default_text_label_style']
        return FormLabel.LabelStyle.WIDGET

    def num_pages(self):
        return self.blocks.aggregate(Max('page'))['page__max']

    def custom_blocks(self):
        return CustomBlock.objects.filter(form=self).non_polymorphic()

    def submission_blocks(self):
        blocks = self.blocks.not_instance_of(CollectionBlock)
        return blocks.exclude(page=0, _rank__gt=0)

    def collection_field_blocks(self):
        return self.custom_blocks().filter(page=0, _rank__gt=1)

    def collections(self, name=None):
        blocks = CollectionBlock.objects.filter(form=self)
        if name: return blocks.filter(name=name).non_polymorphic()
        return blocks.non_polymorphic()

    def validation_block(self):
        return self.blocks.get(page=0, _rank=1)

    def visible_blocks(self, page=None, skip=None):
        query = self.blocks.all()
        if skip: query = query.exclude(id__in=skip)
        if page and page > 0: return query.filter(page=page)
        else: return query.exclude(page=0, _rank__gt=0)
        return query.filter(page__gt=0)

    def visible_items(self, submission, page=None, skip=None):
        if not self.item_model: return []

        query = self.item_model.objects.filter(_submission=submission)
        if skip: query = query.exclude(_block__in=skip)
        if page and page > 0:
            block_ids = Subquery(self.blocks.filter(page=page).values('pk'))
            query = query.filter(_block__in=block_ids)

        query = query.exclude(_file='', _filesize__gt=0) # upload in progress
        return query.order_by('_collection', '_block', '_rank')

    def field_labels(self):
        labels = {}
        for label in self.labels.all():
            key, target = label.path, labels
            if '.' in label.path:
                base, key = label.path.split('.')
                if key[-1] == '_': base, key = base + '_', key[:-1]

                if base not in labels: labels[base] = {}
                target = labels[base]
            if key not in target: target[key] = {}
            target[key][label.style] = label

        return labels

    def label_class(self):
        return FormLabel

    def status_message(self):
        if self.status == self.Status.DRAFT:
            return 'NA'
        elif self.status == self.Status.DISABLED:
            if 'disabled_message' in self.options:
                return self.options['disabled_message']
            return _('Not yet open for submissions')
        elif self.status == self.Status.COMPLETED:
            if 'completed_message' in self.options:
                return self.options['completed_message']
            return _('Closed')
        if 'enabled_message' in self.options:
            return self.options['enabled_message']
        return _('Open for submissions')

    def hidden(self):
        return self.status != self.Status.ENABLED or 'hidden' in self.options

    def access_enable(self):
        if 'access_enable' in self.options: return self.options['access_enable']
        return None

    def timed_completion(self):
        if 'timed_completion' in self.options:
            return self.options['timed_completion']
        return None

    def complete_submit_time(self):
        if 'complete_submit_time' in self.options:
            return self.options['complete_submit_time']
        return 5 # minutes

    def extra_time(self):
        extra = self.complete_submit_time()
        if timezone.now() - self.completed <= timedelta(minutes=extra):
            return True
        return False

    def review_pre(self, prefix=''):
        name = prefix + 'review_pre'
        if name in self.options:
            return mark_safe(markdown.convert(self.options[name]))
        return ''

    def review_post(self):
        if 'review_post' in self.options:
            return mark_safe(markdown.convert(self.options['review_post']))
        return ''

    def submit_submission(self, submission):
        submission._submit()

        rec, created = SubmissionRecord.objects.get_or_create(
            program=self.program, form=self.slug, submission=submission._id,
            type=SubmissionRecord.RecordType.SUBMISSION
        )
        rec.text = submission._email
        rec.save()

        if self.item_model:
            dir = os.path.join(settings.MEDIA_ROOT, str(submission._id))
            if os.path.isdir(dir):
                Path(os.path.join(dir, 'submitted')).touch()

    def submitted_review_pre(self):
        return self.review_pre(prefix='submitted_')

    def review_after_submit(self):
        return 'no_review_after_submit' not in self.options

    def submit_button_label(self):
        if 'submit_button_label' in self.options:
            return self.options['submit_button_label']
        return 'submit'

    def thanks(self):
        if 'thanks' in self.options:
            return mark_safe(markdown.convert(self.options['thanks']))

    def emails(self):
        if 'emails' in self.options: return self.options['emails']
        return {}

    def email_names(self):
        names = list(self.emails())
        for name in ('confirmation', 'continue'):
            if name not in names: names.insert(0, name)
        return names

    def load_email_templates(self, n):
        subject = loader.get_template('formative/emails/' + n + '_subject.html')
        content = loader.get_template('formative/emails/' + n + '.html')
        return subject, content

    def email_templates(self):
        emails = self.emails()
        for name in ('continue', 'confirmation'):
            if name in emails: continue
            subject, content = self.load_email_templates(name)

            emails[name] = {'content': content.template.source,
                            'subject': subject.template.source.rstrip('\n')}
        return emails

class FormLabelManager(models.Manager):
    def get_by_natural_key(self, program_slug, form_slug, path, style):
        return self.get(form__program__slug=program_slug, form__slug=form_slug,
                        path=path, style=style)

class FormLabel(models.Model):
    class Meta:
        constraints = [
            UniqueConstraint(fields=['form', 'path', 'style'],
                             name='unique_path_style')
        ]

    class LabelStyle(models.TextChoices):
        WIDGET = 'widget', _('widget label')
        VERTICAL = 'vertical', _('vertical label')
        HORIZONTAL = 'horizontal', _('horizontal label')

    form = models.ForeignKey('Form', models.CASCADE, related_name='labels',
                             related_query_name='label')
    path = models.CharField(max_length=128)
    text = models.CharField(max_length=1000)
    style = models.CharField(max_length=16, choices=LabelStyle.choices,
                             default=LabelStyle.WIDGET)

    objects = FormLabelManager()

    def __str__(self):
        return self.path

    def natural_key(self):
        return self.form.natural_key() + (self.path, self.style)
    natural_key.dependencies = ['formative.form']

    def display(self, inline=False):
        s = markdown.convert(self.text)
        if inline: return mark_safe(remove_p(s))
        return mark_safe(s)

    def display_inline(self):
        return self.display(inline=True)

class FormDependencyManager(models.Manager):
    def get_by_natural_key(self, program_slug, form_slug, name, value):
        return self.get(block__form__program__slug=program_slug,
                        block__form__slug=form_slug,
                        block__name=name, value=value)

class FormDependency(models.Model):
    class Meta:
        verbose_name = 'dependency value'
        verbose_name_plural = 'dependency values'
        constraints = [
            UniqueConstraint(fields=['block', 'value'], name='unique_blockval')
        ]

    block = models.ForeignKey('FormBlock', models.CASCADE,
                              related_name='dependencies',
                              related_query_name='dependency')
    value = models.CharField(max_length=64, blank=True)

    objects = FormDependencyManager()

    def __str__(self):
        if self.block.dependence:
            return f'{self.block.dependence.name}="{self.value}"'
        return f'?="{self.value}"'

    def natural_key(self):
        return self.block.natural_key() + (self.value,)
    natural_key.dependencies = ['formative.formblock', 'formative.customblock',
                                'formative.collectionblock']

class FormBlockManager(PolymorphicManager):
    def get_by_natural_key(self, program_slug, form_slug, name):
        return self.non_polymorphic().get(form__program__slug=program_slug,
                                          form__slug=form_slug, name=name)

class FormBlock(PolymorphicModel, RankedModel):
    class Meta(PolymorphicModel.Meta, RankedModel.Meta):
        constraints = [
            UniqueConstraint(fields=['form', 'page', '_rank'],
                             name='unique_rank'),
        ]
        ordering = ['form', 'page', '_rank']

    form = models.ForeignKey(Form, models.CASCADE,
                             related_name='blocks', related_query_name='block')
    name = models.SlugField(max_length=32, verbose_name='identifier',
                            allow_unicode=True)
    options = models.JSONField(default=dict, blank=True)
    page = models.PositiveIntegerField(default=1)
    dependence = models.ForeignKey('FormBlock', models.CASCADE,
                                   null=True, blank=True,
                                   related_name='dependents',
                                   related_query_name='dependent')
    negate_dependencies = models.BooleanField(default=False,
                                              verbose_name='negate dependency')
    objects = FormBlockManager()

    def __str__(self):
        return self.name

    def natural_key(self):
        return self.form.natural_key() + (self.name,)
    natural_key.dependencies = ['formative.form']

    def rank_group(self):
        return FormBlock.objects.filter(form=self.form, page=self.page)

    def validate_unique(self, exclude=None):
        super().validate_unique(exclude=exclude)
        if not self._state.adding: return

        if self.name == 'email' and self.page:
            raise ValidationError('There is already a block called "email."')

        # name of a collection block identifies its "bucket", not its field(s)
        # in this case, it's not required to be unique
        if self.block_type() == 'collection': return

        qs = FormBlock.objects.filter(form_id=self.form_id)
        if self.page: qs = qs.filter(page__gt=0)
        else: qs = qs.filter(page=0)
        if qs.filter(name=self.name).exists():
            msg = 'Identifiers for stock and custom blocks must be unique.'
            raise ValidationError(msg)

    def block_type(self):
        if type(self) == CustomBlock: return 'custom'
        if type(self) == CollectionBlock: return 'collection'
        return 'stock'

    def stock_type(self):
        if 'type' not in self.options:
            raise FieldError('untyped stock widget')

        return StockWidget.by_type(self.options['type'])

    @cached_property
    def stock(self):
        return self.stock_type()(self.name, **self.options)

    def fields(self):
        return self.stock.fields()

    def enabled_blocks(self, value, page=None):
        # blocks on the given page that depend on self, and enabled given value
        query = self.form.blocks.filter(dependence_id=self.id)
        if page: query = query.filter(page=page)

        if type(value) == bool: value = value and 'yes' or 'no' # TODO: numeric
        if value is None: value = ''

        val = FormDependency.objects.filter(block_id=OuterRef('id'),
                                            value=Value(value))
        cond = Case(
            When(negate_dependencies=False, then=Exists(val)),
            When(negate_dependencies=True, then=~Exists(val))
        )
        query = query.annotate(en=cond).filter(en=True)
        return query.values_list('id', flat=True)

    def min_allowed_page(self):
        if not self.page: return 0
        min_page = 1

        if self.dependence: min_page = self.dependence.page + 1
        return min_page

    def max_allowed_page(self, last_page=None):
        if not self.page: return 0
        if last_page is None:
            last_page = self.form.blocks.aggregate(p=Max('page'))['p'] or 1
        max_page = last_page

        for block in self.dependents.all():
            if block.page - 1 < max_page: max_page = block.page - 1
        return max_page

    def show_in_review(self):
        return 'no_review' not in self.options

class CustomBlock(FormBlock):
    class Meta:
        db_table = 'formative_formcustomblock'

    CHOICE_VAL_MAXLEN = 64
    DEFAULT_TEXT_MAXLEN = 1000
    MAX_TEXT_MAXLEN = 65535

    class InputType(models.TextChoices):
        TEXT = 'text', _('text')
        NUMERIC = 'num', _('numeric')
        CHOICE = 'choice', _('multiple choice')
        BOOLEAN = 'bool', _('true/false choice')

    block = models.OneToOneField(FormBlock, on_delete=models.CASCADE,
                                 parent_link=True, primary_key=True)
    type = models.CharField(max_length=16, choices=InputType.choices,
                            default=InputType.TEXT)
    required = models.BooleanField(default=False)
    num_lines = models.PositiveIntegerField(default=1)
    min_chars = models.PositiveIntegerField(null=True, blank=True)
    max_chars = models.PositiveIntegerField(null=True, blank=True)
    min_words = models.PositiveIntegerField(null=True, blank=True)
    max_words = models.PositiveIntegerField(null=True, blank=True)

    @classmethod
    def text_create(cls, *args, **kwargs):
        if 'max_chars' not in kwargs:
            kwargs['max_chars'] = cls.DEFAULT_TEXT_MAXLEN
        return cls(*args, **kwargs, type=cls.InputType.TEXT)

    def choices(self, include_empty=False):
        if include_empty:
            if 'choices' in self.options: return self.options['choices']
            return []
        if 'choices' not in self.options or not self.options['choices']:
            raise FieldError('choices not defined')

        return self.options['choices']

    def field(self):
        # fields are NULL when we haven't yet reached the page, or if their
        # block had a dependency that wasn't met. non-required fields may
        # have a different way to record that no input was made, usually ''
        if self.type == self.InputType.TEXT:
            blank, max_chars = False, self.max_chars

            if not self.min_chars and not self.min_words: blank = True
            if not self.max_chars or self.max_chars > self.MAX_TEXT_MAXLEN:
                max_chars = self.MAX_TEXT_MAXLEN

            if self.num_lines > 1 or max_chars > self.DEFAULT_TEXT_MAXLEN:
                return models.TextField(null=True, max_length=max_chars,
                                        blank=blank)
            return models.CharField(null=True, max_length=self.max_chars,
                                    blank=blank)

        elif self.type == self.InputType.NUMERIC:
            return models.IntegerField(null=True, blank=(not self.required))

        elif self.type == self.InputType.CHOICE:
            return models.CharField(null=True, blank=(not self.required),
                                    max_length=self.CHOICE_VAL_MAXLEN,
                                    choices=[(c, c) for c in self.choices()])

        elif self.type == self.InputType.BOOLEAN:
            return models.BooleanField(null=True)

    def fields(self):
        return [(self.name, self.field())]

    def form_field(self, model_field, **kwargs):
        if self.type == self.InputType.TEXT:
            return model_field.formfield(min_length=self.min_chars, **kwargs)

        elif self.type == self.InputType.NUMERIC:
            return model_field.formfield(min_value=self.numeric_min(),
                                         max_value=self.numeric_max(), **kwargs)

        # or use the ModelForm factory's default:
        return model_field.formfield(**kwargs)

    def clean_field(self, data, field):
        # currently, all are handled from validators set up on the form
        return data

    def numeric_min(self):
        if 'numeric_min' in self.options: return self.options['numeric_min']
        return None

    def numeric_max(self):
        if 'numeric_max' in self.options: return self.options['numeric_max']
        return None

    def default_value(self):
        if self.type == self.InputType.TEXT: return None

        if 'default_value' in self.options: return self.options['default_value']
        return None

    def conditional_value(self, value):
        if self.type in (self.InputType.TEXT, self.InputType.NUMERIC):
            # in this case, condition is whether the field was filled out
            # (and is non-zero for numeric)
            return bool(value)

        return value

    def span(self, media=None):
        width = 6
        if self.max_chars and self.max_chars > 50: width = 8
        if self.num_lines > 1: width = 8
        if self.num_lines > 4: width = 10
        if self.type in (self.InputType.CHOICE, self.InputType.BOOLEAN):
            width = 8
        elif self.type == self.InputType.NUMERIC: width = 2

        if self.type == self.InputType.NUMERIC:
            if 'span_phone' in self.options:
                return min(self.options['span_phone'], 4)

        if 'span_tablet' in self.options:
            if not media: return min(width, self.options['span_tablet'], 4)
        elif not media: return min(width, 4)

        if media == 'tablet' and 'span_tablet' in self.options:
            return self.options['span_tablet']
        if media == 'desktop' and 'span_desktop' in self.options:
            return self.options['span_desktop']

        return width

    def tablet_span(self): return self.span(media='tablet')
    def desktop_span(self): return self.span(media='desktop')

class CollectionBlock(FormBlock):
    class Meta:
        db_table = 'formative_formcollectionblock'

    FIXED_CHOICE_VAL_MAXLEN = 100

    class AlignType(models.TextChoices):
        TABULAR = 'tabular', _('tabular')
        STACKED = 'stacked', _('stacked')

    block = models.OneToOneField(FormBlock, on_delete=models.CASCADE,
                                 parent_link=True, primary_key=True)
    fixed = models.BooleanField(default=False)
    min_items = models.PositiveIntegerField(null=True, blank=True) # null if
    max_items = models.PositiveIntegerField(null=True, blank=True) # fixed
    has_file = models.BooleanField(default=False)
    file_optional = models.BooleanField(default=False)
    # we don't need these references indexed or validated here, so no SlugField
    name1 = models.CharField(max_length=32, default='', blank=True)
    name2 = models.CharField(max_length=32, default='', blank=True)
    name3 = models.CharField(max_length=32, default='', blank=True)
    align_type = models.CharField(max_length=16, choices=AlignType.choices,
                                  default=AlignType.TABULAR)

    def fields(self):
        return []

    def collection_fields(self):
        fields = []
        if self.name1: fields.append(self.name1)
        if self.name2: fields.append(self.name2)
        if self.name3: fields.append(self.name3)

        return fields

    def tabular(self):
        return self.align_type == self.AlignType.TABULAR

    def max_filesize(self):
        if 'max_filesize' in self.options: return self.options['max_filesize']
        return None # TODO: overall default max

    def allowed_filetypes(self):
        if 'file_types' in self.options:
            return self.options['file_types']

        return None # allow any file extension

    def allowed_extensions(self):
        types = self.allowed_filetypes()
        if not types: return None

        extensions = []
        for filetype in types:
            extensions += FileType.by_type(filetype)().allowed_extensions()
        return extensions

    def autoinit_filename(self):
        if 'autoinit_filename' in self.options: return True
        return False

    def fixed_choices(self):
        if 'choices' not in self.options:
            msg = 'choices must be provided for a fixed collection block'
            raise FieldError(msg)

        return self.options['choices']

    def num_choices(self):
        return len(self.fixed_choices())

    def file_limits(self):
        if 'file_limits' in self.options: return self.options['file_limits']
        return {}

    def process_options(self, filetype):
        if 'file_processing' not in self.options: return {}
        if filetype in self.options['file_processing']:
            return self.options['file_processing'][filetype]
        return {}

    def span(self, media=None):
        width = 10
        if media == 'tablet': width = 8

        if not media: return 4

        if media == 'tablet' and 'span_tablet' in self.options:
            return max(4, self.options['span_tablet'])
        if media == 'desktop' and 'span_desktop' in self.options:
            return max(4, self.options['span_desktop'])

        return width

    def tablet_span(self): return self.span(media='tablet')
    def desktop_span(self): return self.span(media='desktop')

    def total_colspan(self):
         fields = self.collection_fields()
         if fields: return len(fields)
         return 1

    def horizontal_width(self, field):
        total = self.total_colspan()
        if 'wide' in self.options:
            total += len(self.options['wide'])
            if field in self.options['wide']: return 200.0 / total
        return 100.0 / total

    def collection_fields_as_blocks(self):
        class ColFieldBlock:
            def __init__(self, name, width):
                self.name, self.width = name, width

        fields = self.collection_fields()
        return [ ColFieldBlock(n, self.horizontal_width(n)) for n in fields ]

    def items_sortable(self):
        return 'unsortable' not in self.options

    def button_text(self):
        if 'button_text' in self.options: return self.options['button_text']

        if self.has_file and not self.file_optional:
            if self.max_items and self.max_items > 1: return _('add files')
            return _('add file')
        return _('add item')

class SubmissionRecord(models.Model):
    class Meta:
        constraints = [
            UniqueConstraint(fields=['submission', 'type'],
                             name='unique_submission_record_type')
        ]

    class RecordType(models.TextChoices):
        FILES = 'files', _('uploaded files')
        SUBMISSION = 'submission', _('form submission')

    program = models.ForeignKey(Program, models.SET_NULL, null=True, blank=True)
    form = models.SlugField(max_length=64, allow_unicode=True)
    submission = models.UUIDField(editable=False)
    type = models.CharField(max_length=32)
    recorded = models.DateTimeField(auto_now=True, verbose_name='recorded at')
    text = models.TextField(blank=True)
    number = models.PositiveBigIntegerField(null=True, blank=True)
    deleted = models.BooleanField(default=False)

# abstract classes, used as templates for the dynamic models:

class Submission(models.Model):
    class Meta:
        abstract = True

    _id = models.UUIDField(primary_key=True, default=uuid.uuid4,
                           editable=False)
    # valid up to page N:
    _valid = models.PositiveIntegerField(default=0, editable=False)
    # an array of N block id arrays, those skipped for form dependency not met:
    _skipped = models.JSONField(default=list, blank=True, editable=False)
    _created = models.DateTimeField(auto_now_add=True)
    _modified = models.DateTimeField(auto_now=True)
    _submitted = models.DateTimeField(null=True, blank=True)

    @classmethod
    def _get_form(cls):
        program_slug = cls._meta.program_slug
        slug = cls._meta.model_name[len(program_slug)+1:]
        return Form.objects.get(program__db_slug=program_slug, db_slug=slug)

    def __str__(self):
        if hasattr(self, '_email'): return self._email
        return str(self._id)

    def _get_absolute_url(self):
        form = self._get_form()
        args = {'program_slug': form.program.slug, 'form_slug': form.slug,
                'sid': self._id}
        return reverse('submission', kwargs=args)

    def _update_context(self, form, context):
        context['review_link'] = submission_link(self, form, rest='review')

        for block in form.visible_blocks():
            if block.block_type() == 'custom':
                context[block.name] = getattr(self, block.name)
            elif block.block_type() == 'stock':
                class Obj: pass
                obj = Obj()
                obj.__dict__ = { n: getattr(self, block.stock.field_name(n))
                                 for n in block.stock.widget_names() }
                if len(obj.__dict__) > 1: context[block.name] = obj
                else: context[block.name] = next(iter(obj.__dict__.values()))

    def _send_email(self, form, name, **kwargs):
        form_emails = form.emails()
        if name in form_emails:
            subject = Template(form_emails[name]['subject'])
            template = Template(form_emails[name]['content'])
        else: subject, template = form.load_email_templates(name)

        context = {
            'submission': self, 'form': form,
            'submission_link': submission_link(self, form)
        }
        if self._submitted: self._update_context(form, context)

        return send_email(template=template, to=self._email,
                          subject=subject, context=context, **kwargs)

    def _submit(self):
        self._submitted = timezone.now()
        self.save()

    def _collections(self, queryset=None, form=None):
        if not form: form = self._get_form()
        if not queryset: queryset = self._items.all()

        # form's order also orders blocks' items with the same collection name
        block = FormBlock.objects.filter(form=form, pk=OuterRef('_block'))
        queryset = queryset.annotate(page=Subquery(block.values('page')))
        queryset = queryset.annotate(block_rank=Subquery(block.values('_rank')))
        items = queryset.order_by('_collection', 'page', 'block_rank', '_rank')
        collections = groupby(items, key=lambda item: item._collection)
        return { k: list(items) for k, items in collections }

def file_path(instance, filename):
    return os.path.join(str(instance._submission_id), filename)

class SubmissionItem(UnderscoredRankedModel):
    class Meta:
        abstract = True

    _id = models.BigAutoField(primary_key=True, editable=False)
    # see Form.item_model() for _submission = models.ForeignKey(Submission)

    # the item's collection name == the name of the CollectionBlock
    _collection = models.CharField(max_length=32)

    # id of the collection block this item came from, as some may have same name
    _block = models.PositiveBigIntegerField()

    _file = models.FileField(upload_to=file_path, max_length=172, blank=True)
    _filesize = models.PositiveBigIntegerField(default=0)
    _filemeta = models.JSONField(default=dict, blank=True)
    _error = models.BooleanField(default=False)
    _message = models.CharField(max_length=100, default='', blank=True)

    @classmethod
    def _filename_maxlen(cls):
        # use 37 for directory uuid, 8 for possible alt name, 7 for order prefix
        return cls._meta.get_field('_file').max_length - 37 - 8 - 7

    @classmethod
    def _message_maxlen(cls):
        return cls._meta.get_field('_message').max_length

    def _rank_group(self):
        return self.__class__.objects.filter(_submission=self._submission,
                                             _collection=self._collection,
                                             _block=self._block)

    def _file_name(self):
        if not self._file: return None
        return self._file.name[self._file.name.index('/')+1:]

    def _file_type(self):
        if 'type' in self._filemeta: return self._filemeta['type']
        return ''

    def _artifact_url(self, name='thumbnail'):
        if not self._file: return None
        type = self._file_type()
        if not type: return None
        if name != 'thumbnail' or type not in ('image', 'video'):
            filetype = FileType.by_type(type)()
            return filetype.artifact_url(name, self._file.url)

        if type == 'image': return thumbnail_path(self._file.url)
        elif type == 'video': return thumbnail_path(self._file.url, ext='jpg')
        return None


from lib.cuckoo.common.abstracts import Signature

class DiskInformation(Signature):
    name = "antivm_generic_disk"
    description = "Queries information on disks, possibly for anti-virtualization"
    severity = 3
    categories = ["anti-vm"]
    authors = ["nex"]
    minimum = "2.0"

    filter_apinames = [
        "NtCreateFile",
        "DeviceIoControl",
        "NtDeviceIoControlFile",
    ]

    indicators = [
        "scsi0",
        "physicaldrive0",
    ]

    ioctls = {
        2954240: "IOCTL_STORAGE_QUERY_PROPERTY",
        458752: "IOCTL_DISK_GET_DRIVE_GEOMETRY",
        315400: "IOCTL_SCSI_MINIPORT",
    }

    def init(self):
        self.drive_opened = False

    def on_call(self, call, process):
        if call["api"] == "NtCreateFile":
            filepath = call["arguments"]["filepath"].lower()
            if "scsi0" in filepath or "physicaldrive0" in filepath:
                self.drive_opened = True
                self.mark_call()

        if call["api"] in ["DeviceIoControl", "NtDeviceIoControlFile"]:
            if self.drive_opened and call["arguments"]["control_code"] in self.ioctls:
                self.mark_call()
                return True


from scripts.helpful_scripts import get_account, get_contract, OPENSEA_URL
from brownie import DappToken, Escrow, SimpleNFT, network, config, ANFT
from web3 import Web3
import time

import yaml
import json
import os
import shutil

sample_token_uri = (
    "ipfs://Qmd9MCGtdVz2miNumBHDbvj8bigSgTwnr4SbyH6DNnpWdt?filename=0-PUG.json"
)

KEPT_BALANCE = Web3.toWei(1000, "ether")
KEPT_LOAT_BALANCE = Web3.toWei(500000, "ether")

def update_front_end():
    # sending the build folder
    src = "./build"
    # dest = "./front_end/src/chain-info"
    dest = "../loanAgainstNFT/contracts"

    copy_folders_to_front_end(src, dest)
    # # sending the front end our config in JSON format
    # with open("brownie-config.yaml", "r") as brownie_config:
    #     config_dict = yaml.load(brownie_config, Loader=yaml.FullLoader)
    #     with open("./front_end/src/brownie-config.json", "w") as brownie_config_json:
    #         json.dump(config_dict, brownie_config_json)
    # print("front end updated")

def copy_folders_to_front_end(src, dest):
    if os.path.exists(dest):
        shutil.rmtree(dest)
    shutil.copytree(src, dest)

def deploy_escrow_and_tokens_and_nfts():
    account = get_account()
    # non_owner = get_account(index=1)  # gonachi
    non_owner = get_account(key=1)  # goerli
    dapp_token = DappToken.deploy({"from": account})  # governance token
    # loan_token = LoanToken.deploy({"from": account})  # loan token
    escrow = Escrow.deploy(  # escrow wallet
        dapp_token.address,
        {"from": account},
        publish_source=config["networks"][network.show_active()]["verify"],
    )
    tx = dapp_token.transfer(  # no approval because the account is the owner
        escrow.address,
        dapp_token.totalSupply() - KEPT_BALANCE,
        {"from": account},  # 99.9%
    )
    tx.wait(1)
    # SimpleNFT, and we have the NFT address, can we mock NFT too? no need?
    simple_nft = SimpleNFT.deploy({"from": account})
    tx = simple_nft.createNFT(sample_token_uri, {"from": account})
    tx.wait(1)
    simple_nft_id = 0
    #
    # a_nft = get_contract("a_nft")
    a_nft = ANFT.deploy({"from": account})
    tx = a_nft.createNFT(sample_token_uri, {"from": account})
    tx.wait(1)
    a_nft_id = 0

    loan_token = get_contract("loan_token")
    loan_token_price_feed = get_contract("loan_token_price_feed")
    init_amount = (
        loan_token.balanceOf(account.address) / 10
    )  # give escrow half of the loan token for test
    loan_token.approve(escrow.address, init_amount, {"from": account})
    tx = loan_token.transfer(
        escrow.address,
        init_amount,
        {"from": account},
    )
    tx.wait(1)
    init_amount = (
        loan_token.balanceOf(account.address) / 100
    )  # give non-owner half of the left loan token for test
    loan_token.approve(non_owner.address, init_amount, {"from": account})
    tx = loan_token.transfer(
        non_owner.address,
        init_amount,
        {"from": account},
    )
    tx.wait(1)
    dict_of_allowed_nfts = {
        simple_nft: get_contract("simple_nft_price_feed"),
        a_nft: get_contract("a_nft_price_feed"),
        # b_nft: get_contract("b_nft_price_feed"),
    }
    add_allowed_nfts(escrow, dict_of_allowed_nfts, account)
    # set simple and a nft collection offers and specific offers
    loan_Amount = Web3.toWei(0.0001, "ether")
    loan_Days = 3
    loan_Interest = 286
    sel_index = 1
    tx = escrow.setCollectionOffers(
        simple_nft.address, loan_Amount, loan_Days, loan_Interest, sel_index
    )
    tx.wait(1)
    sel_index = 2
    tx = escrow.setCollectionOffers(
        simple_nft.address, loan_Amount / 2, loan_Days * 2, loan_Interest, sel_index
    )
    tx.wait(1)
    sel_index = 3
    tx = escrow.setCollectionOffers(
        simple_nft.address, loan_Amount / 4, loan_Days * 5, loan_Interest, sel_index
    )
    tx.wait(1)
    tx = escrow.setOffers(
        simple_nft.address, simple_nft_id, loan_Amount * 2, loan_Days * 3, loan_Interest
    )
    tx.wait(1)
    sel_index = 1
    tx = escrow.setCollectionOffers(
        a_nft.address, loan_Amount, loan_Days, loan_Interest, sel_index
    )
    tx.wait(1)
    sel_index = 2
    tx = escrow.setCollectionOffers(
        a_nft.address, loan_Amount / 4, loan_Days * 3, loan_Interest, sel_index
    )
    tx.wait(1)
    sel_index = 3
    tx = escrow.setCollectionOffers(
        a_nft.address, loan_Amount / 8, loan_Days * 6, loan_Interest, sel_index
    )
    tx.wait(1)
    tx = escrow.setOffers(
        a_nft.address, a_nft_id, loan_Amount * 3, loan_Days * 2, loan_Interest
    )
    tx.wait(1)

    sel_index = 2
    loanProcess2(escrow, simple_nft, simple_nft_id, sel_index, account, loan_token)
    get_stats(escrow)
    sel_index = 0
    loanProcess2(escrow, a_nft, a_nft_id, sel_index, account, loan_token)
    get_stats(escrow)
    tx = simple_nft.createNFT(sample_token_uri, {"from": non_owner})
    tx.wait(1)
    simple_nft_id = 1
    sel_index = 0
    loanProcess2(escrow, simple_nft, simple_nft_id, sel_index, non_owner, loan_token)
    get_stats(escrow)
    tx = a_nft.createNFT(sample_token_uri, {"from": non_owner})
    tx.wait(1)
    a_nft_id = 1
    sel_index = 2
    loanProcess2(escrow, a_nft, a_nft_id, sel_index, non_owner, loan_token)
    get_stats(escrow)

    simple_nft_id = 0
    repayProcess2(escrow, simple_nft, simple_nft_id, account, loan_token)
    get_stats(escrow)
    a_nft_id = 1
    repayProcess2(escrow, a_nft, a_nft_id, non_owner, loan_token)
    get_stats(escrow)
    simple_nft_id = 1
    repayProcess2(escrow, simple_nft, simple_nft_id, non_owner, loan_token)
    get_stats(escrow)
    a_nft_id = 0
    repayProcess2(escrow, a_nft, a_nft_id, account, loan_token)
    get_stats(escrow)

    # print(escrow.allowedNfts(0))
    # print(escrow.allowedNfts(1))
    # print(escrow.numOfAllowedNfts())
    # tx = escrow.updateAllowedNfts(simple_nft.address, False, {"from": account})
    # tx.wait(1)
    # print(escrow.allowedNfts(0))
    # print(escrow.numOfAllowedNfts())

    # return escrow, simple_nft, dapp_token, loan_token

def get_stats(escrow):
    numOfBorrowers = int(escrow.numOfBorrowers())
    print("There are", numOfBorrowers, "borrowers")
    print("They are", escrow.borrowers)
    for borrowerIndex in range(int(numOfBorrowers)):
        borrower = escrow.borrowers(borrowerIndex)
        numOfNftStaked = escrow.numOfNftStaked(borrower)
        print(borrower, "has", numOfNftStaked, "nfts staked")
        for index in range(int(numOfNftStaked)):
            stakedNftAddress = escrow.stakedNftAddress(borrower, index)
            stakedNftId = escrow.stakedNftId(borrower, index)
            print(index, "th staked nft address is ", stakedNftAddress)
            print(index, "th staked nft id is ", stakedNftId)
            nftLoanAmount = escrow.nftLoanAmount(stakedNftAddress, stakedNftId)
            nftLoanPeriod = escrow.nftLoanPeriod(stakedNftAddress, stakedNftId)
            nftLoanInterest = escrow.nftLoanInterest(stakedNftAddress, stakedNftId)

            nftLoanRepayAmount = escrow.nftLoanRepayAmount(
                stakedNftAddress, stakedNftId
            )
            nftLoanExpireTime = escrow.nftLoanExpireTime(stakedNftAddress, stakedNftId)
            nftLoanHolderAddress = escrow.nftLoanHolderAddress(
                stakedNftAddress, stakedNftId
            )
            print(
                "nftLoanAmount=",
                nftLoanAmount,
                "nftLoanPeriod=",
                nftLoanPeriod,
                "nftLoanInterest=",
                nftLoanInterest,
                "nftLoanRepayAmount=",
                nftLoanRepayAmount,
                "nftLoanExpireTime=",
                nftLoanExpireTime,
                "nftLoanHolderAddress=",
                nftLoanHolderAddress,
            )

def loanProcess2(escrow, simple_nft, simple_nft_id, sel_index, _account, loan_token):
    simple_nft.approve(escrow.address, simple_nft_id, {"from": _account})

    loan_amount, loan_days, loan_interest = escrow.getOffers(
        simple_nft.address, simple_nft_id, sel_index
    )
    loan_token.approve(escrow.address, loan_amount, {"from": _account})
    tx = escrow.requestLoan(
        loan_token.address,
        simple_nft.address,
        simple_nft_id,
        sel_index,
        {"from": _account},
    )
    tx.wait(1)

def repayProcess2(escrow, simple_nft, simple_nft_id, account, loan_token):
    time.sleep(1)
    # holder_address, expire_time, repay_amount = escrow.getNftLockData(
    #     simple_nft.address, simple_nft_id, {"from": account}
    # )
    deposit_amount = escrow.nftLoanRepayAmount(simple_nft.address, simple_nft_id)
    loan_token.approve(escrow.address, deposit_amount, {"from": account})
    tx = escrow.redeemLoan(
        loan_token.address,
        simple_nft.address,
        simple_nft_id,
        {"from": account},
    )
    tx.wait(1)

def loanProcess(escrow, simple_nft, simple_nft_id, account, loan_token):
    simple_nft.approve(escrow.address, simple_nft_id, {"from": account})
    tx = escrow.nftStaking(
        simple_nft.address,
        simple_nft_id,
        {
            "from": account,  # it's not the approval problem that make us cannt pass here
            # "gas_price": 0,
            # "gas_limit": 120000000000,
            # "allow_revert": True,
        },
    )
    tx.wait(1)

    loan_Amount = Web3.toWei(0.001, "ether")
    loan_Days = 3
    loan_Interest = 286
    escrow.setOffers(
        simple_nft.address, simple_nft_id, loan_Amount, loan_Days, loan_Interest
    )
    loan_amount, loan_days, loan_interest = escrow.getOffers(
        simple_nft.address, simple_nft_id
    )
    loan_token.approve(escrow.address, loan_amount, {"from": account})
    tx = escrow.loanTransfer(
        loan_token.address, account, loan_amount, {"from": account}
    )
    tx.wait(1)

    initTime = time.time()
    expireTime = initTime + loan_days * 24 * 60 * 60
    repayAmount = loan_amount * (1 + loan_interest / (10000))
    tx = escrow.nftLock(
        simple_nft.address,
        simple_nft_id,
        account,
        expireTime,
        repayAmount,
        {"from": account},
    )
    tx.wait(1)

def repayProcess(escrow, simple_nft, simple_nft_id, account, loan_token):
    time.sleep(1)
    holder_address, expire_time, repay_amount = escrow.getNftLockData(
        simple_nft.address, simple_nft_id, {"from": account}
    )
    deposit_amount = repay_amount
    current_time = time.time()
    if (holder_address == account.address) & (time.time() < expire_time):
        loan_token.approve(escrow.address, deposit_amount, {"from": account})
        tx = escrow.loanRepay(
            loan_token.address,
            deposit_amount,
            {"from": account},
        )
        tx.wait(1)
        if deposit_amount >= repay_amount:
            # simple_nft.approve(account, 0, {"from": account})
            tx = escrow.nftUnStaking(
                simple_nft.address, simple_nft_id, {"from": account}
            )
            tx.wait(1)

def add_allowed_nfts(escrow, dict_of_allowed_nfts, account):
    update = True
    for nft in dict_of_allowed_nfts:
        add_tx = escrow.updateAllowedNfts(nft.address, update, {"from": account})
        add_tx.wait(1)
        set_tx = escrow.setPriceFeedContract(
            nft.address, dict_of_allowed_nfts[nft], {"from": account}
        )
        set_tx.wait(1)

def main():
    deploy_escrow_and_tokens_and_nfts()
    # update_front_end()


import urlparse

from website.models import Node, User, Guid
from website.files.models.base import StoredFileNode
from website import settings as website_settings

from api.base.utils import absolute_reverse

from api.base.serializers import (JSONAPISerializer, IDField, TypeField, RelationshipField, LinksField)

def get_type(record):
    if isinstance(record, Node):
        return 'nodes'
    elif isinstance(record, User):
        return 'users'
    elif isinstance(record, StoredFileNode):
        return 'files'
    elif isinstance(record, Guid):
        return get_type(record.referent)

def get_related_view(record):
    kind = get_type(record)
    # slight hack, works for existing types
    singular = kind.rstrip('s')
    return '{}:{}-detail'.format(kind, singular)

def get_related_view_kwargs(record):
    kind = get_type(record)
    # slight hack, works for existing types
    singular = kind.rstrip('s')
    return {
        '{}_id'.format(singular): '<_id>'
    }

class GuidSerializer(JSONAPISerializer):

    class Meta:
        type_ = 'guids'

    filterable_fields = tuple()

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    referent = RelationshipField(
        related_view=get_related_view,
        related_view_kwargs=get_related_view_kwargs,
        related_meta={
            'type': 'get_type'
        }
    )
    links = LinksField({
        'self': 'get_absolute_url',
        'html': 'get_absolute_html_url'
    })

    def get_type(self, guid):
        return get_type(guid.referent)

    def get_absolute_url(self, obj):
        return absolute_reverse('guids:guid-detail', kwargs={'guids': obj._id})

    def get_absolute_html_url(self, obj):
        if not isinstance(obj.referent, StoredFileNode):
            return obj.referent.absolute_url
        return urlparse.urljoin(website_settings.DOMAIN, '/{}/'.format(obj._id))