from typing import List

from src.chain.Pool import Pool
from src.chain.blockchain import BlockChain
from src.chain.pojos import Transaction, TxType

class SwapPath:
    def __init__(self,pools:List[Pool],inToken,outToken):
        self.pools= pools
        self.inToken= inToken
        self.outToken= outToken

class CompositeSwap(Transaction):

    def __init__(self, owner: str, to: str, tokenFrom:str, tokenTo:str, pools:List[Pool], amount:float):
        super().__init__(owner=owner, type=TxType.CompositeSwap)
        self.to = to
        self.tokenFrom= tokenFrom
        self.tokenTo = tokenTo
        self.pools= pools
        self.amount = amount

    def calcPriceOnPath(swapPath:SwapPath):
        inToken = swapPath.inToken
        result = 1
        for pool in swapPath.pools:
            AtoB = pool.tokenA.symbol == inToken
            result *= pool.getSwapValueAfterFees(AtoB)
            inToken = pool.tokenB.symbol if AtoB else pool.tokenA.symbol
        return 1/result

    def checkValid(self, chain: BlockChain):
        if self.owner not in chain.wallets or self.to not in chain.wallets\
                or self.tokenFrom not in chain.tokens or self.tokenTo not in chain.tokens:
            return False

        inToken= self.tokenFrom
        for pool in self.pools:
            if inToken != pool.tokenA.symbol and inToken != pool.tokenB.symbol:
                return False
            inToken = pool.tokenB.symbol if pool.tokenA.symbol == inToken else pool.tokenA.symbol

        if inToken != self.tokenTo:
            return False

        owner = chain.wallets[self.owner]
        if self.tokenFrom not in owner.balances:
            return False
        if owner.balances[self.tokenFrom] < self.amount:
            return False

        return True

    def applyToChain(self, chain: BlockChain):
        fromWallet = chain.wallets[self.owner]
        toWallet = chain.wallets[self.to]
        inVolume= self.amount
        inToken= self.tokenFrom
        for pool in self.pools:
            inVolume= pool.swap(inToken,inVolume)
            inToken= pool.tokenB.symbol if inToken == pool.tokenA.symbol else pool.tokenA.symbol

        fromWallet.balances[self.tokenFrom] -= self.amount
        if self.tokenTo not in toWallet.balances:
            toWallet.balances[self.tokenTo]= 0
        toWallet.balances[self.tokenTo] += inVolume


import os
import sys
import time
import requests
from re import search, split
from datetime import datetime
import xlsxwriter
from PIL import Image
from psaw import PushshiftAPI

print("Please enter the date range you'd like to scrape. The dates should be in the MM/DD/YYYY format.")

before = split("-|/| ", input("Scrape subreddit before: "))
after = split("-|/| ", input("Scrape subreddit after: "))
if len(before) != 3:
    before = [1, 2, 2020]
if len(after) != 3:
    after = [1, 1, 2020]

api = PushshiftAPI()

root_urls = ("i.redd.it/", "imgur.com/")
file_formats = (".jpg", ".jpeg", ".png")

class Subreddit:
    def __init__(self, sub):
        self.row = 1
        self.sub = sub
        self.fields = ("subreddit", "created", "created_utc",
                       "title", "url", "image")
        self.output_dir = "output/" + sub + "/"
        self.images_dir = self.output_dir + "images/"

        if not os.path.exists(self.images_dir):
            os.makedirs(self.images_dir)

        self.image_files = os.listdir(self.images_dir)
        self.workbook = xlsxwriter.Workbook(self.output_dir + 'data.xlsx')
        self.worksheet = self.workbook.add_worksheet()
        self.worksheet.set_default_row(300)
        self.worksheet.set_column(0, len(self.fields) - 1, 30)

        col_index = 0

        while col_index < len(self.fields):
            self.worksheet.write(0, col_index, self.fields[col_index])
            col_index += 1

    def addRow(self, data, url):
        # Collect post data and image for each row to write to spreadsheet
        col_index = 0
        for field in self.fields:
            try:
                if field == "created":
                    date = datetime.fromtimestamp(data.d_[field])
                    cell_data = date.ctime()
                elif field == "image":
                    cell_data = self.downloadImage(url)
                else:
                    cell_data = data.d_[field]
                self.writeToXlsx(cell_data, col_index)
                col_index += 1
            except:
                col_index += 1
        self.row += 1

    def downloadImage(self, url):
        # Downloads the image from the url to the specified images folder
        for root_url in root_urls:
            if root_url in url:
                file = url.replace(root_url, '').replace(
                    'https://', '').replace('http://', '')
                break

        if not os.path.isfile(self.images_dir + file):
            try:
                image = requests.get(url, allow_redirects=False)
                if(image.status_code == 200):
                    try:
                        output_filehandle = open(
                            self.images_dir + file, mode='bx')
                        output_filehandle.write(image.content)
                        self.resizeImage(file)
                        return file
                    except:
                        print('downloading ' + file + ' failed!')
                        pass
            except:
                print('Could not reach url')
        else:
            return file

    def getImgPost(self, row):
        # Only get rows with a url that's an image.
        this_url = row.d_["url"]
        for root_url in root_urls:
            if root_url in this_url:
                if root_url == "imgur.com/":
                    this_url == this_url + ".jpeg"
                for file_format in file_formats:
                    if this_url.endswith(file_format):
                        self.addRow(row, this_url)
                        break

    def resizeImage(self, file):
        # Resize the image using Pillow, a fork of the Python Imaging Libary.
        basewidth = 400
        try:
            img = Image.open(self.images_dir + file)
            wpercent = (basewidth/float(img.size[0]))
            hsize = int((float(img.size[1])*float(wpercent)))
            img = img.resize((basewidth, hsize), Image.ANTIALIAS)
            img.save(self.images_dir + file)
        except Exception as err:
            print(err)

    def scrape(self):
        # Use psaw, a wrapper for the pushshift.io API (which makes it much easier to choose a date range for reddit scrapes), to scrape reddit with the submitted parameters.
        afterDate = int(datetime(
            int(after[2]),
            int(after[0]),
            int(after[1]))
            .timestamp())
        beforeDate = int(datetime(
            int(before[2]),
            int(before[0]),
            int(before[1]))
            .timestamp())

        try:
            submission_gen = api.search_submissions(
                after=afterDate,
                before=beforeDate,
                subreddit=self.sub,
                filter=self.fields
            )

            for g in submission_gen:
                self.getImgPost(g)

        except Exception as err:
            print(err)

        self.workbook.close()

    def writeToXlsx(self, data, col):
        # This is the method that actually writes everything to an .xlsx file
        if self.fields[col] == "image":
            self.worksheet.insert_image(self.row, col, self.images_dir + data, {
                'x_scale': 0.5,
                'y_scale': 0.5,
                'x_offset': 5,
                'y_offset': 5,
                'positioning': 1
                })
        else:
            self.worksheet.write(self.row, col, data)

if not sys.argv:
    print("Please enter which subreddits you'd like scraped")
    subs = split(",|, |;| ", input("Scrape subreddit after: "))
    i = 0
else:
    subs = sys.argv
    i = 1

while i < len(subs):
    print("Scraping r/" + subs[i])
    S = Subreddit(subs[i])
    S.scrape()
    i += 1


import copy
import threading

from typing import Dict, List, Set, Sequence, Tuple, Iterable  # noqa: F401
from typing import Callable, Optional, Union, Any  # noqa: F401

class SSH2_KexDB:  # pylint: disable=too-few-public-methods
    FAIL_1024BIT_MODULUS = 'using small 1024-bit modulus'
    FAIL_3DES = 'using broken & deprecated 3DES cipher'
    FAIL_BLOWFISH = 'using weak & deprecated Blowfish cipher'
    FAIL_CAST = 'using weak & deprecated CAST cipher'
    FAIL_DES = 'using broken DES cipher'
    FAIL_IDEA = 'using deprecated IDEA cipher'
    FAIL_LOGJAM_ATTACK = 'vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)'
    FAIL_MD5 = 'using broken MD5 hash algorithm'
    FAIL_NSA_BACKDOORED_CURVE = 'using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency'
    FAIL_PLAINTEXT = 'no encryption/integrity'
    FAIL_RC4 = 'using broken RC4 cipher'
    FAIL_RIJNDAEL = 'using deprecated & non-standardized Rijndael cipher'
    FAIL_RIPEMD = 'using deprecated RIPEMD hash algorithm'
    FAIL_SEED = 'using deprecated SEED cipher'
    FAIL_SERPENT = 'using deprecated Serpent cipher'
    FAIL_SHA1 = 'using broken SHA-1 hash algorithm'
    FAIL_SMALL_ECC_MODULUS = 'using small ECC modulus'
    FAIL_UNKNOWN = 'using unknown algorithm'
    FAIL_UNPROVEN = 'using unproven algorithm'
    FAIL_UNTRUSTED = 'using untrusted algorithm developed in secret by a government entity'

    WARN_2048BIT_MODULUS = '2048-bit modulus only provides 112-bits of symmetric strength'
    WARN_BLOCK_SIZE = 'using small 64-bit block size'
    WARN_CIPHER_MODE = 'using weak cipher mode'
    WARN_ENCRYPT_AND_MAC = 'using encrypt-and-MAC mode'
    WARN_EXPERIMENTAL = 'using experimental algorithm'
    WARN_RNDSIG_KEY = 'using weak random number generator could reveal the key'
    WARN_TAG_SIZE = 'using small 64-bit tag size'
    WARN_TAG_SIZE_96 = 'using small 96-bit tag size'

    INFO_DEFAULT_OPENSSH_CIPHER = 'default cipher since OpenSSH 6.9'
    INFO_DEFAULT_OPENSSH_KEX = 'default key exchange since OpenSSH 6.4'
    INFO_DEPRECATED_IN_OPENSSH88 = 'deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8'
    INFO_DISABLED_IN_DBEAR67 = 'disabled in Dropbear SSH 2015.67'
    INFO_DISABLED_IN_OPENSSH70 = 'disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0'
    INFO_NEVER_IMPLEMENTED_IN_OPENSSH = 'despite the @openssh.com tag, this was never implemented in OpenSSH'
    INFO_REMOVED_IN_OPENSSH61 = 'removed since OpenSSH 6.1, removed from specification'
    INFO_REMOVED_IN_OPENSSH69 = 'removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9'
    INFO_REMOVED_IN_OPENSSH70 = 'removed in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0'
    INFO_WITHDRAWN_PQ_ALG = 'the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security'

    # Maintains a dictionary per calling thread that yields its own copy of MASTER_DB.  This prevents results from one thread polluting the results of another thread.
    DB_PER_THREAD: Dict[int, Dict[str, Dict[str, List[List[Optional[str]]]]]] = {}

    MASTER_DB: Dict[str, Dict[str, List[List[Optional[str]]]]] = {
        # Format: 'algorithm_name': [['version_first_appeared_in'], [reason_for_failure1, reason_for_failure2, ...], [warning1, warning2, ...], [info1, info2, ...]]
        'kex': {
            'Curve25519SHA256': [[]],
            'curve25519-sha256': [['7.4,d2018.76'], [], [], [INFO_DEFAULT_OPENSSH_KEX]],
            'curve25519-sha256@libssh.org': [['6.4,d2013.62,l10.6.0'], [], [], [INFO_DEFAULT_OPENSSH_KEX]],
            'curve448-sha512': [[]],
            'curve448-sha512@libssh.org': [[]],
            'diffie-hellman-group14-sha1': [['3.9,d0.53,l10.6.0'], [FAIL_SHA1], [WARN_2048BIT_MODULUS]],
            'diffie-hellman-group14-sha224@ssh.com': [[]],
            'diffie-hellman-group14-sha256': [['7.3,d2016.73'], [], [WARN_2048BIT_MODULUS]],
            'diffie-hellman-group14-sha256@ssh.com': [[], [], [WARN_2048BIT_MODULUS]],
            'diffie-hellman-group15-sha256': [[]],
            'diffie-hellman-group15-sha256@ssh.com': [[]],
            'diffie-hellman-group15-sha384@ssh.com': [[]],
            'diffie-hellman-group15-sha512': [[]],
            'diffie-hellman-group16-sha256': [[]],
            'diffie-hellman-group16-sha384@ssh.com': [[]],
            'diffie-hellman-group16-sha512': [['7.3,d2016.73']],
            'diffie-hellman-group16-sha512@ssh.com': [[]],
            'diffie-hellman-group17-sha512': [[]],
            'diffie-hellman_group17-sha512': [[]],
            'diffie-hellman-group18-sha512': [['7.3']],
            'diffie-hellman-group18-sha512@ssh.com': [[]],
            'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1], [], [INFO_REMOVED_IN_OPENSSH69]],
            'diffie-hellman-group1-sha256': [[], [FAIL_1024BIT_MODULUS]],
            'diffie-hellman-group-exchange-sha1': [['2.3.0', '6.6', None], [FAIL_SHA1]],
            'diffie-hellman-group-exchange-sha224@ssh.com': [[]],
            'diffie-hellman-group-exchange-sha256': [['4.4']],
            'diffie-hellman-group-exchange-sha256@ssh.com': [[]],
            'diffie-hellman-group-exchange-sha384@ssh.com': [[]],
            'diffie-hellman-group-exchange-sha512@ssh.com': [[]],
            'ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'ecdh-sha2-1.2.840.10045.3.1.1': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-192 / secp192r1
            'ecdh-sha2-1.2.840.10045.3.1.7': [[], [FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-256 / secp256r1
            'ecdh-sha2-1.3.132.0.10': [[]],  # ECDH over secp256k1 (i.e.: the Bitcoin curve)
            'ecdh-sha2-1.3.132.0.16': [[], [FAIL_UNPROVEN]],  # sect283k1
            'ecdh-sha2-1.3.132.0.1': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],  # sect163k1
            'ecdh-sha2-1.3.132.0.26': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],  # sect233k1
            'ecdh-sha2-1.3.132.0.27': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]],  # sect233r1
            'ecdh-sha2-1.3.132.0.33': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-224 / secp224r1
            'ecdh-sha2-1.3.132.0.34': [[], [FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-384 / secp384r1
            'ecdh-sha2-1.3.132.0.35': [[], [FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-521 / secp521r1
            'ecdh-sha2-1.3.132.0.36': [[], [FAIL_UNPROVEN]],  # sect409k1
            'ecdh-sha2-1.3.132.0.37': [[], [FAIL_NSA_BACKDOORED_CURVE]],  # sect409r1
            'ecdh-sha2-1.3.132.0.38': [[], [FAIL_UNPROVEN]],  # sect571k1

            # Note: the base64 strings, according to draft 6 of RFC5656, is Base64(MD5(DER(OID))).  The final RFC5656 dropped the base64 strings in favor of plain OID concatenation, but apparently some SSH servers implement them anyway.  See: https://datatracker.ietf.org/doc/html/draft-green-secsh-ecc-06#section-9.2
            'ecdh-sha2-4MHB+NBt3AlaSRQ7MnB4cg==': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],  # sect163k1
            'ecdh-sha2-5pPrSUQtIaTjUSt5VZNBjg==': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-192 / secp192r1
            'ecdh-sha2-9UzNcgwTlEnSCECZa7V1mw==': [[], [FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-256 / secp256r1
            'ecdh-sha2-brainpoolp256r1@genua.de': [[], [FAIL_UNPROVEN]],
            'ecdh-sha2-brainpoolp384r1@genua.de': [[], [FAIL_UNPROVEN]],
            'ecdh-sha2-brainpoolp521r1@genua.de': [[], [FAIL_UNPROVEN]],
            'ecdh-sha2-curve25519': [[], []],
            'ecdh-sha2-D3FefCjYoJ/kfXgAyLddYA==': [[], [FAIL_NSA_BACKDOORED_CURVE]],  # sect409r1
            'ecdh-sha2-h/SsxnLCtRBh7I9ATyeB3A==': [[], [FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-521 / secp521r1
            'ecdh-sha2-m/FtSAmrV4j/Wy6RVUaK7A==': [[], [FAIL_UNPROVEN]],  # sect409k1
            'ecdh-sha2-mNVwCXAoS1HGmHpLvBC94w==': [[], [FAIL_UNPROVEN]],  # sect571k1
            'ecdh-sha2-nistb233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],
            'ecdh-sha2-nistb409': [[], [FAIL_UNPROVEN]],
            'ecdh-sha2-nistk163': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],
            'ecdh-sha2-nistk233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],
            'ecdh-sha2-nistk283': [[], [FAIL_UNPROVEN]],
            'ecdh-sha2-nistk409': [[], [FAIL_UNPROVEN]],
            'ecdh-sha2-nistp192': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'ecdh-sha2-nistp224': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'ecdh-sha2-nistp256': [['5.7,d2013.62,l10.6.0'], [FAIL_NSA_BACKDOORED_CURVE]],
            'ecdh-sha2-nistp384': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE]],
            'ecdh-sha2-nistp521': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE]],
            'ecdh-sha2-nistt571': [[], [FAIL_UNPROVEN]],
            'ecdh-sha2-qCbG5Cn/jjsZ7nBeR7EnOA==': [[FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]],  # sect233r1
            'ecdh-sha2-qcFQaMAMGhTziMT0z+Tuzw==': [[], [FAIL_NSA_BACKDOORED_CURVE]],  # NIST P-384 / secp384r1
            'ecdh-sha2-VqBg4QRPjxx1EXZdV0GdWQ==': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_SMALL_ECC_MODULUS]],  # NIST P-224 / secp224r1
            'ecdh-sha2-wiRIU8TKjMZ418sMqlqtvQ==': [[], [FAIL_UNPROVEN]],  # sect283k1
            'ecdh-sha2-zD/b3hu/71952ArpUG4OjQ==': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],  # sect233k1
            'ecmqv-sha2': [[], [FAIL_UNPROVEN]],
            'ext-info-c': [[]],  # Extension negotiation (RFC 8308)
            'ext-info-s': [[]],  # Extension negotiation (RFC 8308)

            # The GSS kex algorithms get special wildcard handling, since they include variable base64 data after their standard prefixes.
            'gss-13.3.132.0.10-sha256-*': [[], [FAIL_UNKNOWN]],
            'gss-curve25519-sha256-*': [[]],
            'gss-curve448-sha512-*': [[]],
            'gss-gex-sha1-*': [[], [FAIL_SHA1]],
            'gss-gex-sha256-*': [[]],
            'gss-group14-sha1-*': [[], [FAIL_SHA1], [WARN_2048BIT_MODULUS]],
            'gss-group14-sha256-*': [[], [], [WARN_2048BIT_MODULUS]],
            'gss-group15-sha512-*': [[]],
            'gss-group16-sha512-*': [[]],
            'gss-group17-sha512-*': [[]],
            'gss-group18-sha512-*': [[]],
            'gss-group1-sha1-*': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1]],
            'gss-nistp256-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'gss-nistp384-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'gss-nistp521-sha512-*': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'kexAlgoCurve25519SHA256': [[]],
            'kexAlgoDH14SHA1': [[], [FAIL_SHA1], [WARN_2048BIT_MODULUS]],
            'kexAlgoDH1SHA1': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1]],
            'kexAlgoECDH256': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'kexAlgoECDH384': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'kexAlgoECDH521': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'kexguess2@matt.ucc.asn.au': [['d2013.57']],
            'm383-sha384@libassh.org': [[], [FAIL_UNPROVEN]],
            'm511-sha512@libassh.org': [[], [FAIL_UNPROVEN]],
            'rsa1024-sha1': [[], [FAIL_1024BIT_MODULUS, FAIL_SHA1]],
            'rsa2048-sha256': [[], [], [WARN_2048BIT_MODULUS]],
            'sm2kep-sha2-nistp256': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_UNTRUSTED]],
            'sntrup4591761x25519-sha512@tinyssh.org': [['8.0', '8.4'], [], [WARN_EXPERIMENTAL], [INFO_WITHDRAWN_PQ_ALG]],
            'sntrup761x25519-sha512@openssh.com': [['8.5'], [], []],
            'x25519-kyber-512r3-sha256-d00@amazon.com': [[]],
            'x25519-kyber512-sha512@aws.amazon.com': [[]],
        },
        'key': {
            'dsa2048-sha224@libassh.org': [[], [FAIL_UNPROVEN], [WARN_2048BIT_MODULUS]],
            'dsa2048-sha256@libassh.org': [[], [FAIL_UNPROVEN], [WARN_2048BIT_MODULUS]],
            'dsa3072-sha256@libassh.org': [[], [FAIL_UNPROVEN]],
            'ecdsa-sha2-1.3.132.0.10-cert-v01@openssh.com': [[], [FAIL_UNKNOWN]],
            'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]],  # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
            'ecdsa-sha2-curve25519': [[], [], [WARN_RNDSIG_KEY]],  # ECDSA with Curve25519?  Bizarre...
            'ecdsa-sha2-nistb233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistb409': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistk163': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistk233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistk283': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistk409': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistp224': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistp192': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistp256': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistp256-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistp384': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistp384-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistp521': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
            'ecdsa-sha2-nistt571': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
            'eddsa-e382-shake256@libassh.org': [[], [FAIL_UNPROVEN]],
            'eddsa-e521-shake256@libassh.org': [[], [FAIL_UNPROVEN]],
            'null': [[], [FAIL_PLAINTEXT]],
            'pgp-sign-dss': [[], [FAIL_1024BIT_MODULUS]],
            'pgp-sign-rsa': [[], [FAIL_1024BIT_MODULUS]],
            'rsa-sha2-256': [['7.2']],
            'rsa-sha2-256-cert-v01@openssh.com': [['7.8']],
            'rsa-sha2-512': [['7.2']],
            'rsa-sha2-512-cert-v01@openssh.com': [['7.8']],
            'sk-ecdsa-sha2-nistp256-cert-v01@openssh.com': [['8.2'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
            'sk-ecdsa-sha2-nistp256@openssh.com': [['8.2'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
            'sk-ssh-ed25519-cert-v01@openssh.com': [['8.2']],
            'sk-ssh-ed25519@openssh.com': [['8.2']],
            'spi-sign-rsa': [[]],
            'spki-sign-dss': [[], [FAIL_1024BIT_MODULUS]],
            'spki-sign-rsa': [[], [FAIL_1024BIT_MODULUS]],
            'ssh-dsa': [[], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY]],
            'ssh-dss': [['2.1.0,d0.28,l10.2', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY], [INFO_DISABLED_IN_OPENSSH70]],
            'ssh-dss-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY], [INFO_DISABLED_IN_OPENSSH70]],
            'ssh-dss-cert-v01@openssh.com': [['5.6', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY]],
            'ssh-dss-sha224@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
            'ssh-dss-sha256@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
            'ssh-dss-sha384@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
            'ssh-dss-sha512@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
            'ssh-ed25519': [['6.5,l10.7.0']],
            'ssh-ed25519-cert-v01@openssh.com': [['6.5']],
            'ssh-ed448': [[]],
            'ssh-ed448-cert-v01@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
            'ssh-gost2001': [[], [FAIL_UNTRUSTED]],
            'ssh-gost2012-256': [[], [FAIL_UNTRUSTED]],
            'ssh-gost2012-512': [[], [FAIL_UNTRUSTED]],
            'ssh-rsa1': [[], [FAIL_SHA1]],
            'ssh-rsa': [['2.5.0,d0.28,l10.2'], [FAIL_SHA1], [], [INFO_DEPRECATED_IN_OPENSSH88]],
            'ssh-rsa-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_SHA1], [], [INFO_REMOVED_IN_OPENSSH70]],
            'ssh-rsa-cert-v01@openssh.com': [['5.6'], [FAIL_SHA1], [], [INFO_DEPRECATED_IN_OPENSSH88]],
            'ssh-rsa-sha224@ssh.com': [[]],
            'ssh-rsa-sha2-256': [[]],
            'ssh-rsa-sha2-512': [[]],
            'ssh-rsa-sha256@ssh.com': [[]],
            'ssh-rsa-sha384@ssh.com': [[]],
            'ssh-rsa-sha512@ssh.com': [[]],
            'ssh-xmss-cert-v01@openssh.com': [['7.7'], [WARN_EXPERIMENTAL]],
            'ssh-xmss@openssh.com': [['7.7'], [WARN_EXPERIMENTAL]],
            'webauthn-sk-ecdsa-sha2-nistp256@openssh.com': [['8.3'], [FAIL_NSA_BACKDOORED_CURVE]],
            'x509v3-ecdsa-sha2-1.3.132.0.10': [[], [FAIL_UNKNOWN]],
            'x509v3-ecdsa-sha2-nistp256': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'x509v3-ecdsa-sha2-nistp384': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'x509v3-ecdsa-sha2-nistp521': [[], [FAIL_NSA_BACKDOORED_CURVE]],
            'x509v3-rsa2048-sha256': [[]],
            'x509v3-sign-dss': [[], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY]],
            'x509v3-sign-dss-sha1': [[], [FAIL_1024BIT_MODULUS, FAIL_SHA1]],
            'x509v3-sign-dss-sha224@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
            'x509v3-sign-dss-sha256@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
            'x509v3-sign-dss-sha384@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
            'x509v3-sign-dss-sha512@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
            'x509v3-sign-rsa': [[], [FAIL_SHA1]],
            'x509v3-sign-rsa-sha1': [[], [FAIL_SHA1]],
            'x509v3-sign-rsa-sha224@ssh.com': [[]],
            'x509v3-sign-rsa-sha256': [[]],
            'x509v3-sign-rsa-sha256@ssh.com': [[]],
            'x509v3-sign-rsa-sha384@ssh.com': [[]],
            'x509v3-sign-rsa-sha512@ssh.com': [[]],
            'x509v3-ssh-dss': [[], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY]],
            'x509v3-ssh-rsa': [[], [FAIL_SHA1], [], [INFO_DEPRECATED_IN_OPENSSH88]],
        },
        'enc': {
            '3des-cbc': [['1.2.2,d0.28,l10.2', '6.6', None], [FAIL_3DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            '3des-cfb': [[], [FAIL_3DES], [WARN_CIPHER_MODE]],
            '3des-ctr': [['d0.52'], [FAIL_3DES]],
            '3des-ecb': [[], [FAIL_3DES], [WARN_CIPHER_MODE]],
            '3des': [[], [FAIL_3DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            '3des-ofb': [[], [FAIL_3DES], [WARN_CIPHER_MODE]],
            'AEAD_AES_128_GCM': [[]],
            'AEAD_AES_256_GCM': [[]],
            'aes128-cbc': [['2.3.0,d0.28,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]],
            'aes128-ctr': [['3.7,d0.52,l10.4.1']],
            'aes128-gcm': [[]],
            'aes128-gcm@openssh.com': [['6.2']],
            'aes192-cbc': [['2.3.0,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]],
            'aes192-ctr': [['3.7,l10.4.1']],
            'aes192-gcm@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
            'aes256-cbc': [['2.3.0,d0.47,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]],
            'aes256-ctr': [['3.7,d0.52,l10.4.1']],
            'aes256-gcm': [[]],
            'aes256-gcm@openssh.com': [['6.2']],
            'arcfour128': [['4.2', '6.6', '7.1'], [FAIL_RC4]],
            'arcfour': [['2.1.0', '6.6', '7.1'], [FAIL_RC4]],
            'arcfour256': [['4.2', '6.6', '7.1'], [FAIL_RC4]],
            'blowfish-cbc': [['1.2.2,d0.28,l10.2', '6.6,d0.52', '7.1,d0.52'], [FAIL_BLOWFISH], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'blowfish-cfb': [[], [FAIL_BLOWFISH], [WARN_CIPHER_MODE]],
            'blowfish-ctr': [[], [FAIL_BLOWFISH], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'blowfish-ecb': [[], [FAIL_BLOWFISH], [WARN_CIPHER_MODE]],
            'blowfish': [[], [FAIL_BLOWFISH], [WARN_BLOCK_SIZE]],
            'blowfish-ofb': [[], [FAIL_BLOWFISH], [WARN_CIPHER_MODE]],
            'camellia128-cbc@openssh.org': [[], [], [WARN_CIPHER_MODE]],
            'camellia128-cbc': [[], [], [WARN_CIPHER_MODE]],
            'camellia128-ctr': [[]],
            'camellia128-ctr@openssh.org': [[]],
            'camellia192-cbc@openssh.org': [[], [], [WARN_CIPHER_MODE]],
            'camellia192-cbc': [[], [], [WARN_CIPHER_MODE]],
            'camellia192-ctr': [[]],
            'camellia192-ctr@openssh.org': [[]],
            'camellia256-cbc@openssh.org': [[], [], [WARN_CIPHER_MODE]],
            'camellia256-cbc': [[], [], [WARN_CIPHER_MODE]],
            'camellia256-ctr': [[]],
            'camellia256-ctr@openssh.org': [[]],
            'cast128-12-cbc@ssh.com': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
            'cast128-cbc': [['2.1.0', '6.6', '7.1'], [FAIL_CAST], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'cast128-12-cbc': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
            'cast128-12-cfb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
            'cast128-12-ecb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
            'cast128-12-ofb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
            'cast128-cfb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
            'cast128-ctr': [[], [FAIL_CAST]],
            'cast128-ecb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
            'cast128-ofb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
            'chacha20-poly1305': [[], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]],
            'chacha20-poly1305@openssh.com': [['6.5'], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]],
            'crypticore128@ssh.com': [[], [FAIL_UNPROVEN]],
            'des-cbc': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'des-cfb': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'des-ecb': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'des-ofb': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'des-cbc-ssh1': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'des-cbc@ssh.com': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'des': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
            'idea-cbc': [[], [FAIL_IDEA], [WARN_CIPHER_MODE]],
            'idea-cfb': [[], [FAIL_IDEA], [WARN_CIPHER_MODE]],
            'idea-ctr': [[], [FAIL_IDEA]],
            'idea-ecb': [[], [FAIL_IDEA], [WARN_CIPHER_MODE]],
            'idea-ofb': [[], [FAIL_IDEA], [WARN_CIPHER_MODE]],
            'none': [['1.2.2,d2013.56,l10.2'], [FAIL_PLAINTEXT]],
            'rijndael128-cbc': [['2.3.0', '7.0'], [FAIL_RIJNDAEL], [WARN_CIPHER_MODE], [INFO_DISABLED_IN_OPENSSH70]],
            'rijndael192-cbc': [['2.3.0', '7.0'], [FAIL_RIJNDAEL], [WARN_CIPHER_MODE], [INFO_DISABLED_IN_OPENSSH70]],
            'rijndael256-cbc': [['2.3.0', '7.0'], [FAIL_RIJNDAEL], [WARN_CIPHER_MODE], [INFO_DISABLED_IN_OPENSSH70]],
            'rijndael-cbc@lysator.liu.se': [['2.3.0', '6.6', '7.0'], [FAIL_RIJNDAEL], [WARN_CIPHER_MODE], [INFO_DISABLED_IN_OPENSSH70]],
            'rijndael-cbc@ssh.com': [[], [FAIL_RIJNDAEL], [WARN_CIPHER_MODE]],
            'seed-cbc@ssh.com': [[], [FAIL_SEED], [WARN_CIPHER_MODE]],
            'seed-ctr@ssh.com': [[], [FAIL_SEED]],
            'serpent128-cbc': [[], [FAIL_SERPENT], [WARN_CIPHER_MODE]],
            'serpent128-ctr': [[], [FAIL_SERPENT]],
            'serpent128-gcm@libassh.org': [[], [FAIL_SERPENT]],
            'serpent192-cbc': [[], [FAIL_SERPENT], [WARN_CIPHER_MODE]],
            'serpent192-ctr': [[], [FAIL_SERPENT]],
            'serpent256-cbc': [[], [FAIL_SERPENT], [WARN_CIPHER_MODE]],
            'serpent256-ctr': [[], [FAIL_SERPENT]],
            'serpent256-gcm@libassh.org': [[], [FAIL_SERPENT]],
            'twofish128-cbc': [['d0.47', 'd2014.66'], [], [WARN_CIPHER_MODE], [INFO_DISABLED_IN_DBEAR67]],
            'twofish128-ctr': [['d2015.68']],
            'twofish128-gcm@libassh.org': [[]],
            'twofish192-cbc': [[], [], [WARN_CIPHER_MODE]],
            'twofish192-ctr': [[]],
            'twofish256-cbc': [['d0.47', 'd2014.66'], [], [WARN_CIPHER_MODE], [INFO_DISABLED_IN_DBEAR67]],
            'twofish256-ctr': [['d2015.68']],
            'twofish256-gcm@libassh.org': [[]],
            'twofish-cbc': [['d0.28', 'd2014.66'], [], [WARN_CIPHER_MODE], [INFO_DISABLED_IN_DBEAR67]],
            'twofish-cfb': [[], [], [WARN_CIPHER_MODE]],
            'twofish-ctr': [[]],
            'twofish-ecb': [[], [], [WARN_CIPHER_MODE]],
            'twofish-ofb': [[], [], [WARN_CIPHER_MODE]],
        },
        'mac': {
            'AEAD_AES_128_GCM': [[]],
            'AEAD_AES_256_GCM': [[]],
            'aes128-gcm': [[]],
            'aes256-gcm': [[]],
            'cbcmac-3des': [[], [FAIL_UNPROVEN, FAIL_3DES]],
            'cbcmac-aes': [[], [FAIL_UNPROVEN]],
            'cbcmac-blowfish': [[], [FAIL_UNPROVEN, FAIL_BLOWFISH]],
            'cbcmac-des': [[], [FAIL_UNPROVEN, FAIL_DES]],
            'cbcmac-rijndael': [[], [FAIL_UNPROVEN, FAIL_RIJNDAEL]],
            'cbcmac-twofish': [[], [FAIL_UNPROVEN]],
            'chacha20-poly1305@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],  # Despite the @openssh.com tag, this was never shipped as a MAC in OpenSSH (only as a cipher); it is only implemented as a MAC in Syncplify.
            'crypticore-mac@ssh.com': [[], [FAIL_UNPROVEN]],
            'hmac-md5': [['2.1.0,d0.28', '6.6', '7.1'], [FAIL_MD5], [WARN_ENCRYPT_AND_MAC]],
            'hmac-md5-96': [['2.5.0', '6.6', '7.1'], [FAIL_MD5], [WARN_ENCRYPT_AND_MAC]],
            'hmac-md5-96-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_MD5]],
            'hmac-md5-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_MD5]],
            'hmac-ripemd160': [['2.5.0', '6.6', '7.1'], [FAIL_RIPEMD], [WARN_ENCRYPT_AND_MAC]],
            'hmac-ripemd160-96': [[], [FAIL_RIPEMD], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
            'hmac-ripemd160-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_RIPEMD]],
            'hmac-ripemd160@openssh.com': [['2.1.0', '6.6', '7.1'], [FAIL_RIPEMD], [WARN_ENCRYPT_AND_MAC]],
            'hmac-ripemd': [[], [FAIL_RIPEMD], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha1': [['2.1.0,d0.28,l10.2'], [FAIL_SHA1], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha1-96': [['2.5.0,d0.47', '6.6', '7.1'], [FAIL_SHA1], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha1-96-etm@openssh.com': [['6.2', '6.6', None], [FAIL_SHA1]],
            'hmac-sha1-96@openssh.com': [[], [FAIL_SHA1], [WARN_TAG_SIZE, WARN_ENCRYPT_AND_MAC], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
            'hmac-sha1-etm@openssh.com': [['6.2'], [FAIL_SHA1]],
            'hmac-sha2-224': [[], [], [WARN_TAG_SIZE, WARN_ENCRYPT_AND_MAC]],
            'hmac-sha224@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha2-256': [['5.9,d2013.56,l10.7.0'], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha2-256-96': [['5.9', '6.0'], [], [WARN_ENCRYPT_AND_MAC], [INFO_REMOVED_IN_OPENSSH61]],
            'hmac-sha2-256-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],  # Only ever implemented in AsyncSSH (?).
            'hmac-sha2-256-etm@openssh.com': [['6.2']],
            'hmac-sha2-384': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha2-512': [['5.9,d2013.56,l10.7.0'], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha2-512-96': [['5.9', '6.0'], [], [WARN_ENCRYPT_AND_MAC], [INFO_REMOVED_IN_OPENSSH61]],
            'hmac-sha2-512-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],  # Only ever implemented in AsyncSSH (?).
            'hmac-sha2-512-etm@openssh.com': [['6.2']],
            'hmac-sha256-2@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha256-96@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
            'hmac-sha256-96': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
            'hmac-sha256@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha256': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha2-56': [[], [], [WARN_TAG_SIZE, WARN_ENCRYPT_AND_MAC]],
            'hmac-sha3-224': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha3-256': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha3-384': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha3-512': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha384@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha512@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-sha512': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'hmac-whirlpool': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'md5':  [[], [FAIL_PLAINTEXT]],
            'md5-8':  [[], [FAIL_PLAINTEXT]],
            'none': [['d2013.56'], [FAIL_PLAINTEXT]],
            'ripemd160':  [[], [FAIL_PLAINTEXT]],
            'ripemd160-8':  [[], [FAIL_PLAINTEXT]],
            'sha1':  [[], [FAIL_PLAINTEXT]],
            'sha1-8':  [[], [FAIL_PLAINTEXT]],
            'umac-128': [[], [], [WARN_ENCRYPT_AND_MAC]],
            'umac-128-etm@openssh.com': [['6.2']],
            'umac-128@openssh.com': [['6.2'], [], [WARN_ENCRYPT_AND_MAC]],
            'umac-32@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
            'umac-64-etm@openssh.com': [['6.2'], [], [WARN_TAG_SIZE]],
            'umac-64@openssh.com': [['4.7'], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
            'umac-96@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
        }
    }

    @staticmethod
    def get_db() -> Dict[str, Dict[str, List[List[Optional[str]]]]]:
        '''Returns a copy of the MASTER_DB that is private to the calling thread.  This prevents multiple threads from polluting the results of other threads.'''
        calling_thread_id = threading.get_ident()

        if calling_thread_id not in SSH2_KexDB.DB_PER_THREAD:
            SSH2_KexDB.DB_PER_THREAD[calling_thread_id] = copy.deepcopy(SSH2_KexDB.MASTER_DB)

        return SSH2_KexDB.DB_PER_THREAD[calling_thread_id]

    @staticmethod
    def thread_exit() -> None:
        '''Deletes the calling thread's copy of the MASTER_DB.  This is needed because, in rare circumstances, a terminated thread's ID can be re-used by new threads.'''

        calling_thread_id = threading.get_ident()

        if calling_thread_id in SSH2_KexDB.DB_PER_THREAD:
            del SSH2_KexDB.DB_PER_THREAD[calling_thread_id]


from collections import OrderedDict
from odoo import fields, http, _
from odoo.exceptions import AccessError
from odoo.http import request, request, route
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
from odoo.osv.expression import OR

class CustomerPortal(CustomerPortal):

    def _prepare_portal_layout_values(self):
        values = super(CustomerPortal, self)._prepare_portal_layout_values()
        expense_sheet = request.env['hr.expense.sheet']
        user = request.env.user
        employee = request.env['hr.employee'].sudo().search([('user_id', '=', user.id)])
        expense_sheet_count = expense_sheet.sudo().search_count([('employee_id', '=', employee.id)])
        values.update({
            'expense_sheet_count': expense_sheet_count,

        })
        return values

    @http.route(['/expence_sheet/list/expenses', '/expence_sheet/list/expenses/page/<int:page>'], type='http',
                auth="public", website=True)
    def portal_expense_sheet(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, search=None,
                             search_in='content', **kw):
        expenses_sheet = request.env['hr.expense.sheet']
        domain = []
        values = {}
        user = request.env.user
        employee = request.env['hr.employee'].sudo().search([('user_id', '=', user.id)])
        if search and search_in:
            search_domain = []
            if search_in in ('content', 'all'):
                search_domain = OR([search_domain, [('name', 'ilike', search)]])
            domain += search_domain

        domain += ([('employee_id', '=', employee.id)])
        expense_sheet_record = request.env['hr.expense.sheet'].sudo().search(domain)
        searchbar_sortings = {
            'accounting_date': {'label': _('Newest'), 'order': 'accounting_date desc'},
            'name': {'label': _('Title'), 'order': 'name'},
            'status': {'label': _('Status'), 'order': 'state'},
        }

        searchbar_filters = {
            'all': {'label': _('All'), 'domain': []},
        }

        searchbar_inputs = {
            'content': {'input': 'content', 'label': _('Search  (in Content)')},
            'employee_id': {'input': 'employee_id', 'label': _('Search in Employee')},
            'accounting_date': {'input': 'accounting_date', 'label': _('Search in Date')},
            'all': {'input': 'all', 'label': _('Search in All')},
        }

        if not sortby:
            sortby = 'name'
        order = searchbar_sortings[sortby]['order']
        if not filterby:
            filterby = 'all'
        domain += searchbar_filters[filterby]['domain']

        if search and search_in:
            search_domain = []
            if search_in in ('content', 'all'):
                search_domain = OR([search_domain, [('name', 'ilike', search)]])
            if search_in in ('employee_id', 'all'):
                search_domain = OR([search_domain, [('employee_id', 'ilike', search)]])
            if search_in in ('accounting_date', 'all'):
                search_domain = OR([search_domain, [('accounting_date', 'ilike', search)]])
            domain += search_domain

        expense_sheet_count = expenses_sheet.sudo().search_count([('employee_id', '=', employee.id)])
        pager = portal_pager(
            url="/expence_sheet/list/expenses",
            url_args={'sortby': sortby, 'filterby': filterby},
            total=expense_sheet_count,
            page=page,
            step=self._items_per_page
        )
        expenses_sheet = request.env['hr.expense.sheet'].sudo().search(domain, order=order, limit=self._items_per_page,
                                                                       offset=pager['offset'])

        values.update({
            'date': date_begin,
            'page_name': 'expenses_sheet',
            'pager': pager,
            'default_url': '/expence_sheet/list/expenses',
            'searchbar_sortings': searchbar_sortings,
            'expense_sheet_record': expenses_sheet,
            'searchbar_inputs': searchbar_inputs,
            'search_in': search_in,
            'sortby': sortby,
            'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
            'filterby': filterby,
        })
        return request.render("portal_user_expense_submit.list_expenses_sheet", values)

    @http.route(['/my/expense_sheet_form/<int:order>'], type='http', auth="public", website=True)
    def my_sheet_expense(self, order=None, **kw):
        values = {}
        expense_sheet_form = http.request.env['hr.expense.sheet']
        values.update({
            'page_name': 'form_expense',
            'expense_sheet_form': expense_sheet_form.sudo().browse([order]),
        })

        return http.request.render('portal_user_expense_submit.expences_sheet_form_view', values)

    @http.route(['/my/expense_sheet/<int:order>'], type='http', auth="user", website=True)
    def expense_sheet_print(self, order, **kw):
        pdf, _ = request.env.ref('hr_expense.action_report_hr_expense_sheet').sudo().render_qweb_pdf([order])
        pdfhttpheaders = [
            ('Content-Type', 'application/pdf'),
            ('Content-Length', len(pdf)),
        ]
        return request.make_response(pdf, headers=pdfhttpheaders)

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:


import datetime
def printTimeStamp(name):
    print('prodaivoda yan prodaivoda dan: ' + name)
    print('Час компіляції: ' + str(datetime.datetime.now()))

x = int(input("Год: "))
y = int(input("Месяц: "))
z = int(input("День: "))
today = datetime.date(x, y, z)
tomorrow = today + datetime.timedelta(days=1)

print("Следуйщий день " + str(tomorrow))


from __future__ import annotations

import os
from datetime import datetime

from airflow import DAG
from airflow.models.baseoperator import chain
from airflow.providers.amazon.aws.operators.ec2 import EC2StartInstanceOperator, EC2StopInstanceOperator
from airflow.providers.amazon.aws.sensors.ec2 import EC2InstanceStateSensor

INSTANCE_ID = os.getenv("INSTANCE_ID", "instance-id")

with DAG(
    dag_id='example_ec2',
    start_date=datetime(2021, 1, 1),
    tags=['example'],
    catchup=False,
) as dag:
    # [START howto_operator_ec2_start_instance]
    start_instance = EC2StartInstanceOperator(
        task_id="ec2_start_instance",
        instance_id=INSTANCE_ID,
    )
    # [END howto_operator_ec2_start_instance]

    # [START howto_sensor_ec2_instance_state]
    instance_state = EC2InstanceStateSensor(
        task_id="ec2_instance_state",
        instance_id=INSTANCE_ID,
        target_state="running",
    )
    # [END howto_sensor_ec2_instance_state]

    # [START howto_operator_ec2_stop_instance]
    stop_instance = EC2StopInstanceOperator(
        task_id="ec2_stop_instance",
        instance_id=INSTANCE_ID,
    )
    # [END howto_operator_ec2_stop_instance]

    chain(start_instance, instance_state, stop_instance)


import argparse
from textwrap import dedent

from handlers.spotify import Spotify
from handlers.youtube import Youtube

def spotify_to_yt(url: str, name: str = None):
    raise NotImplementedError

def yt_to_spotify(url: str, name: str = None):
    yt = Youtube()
    spotify = Spotify('b4eab4a6ee4e47c8acb628285fa1d716', '37a562b3a9af4af09fd425b64d7236ad')

    tracks = yt.get_playlist_tracks(url)
    spotify_uris = [spotify.search(track['title'], track['artist']) for track in tracks]

    name = name or input('Playlist name: ')

    spotify_playlist_url = spotify.create_playlist(name, spotify_uris)
    print(f'Playlist created: {spotify_playlist_url}')

def main():
    parser = argparse.ArgumentParser(description='Transfer playlists between Spotify and YouTube Music')
    parser.add_argument(
        '-u',
        '--url',
        type=str,
        metavar='URL',
        help='URL of the playlist to transfer'
    )
    parser.add_argument(
        '-d',
        '--direction',
        type=str,
        choices=['yt-to-spotify', 'spotify-to-yt'],
        metavar='DIRECTION',
        help='Direction of the transfer: [yt-to-spotify, spotify-to-yt]'
    )
    parser.add_argument(
        '-n',
        '--name',
        type=str,
        metavar='NAME',
        help='Name of the playlist to create'
    )
    args = parser.parse_args()

    direction = args.direction or 'yt-to-spotify' if int(input(dedent('''
    Transfer direction:
    1. YouTube Music to Spotify
    2. Spotify to YouTube Music
    Enter your choice: ''')).strip()) == 1 else 'spotify-to-yt'
    url = args.url or input('Playlist URL: ')

    if direction not in ['yt-to-spotify', 'spotify-to-yt']:
        raise ValueError('Invalid direction')

    if direction == 'yt-to-spotify':
        yt_to_spotify(url, args.name)
        return

    spotify_to_yt(url, args.name)

if __name__ == '__main__':
    main()


from collections import namedtuple

from construct import Struct, Int8ub, Int16ub, Int24ub, BytesInteger, Bytes, Enum, Byte, Switch, Prefixed, \
    GreedyBytes, IfThenElse, GreedyRange, Default

from . import tls
from .helper import Extensions
from ..const import tls as const_tls, dtls as const_dtls

# from dtls.constructs.old_adapter import Pre

Cookie = Prefixed(Int8ub, Default(GreedyBytes, b''))

ProtocolVersion = Enum(Int16ub, const_dtls.ProtocolVersion)

ClientHello = Struct(
    client_version=ProtocolVersion,
    random=Bytes(32),  # Random,
    session_id=tls.SessionID,
    cookie=Cookie,
    cipher_suites=tls.CipherSuites,
    compression_methods=tls.CompressionMethods,
    extension=Extensions,
)

HelloVerifyRequest = Struct(
    server_version=ProtocolVersion,
    cookie=Cookie,
)

Handshake = Struct(
    handshake_type=Enum(Byte, const_tls.HandshakeType),
    length=Int24ub,
    message_sequence=Int16ub,
    fragment_offset=Default(Int24ub, 0),
    fragment_length=Default(Int24ub, lambda ctx: ctx.length),
    fragment=IfThenElse(
        lambda ctx: ctx.length == ctx.fragment_length,
        Switch(lambda ctx: int(ctx.handshake_type), {
            const_tls.HandshakeType.CLIENT_HELLO.value: ClientHello,
            const_tls.HandshakeType.HELLO_VERIFY_REQUEST.value: HelloVerifyRequest,
            const_tls.HandshakeType.SERVER_HELLO.value: tls.ServerHello,
            const_tls.HandshakeType.CERTIFICATE.value: tls.Certificate,
            const_tls.HandshakeType.SERVER_HELLO_DONE.value: tls.ServerHelloDone,
            const_tls.HandshakeType.FINISHED.value: tls.Finished
        }, default=Bytes(lambda ctx: ctx.fragment_length)),
        Bytes(lambda ctx: ctx.fragment_length)
    )
)

RawHandshake = Struct(
    handshake_type=Enum(Byte, const_tls.HandshakeType),
    length=Int24ub,
    message_seq=Int16ub,
    fragment_offset=Int24ub,
    fragment_length=Int24ub,
    fragment=Bytes(lambda ctx: ctx.fragment_length)
)

Plaintext = Struct(
    type=tls.ContentType,
    version=ProtocolVersion,
    epoch=Int16ub,
    sequence_number=BytesInteger(6),
    fragment=Prefixed(
        Int16ub,
        Switch(lambda ctx: ctx.type.intvalue, {
            const_tls.ContentType.CHANGE_CIPHER_SPEC.value: GreedyBytes,
            const_tls.ContentType.ALERT.value: tls.Alert,
            const_tls.ContentType.HANDSHAKE.value: Handshake,
            const_tls.ContentType.APPLICATION_DATA.value: GreedyBytes
        }, default=GreedyBytes))
)

RawPlaintext = Struct(
    type=tls.ContentType,
    version=ProtocolVersion,
    epoch=Int16ub,
    sequence_number=BytesInteger(6),
    fragment=Prefixed(
        Int16ub,
        GreedyBytes
    )
)

RawDatagram = GreedyRange(RawPlaintext)
Datagram = GreedyRange(Plaintext)

AnswerRecord = namedtuple('AnswerRecord', ('content_type', 'epoch', 'fragment'))

TLSCompressed = Struct(
    type=tls.ContentType,
    version=ProtocolVersion,
    epoch=Int16ub,
    sequence_number=BytesInteger(6),
    fragment=Prefixed(
        Int16ub,
        GreedyBytes
    )
)

TLSCiphertext = Struct(
    type=tls.ContentType,
    version=ProtocolVersion,
    epoch=Int16ub,
    sequence_number=BytesInteger(6),
    fragment=Prefixed(
        Int16ub,
        Switch(lambda ctx: ctx._params.security_parameters.cipher_type, {
            'block': tls.GenericBlockCipher,
            # const_tls.CipherType.aead: GenericAEADCipher
        }, default=GreedyBytes))
)


from models import Player, Vote, Team, UpdateRun
from django.conf import settings
from django.db.models import Count, Sum
import re
import json
import urllib
import os
import logging
import time

logger = logging.getLogger(__name__)

def update():
    logger.info('Starting database update...')
    start = time.time()
    init_teams()
    no_new_p = update_players()
    no_new_v, no_new_o = update_votes()
    update_orphan_players()
    end = time.time()
    duration = float('%.1f' % (end-start))
    update_run = UpdateRun(duration=duration,
                           no_new_votes=no_new_v,
                           no_new_players=no_new_p,
                           no_new_orphans=no_new_o)
    update_run.save()
    logger.info('Database updated!')
    logger.info(update_run)

def drop_db():
    Team.objects.all().delete()
    Player.objects.all().delete()
    Vote.objects.all().delete()

def init_teams():
    '''
    Initialize the Team table with the team names retrieved from Kimono.
    It removes all the entries and re-adds them.
    '''
    logger.info('Initializing teams...')
    url = settings.KIMONO['teams_url']
    teams = _get_results_collection1(url)
    teams_name = [team['name'] for team in teams]
    for team_name in teams_name:
        if not Team.objects.filter(name__iexact=team_name).exists():
            t = Team()
            t.name = team_name
            t.save()

def update_players():
    '''
    Updates the Player table with the json retrieved from Kimono.
    If a player is not in the table it creates one.
    Returns the number of new palyers added to the db
    '''
    logger.info('Updating players...')
    url = settings.KIMONO['players_url']
    players = _get_results_collection1(url)
    logger.info(' - Updating database...')
    no_new_players = 0
    for player in players:
        p = Player()
        # If the player is already in the db, using the same pk end up updating
        # the past value
        p.id = _id_from_url(player['name']['href'])
        if not Player.objects.filter(pk=p.id).exists():
            no_new_players += 1
        p.name = player['name']['text']
        p.role = _fix_role(player['role'])
        p.team = Team.objects.get(name__iexact=player['team'])
        p.price = player['price']
        # Replacing the '-' character with 0 in remaining fields
        p.attendances = _fix_zero(player['attendances'])
        p.gol = _fix_zero(player['gol'])
        p.assist = _fix_zero(player['assist'])
        p.yellow_cards = _fix_zero(player['yellow_cards'])
        p.red_cards = _fix_zero(player['red_cards'])
        p.penalties_kicked = _fix_zero(player['penalties_kicked'])
        p.penalties_scored = _fix_zero(player['penalties_scored'])
        p.penalties_missed = _fix_zero(player['penalties_missed'])
        p.penalties_saved = _fix_zero(player['penalties_saved'])
        p.vote_avg = _fix_zero(player['vote_avg'])
        p.magicvote_avg = _fix_zero(player['magicvote_avg'])
        # Storing on the db
        p.save()
    return no_new_players

def update_votes():
    '''
    Updates the Vote table with the json retrieved from Kimono.
    If a vote is not in the table it creates one.
    Returns the number of new votes and new orphan players added to the db
    '''
    logger.info('Updating votes...')
    url = settings.KIMONO['votes_url']
    votes = _get_results_collection1(url)
    # Keeping a list of players with votes but not present in the Player table
    # so that they could be added later
    logger.info(' - Updating database...')
    no_new_votes = 0
    no_new_orphans = 0
    for vote in votes:
        p_id = _id_from_url(vote['name']['href'])
        v_day = _day_from_url(vote['url'])
        # Checking if the vote already exists. If not, creates a new one, if it
        # exists it will get the current vote and update it
        try:
            v = Vote.objects.get(player__pk=p_id, day=v_day)
        except Vote.DoesNotExist:
            v = Vote()
            no_new_votes += 1
        # Creating a orphan player if there is not a player for this vote
        try:
            p = Player.objects.get(pk=p_id)
        except Player.DoesNotExist:
            p = Player(pk=p_id)
            p.role = _fix_role(vote['role'])
            p.save()
            no_new_orphans += 1
        v.player = p
        v.vote = _fix_zero(vote['vote'])
        v.gol = _fix_zero(vote['gol'])
        v.assist = _fix_zero(vote['assists'])
        v.penalties_scored_saved = _fix_zero(vote['penalties_scored_saved'])
        v.penalties_missed = _fix_zero(vote['penalties_missed'])
        v.own_gol = _fix_zero(vote['own_gol'])
        v.yellow_cards = _fix_zero(vote['yellow_cards'])
        v.red_cards = _fix_zero(vote['red_cards'])
        v.magicvote = _fix_zero(vote['own_gol'])
        v.day = v_day
        v.sub_in = _sub_in(vote['in']['class'])
        v.sub_out = _sub_out(vote['out']['class'])
        # Storing on the db
        v.save()
    return no_new_votes, no_new_orphans

def update_orphan_players():
    '''
    Finds players with no team and updates their stats querying the votes
    table
    '''
    orphans = Player.objects.exclude(team__isnull=False)
    for p in orphans:
        p.name = _name_from_id(p.id)
        p.seriea = False
        p.price = 0
        v = Vote.objects
        ag = v.filter(player__pk=p.id).aggregate(Count('pk'),
                                                 Sum('gol'),
                                                 Sum('assist'),
                                                 Sum('penalties_scored_saved'),
                                                 Sum('penalties_missed'),
                                                 Sum('own_gol'),
                                                 Sum('yellow_cards'),
                                                 Sum('red_cards'),
                                                 Sum('vote'),
                                                 Sum('magicvote'),)
        p.attendances = ag['pk__count']
        p.gol = ag['gol__sum']
        p.assist = ag['assist__sum']
        if p.role == 'P':
            p.penalties_saved = ag['penalties_scored_saved__sum']
        else:
            p.penalties_scored = ag['penalties_scored_saved__sum']
        p.penalties_missed = ag['penalties_missed__sum']
        p.own_gol = ag['own_gol__sum']
        p.yellow_cards = ag['yellow_cards__sum']
        p.red_cards = ag['red_cards__sum']
        p.vote = ag['vote__sum'] / p.attendances
        p.magicvote = ag['magicvote__sum'] / p.attendances
        p.save()

def _name_from_id(p_id):
    '''
    Uses the id to generate the name of the Player
    '''
    return ' '.join(p_id.split('_')[:-1]).title()

def _get_results_collection1(url, offset=0):
    '''
    Given an url it returns the the array 'collection1' that is present in the
    results from Kimono
    Kimono has a result limit of 2500 rows. If that limit is reached
    it recursively calls the API with increasing offsets
    '''
    logger.info(' - Contacting Kimono %s with offset %s' % (url, offset))
    key = os.environ.get('KIMONO_API_KEY')
    fp = urllib.urlopen(url + '?apikey=' + key + '&kimoffset=%i' % (offset))
    results = json.load(fp)
    collection1 = results['results']['collection1']
    if results['count'] == 2500:
        new_collection1 = _get_results_collection1(url, offset=offset + 2500)
        collection1.extend(new_collection1)
    return collection1

def _fix_zero(value):
    '''
    Replaces the character '-' with the '0'
    '''
    # Match the '-' which are not followed by a number
    regex = '-(?![0-9])'
    return re.sub(regex, '0', value)

def _fix_role(value):
    '''
    Converting 'T (C)' or 'T (A)' to 'A' or 'C'. If there is no PDCA, it return
    the same value
    '''
    result = re.sub('[^PDCA]', '', value)
    return value if not result else result

def _id_from_url(url):
    '''
    Uses the last token of the URL to generate the id to use as a primary key
    '''
    return url.split('/')[-1]

def _day_from_url(url):
    '''
    Uses the last token of the URL to generate the number of the day
    '''
    return url.split('/')[-1].split('-')[-1]

def _sub_in(sub_class):
    return "in" in sub_class

def _sub_out(sub_class):
    return "out" in sub_class


from __future__ import print_function
from pyimagesearch.shapedetector import ShapeDetector
from imutils.video import WebcamVideoStream
from imutils.video import FPS
import cv2
import numpy as np
import os
import subprocess as sp
import serial
import time
import imutils
import RPi.GPIO as GPIO

#sets up variables:
ser = serial.Serial('/dev/ttyACM0', 9600)
stateDelivering = False
lowPower = 0
lookingForSign = 0
stateChecking = 0
print("starting python")
print("sleeping for 2 sec...")

#sets up threaded image loading and custom picture width "w"
w = 200
vs = WebcamVideoStream(src=0).start()

# Defines color filters for different color ranes
lr_b = 0
lg_b = 120
lb_b = 190

hr_b = 50
hg_b = 230
hb_b = 255

############## OLD VALUES USED
#lr_b = 0
#lg_b = 130
#lb_b = 160

#hr_b = 130
#hg_b = 255
#hb_b = 255

lr_r = 200
lg_r = 0
lb_r = 0

hr_r = 255
hg_r = 100
hb_r = 145

lr_g = 0
lg_g = 125
lb_g = 0

hr_g = 130
hg_g = 255
hb_g = 165

# Defines numpy array with color filter values
lower_color_blue = np.array([lb_b, lg_b, lr_b], dtype=np.uint8)
upper_color_blue = np.array([hb_b, hg_b, hr_b], dtype=np.uint8)
lower_color_green = np.array([lb_g, lg_g, lr_g], dtype=np.uint8)
upper_color_green = np.array([hb_g, hg_g, hr_g], dtype=np.uint8)
lower_color_red = np.array([lb_r, lg_r, lr_r], dtype=np.uint8)
upper_color_red = np.array([hb_r, hg_r, hr_r], dtype=np.uint8)

#image filtering parameter
max_slider = 40

# current robot line coordinates is defined with null values.
hx1 = 0
hx2 = 0
hy1 = 0
hy2 = 0
# highLineY is a temporary value which remembers max Y value of previous line. A line can not be selected unless it has a higher Y value than this line.
# This variable is slowly decreased in the code if the robot does not detect any valid lines, until it eventually reaches 0 and the robot will detect and blue line it sees.
highLineY = 0
tempX = 320
old_tempX = 320

# Sets up GPIO (hardware pins on raspberrypi)
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(17,GPIO.IN,pull_up_down=GPIO.PUD_UP)

GPIO.setup(18, GPIO.OUT)
GPIO.setup(27,GPIO.OUT)
GPIO.outout(27,0)
servoPWM = GPIO.PWM(18, 50) # channel 0, 50hz PWM frequency.
servoPWM.start(93.2)
servoPWM.ChangeDutyCycle(93.2)

time.sleep(2)

def sendLineInfo(newX,oldX,width):
    try:
        if (newX != oldX): # if tempX has changed from last instance, new line has been found / line has moved
            ser.write(chr(int(newX*(126/width))+1).encode())
            #print("writing serial value: " + str(int(newX*(126/width))+1)).

    except:
        print("No new line info. No serial written.")

def cleanUp():
    cv2.destroyAllWindows()
    vs.stop()
    GPIO.cleanup()

def arduinoCallback1(channel):
    global lookingForSign
    global lowPower
    global stateDelivering
    global count
    global stateChecking
    ser.reset_output_buffer()
    print("interrupt triggered...")
    read = ser.read()
    time.sleep(1)
    if read == b'\x01':
        lookingForSign = 1
        stateChecking = 0
        print("metal detected.")
        #ser.write(chr(int(3)).encode())####################################################################################################################################
    elif read == b'\x02':
        stateChecking = 1
        lookingForSign = 0

GPIO.add_event_detect(17, GPIO.FALLING, callback=arduinoCallback1, bouncetime=2000)

 #0: following line, 1: looking for sign, 2: picking up package, 3: delivering package.
# While loop for main logic

while True:
    packageSymbol = "triangle" # to predefine packagename, enter something other than ""
    image = vs.read()
    image = imutils.resize(image, width=w)
    # Image parameters / set-up for selecting colors and finding liness
    if stateChecking == 1:
        if packageSymbol == "":
            #TURN CAMERA UP
            compare = 0
            foundSign = 0
            triangles = 0
            squares = 0

            for i in range(10):
                image = vs.read()

                mask1 = cv2.inRange(image, lower_color_red, upper_color_red)
                resized = imutils.resize(mask1, width=300)
                ratio = mask1.shape[0] / float(resized.shape[0])

                # convert the resized image to grayscale, blur it slightly,
                # and threshold it
                #gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
                gray = resized
                blurred = cv2.GaussianBlur(gray, (7, 7), 0)
                thresh = cv2.threshold(blurred, 200, 255, cv2.THRESH_BINARY)[1] #60, 255 default

                # find contours in the thresholded image and initialize the
                # shape detector
                cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                    cv2.CHAIN_APPROX_SIMPLE)
                cnts = imutils.grab_contours(cnts)
                sd = ShapeDetector()
                try:
                    # loop over the contours
                    for c in cnts:
                        # compute the center of the contour, then detect the name of the
                        # shape using only the contour
                        M = cv2.moments(c)
                        cX = int((M["m10"] / M["m00"]) * ratio)
                        cY = int((M["m01"] / M["m00"]) * ratio)
                        shape = sd.detect(c)

                        if shape=="triangle":
                            triangles += 1
                        elif shape =="rectangle" or shape == "square":
                            squares += 1
                        compare += 1

                        # multiply the contour (x, y)-coordinates by the resize ratio,
                        # then draw the contours and the name of the shape on the image
                        c = c.astype("float")
                        c *= ratio
                        c = c.astype("int")
                        #cv2.drawContours(image, [c], -1, (0, 0, 255), 2)
                        #cv2.putText(image, shape, (cX, cY), cv2.FONT_HERSHEY_SIMPLEX,
                        #    0.5, (0, 0, 0), 2)

                except:
                    pass
                cv2.imshow("thresh",thresh)
                #cv2.imshow("Image1", image)
                #cv2.imshow("mask1", mask1)
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    ser.write(chr(int(0)).encode()) # sending 0 over serial to stop movement.
                    break # Stops program if button "q" is pressed.
                time.sleep(.200)
            #analyse colors here..
            print("detected following shapes:")
            print(compare)
            print(squares)
            print(triangles)

            if compare < 20 and compare > 6:
                if triangles > squares:
                    packageSymbol = "triangle"
                    print("detected triangle!")
                if triangles < squares:
                    packageSymbol = "rectangle"
                    print("detected rectangle!")
                #stateChecking = 0
                # MOVE CAMERA DOWN
            else:
                print("didn't find sign, checking again...")

            if not packageSymbol == "":
                stateChecking = 0
                GPIO.outout(27,1)
                ser.write(chr(int(63)))
                time.sleep(0.5)
                GPIO.outout(27,0)
                ser.write(chr(int(0)))
        if not packageSymbol == "":
            print("package already defined as: " + packageSymbol)
            stateChecking = 0
            GPIO.outout(27,1)
            ser.write(chr(int(63)))
            time.sleep(0.5)
            GPIO.outout(27,0)
            ser.write(chr(int(0)))

    elif lookingForSign == 1:
        green = 0
        red = 0
        compare = 0
        for i in range(10):
            for l in range(2):
                image = vs.read()

                if l == 0:
                    mask1 = cv2.inRange(image, lower_color_green, upper_color_green)
                elif l == 1:
                    mask1 = cv2.inRange(image, lower_color_red, upper_color_red)
                resized = imutils.resize(mask1, width=300)
                ratio = mask1.shape[0] / float(resized.shape[0])

                # convert the resized image to grayscale, blur it slightly,
                # and threshold it
                #gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
                gray = resized
                blurred = cv2.GaussianBlur(gray, (7, 7), 0)
                thresh = cv2.threshold(blurred, 200, 255, cv2.THRESH_BINARY)[1] #60, 255 default

                # find contours in the thresholded image and initialize the
                # shape detector
                cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                    cv2.CHAIN_APPROX_SIMPLE)
                cnts = imutils.grab_contours(cnts)
                sd = ShapeDetector()

                try:
                    # loop over the contours
                    for c in cnts:
                        # compute the center of the contour, then detect the name of the
                        # shape using only the contour
                        M = cv2.moments(c)
                        cX = int((M["m10"] / M["m00"]) * ratio)
                        cY = int((M["m01"] / M["m00"]) * ratio)
                        shape = sd.detect(c)

                        if (shape==packageSymbol and l==0):
                            green = green + 1
                        elif (shape==packageSymbol and l==1):
                            red = red + 1

                        compare = compare + 1

                        # multiply the contour (x, y)-coordinates by the resize ratio,
                        # then draw the contours and the name of the shape on the image
                        c = c.astype("float")
                        c *= ratio
                        c = c.astype("int")
                        #cv2.drawContours(image, [c], -1, (0, 0, 255), 2)
                        #cv2.putText(image, shape, (cX, cY), cv2.FONT_HERSHEY_SIMPLEX,
                        #    0.5, (0, 0, 0), 2)

                except:
                    pass

                cv2.imshow("thresh",thresh)
                #cv2.imshow("Image1", image).
                #cv2.imshow("mask1", mask1)
                time.sleep(.200)
        #analyse colors here..
        print("shape info:")
        print(compare)
        print(green)
        print(red)

        if compare > 30 and compare < 55:
            if (compare / (green+1)) < 5:
                print("turning right...")
                GPIO.outout(27,1)
                ser.write(chr(int(126)).encode())
                lookingForSign = 0
                time.sleep(0.6)
                GPIO.outout(27,0)
                packageSymbol = ""
            elif (compare / (red+1)) < 5:
                print("turn left...")
                GPIO.outout(27,1)
                ser.write(chr(int(2)).encode())
                lookingForSign = 0
                time.sleep(0.6)
                GPIO.outout(27,0)
                packageSymbol = ""
            else:
                print("bad shapes...")
        else:
            print("too many or not enough!")

    elif lookingForSign == 0 and stateChecking == 0:

        mask = cv2.inRange(image, lower_color_blue, upper_color_blue) # find colors between the color limits defined earlier. This image is black and white.

        edges = cv2.Canny(mask,50,100) # Find edges from the previously defined mask.
        lines = cv2.HoughLinesP(edges, 1, np.pi/180, max_slider, minLineLength=60, maxLineGap=100) # This command finds lines from the edges found previously. Lines becomes an array of line start/end coordinates

        try:
            for index, line in enumerate(lines): # This for-loop finds the line with the highest (lowest on screen) Y-coordinate. This will become the line the robot will follow as it's the line closest to the robot.
                #print("a")
                x1, y1, x2, y2 = line[0]
                if (y1 > y2):
                    if (y1 > highLineY): # If new Y coordinate is higher than previously highest Y coordinate, dump previous line and replace its line coordinates with the newly found line.
                        #print("")
                        hx1 = x1
                        tempX = x1
                        hx2 = x2
                        hy1 = y1
                        hy2 = y2
                        highLineY = y1
                elif (y2 > y1):
                    if (y2 > highLineY):
                        #print("")
                        hx1 = x1
                        hx2 = x2
                        tempX = x2
                        hy1 = y1
                        hy2 = y2
                        highLineY = y2
                else:
                    #print("what")
                    cv2.line(image, (hx1, hy1), (hx2, hy2), (255, 0, 255), 5)
            if (highLineY > 10): # This slowly reduces the previously highest Y coordinate. This mechanism is neccesary as the robot would otherwise quickly select a new totally different line, if it for a moment can't see it's previous line.
                highLineY = highLineY - 5

            cv2.line(image, (hx1, hy1), (hx2, hy2), (255, 0, 255), 5) # Draws the new line on the "img" windows.

        except:
            #print("can't find line.")
            if (highLineY > 10):
                highLineY = highLineY - 5

        if not stateChecking and not lookingForSign:
            sendLineInfo(newX = tempX, oldX = old_tempX, width = w)

        old_tempX = tempX

        try:
            cv2.imshow("Res1", image) # Displays image windows
            cv2.imshow("mask", mask) # Displays the masked window (black and white filter)
        except:
            print("can't show camera...")

    if cv2.waitKey(1) & 0xFF == ord('q'):
        ser.write(chr(int(0)).encode()) # sending 0 over serial to stop movement.
        break # Stops program if button "q" is pressed.

cleanUp()


import numpy as np
import matplotlib.pyplot as plt

def life():
    # print('Enter seeds location:')
    # M = eval(input())

    # For test
    M = [[1, 0, 1, 1, 1],
         [0, 0, 0, 1, 0],
         [0, 0, 1, 0, 0],
         [0, 1, 1, 1, 0],
         [0, 0, 1, 0, 0]]
    M = np.pad(M, ((1, 1), (1, 1)), 'constant')
    M = np.array(M)
    L, W = np.shape(M)
    X = np.zeros((L, W))
    for i in range(1, L - 1):
        for j in range(1, W - 1):
            sub_matrix = M[i - 1:i + 2, j - 1:j + 2]
            # Any live cell with fewer than two live neighbors dies, as if by underpopulation
            if M[i, j] == 1 and np.count_nonzero(sub_matrix == 1) < 2:
                X[i, j] = 0
            # Any live cell with two or three live neighbors lives on to the next generation
            if M[i, j] == 1 and np.count_nonzero(sub_matrix == 1) in (2, 3):
                X[i, j] = M[i, j]
            # Any live cell with more than three live neighbors dies, as if by overpopulation
            if M[i, j] == 1 and np.count_nonzero(sub_matrix == 1) > 3:
                X[i, j] = 0
            # Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction
            if M[i, j] == 0 and np.count_nonzero(sub_matrix == 1) == 3:
                X[i, j] = 1
    X = X[1:L - 1, 1:W - 1]
    print("here is the next patten")
    print(X)
    img = plt.imshow(X)
    img.set_cmap('winter')
    plt.show()
    return X

# Run file
life()


from collections import namedtuple
from unittest.mock import MagicMock

import pytest

from momapper import MappedClass, Field
from momapper.types import StringType, IntType

class Example(MappedClass):
    name = Field("name", StringType)
    count = Field("count", IntType)

@pytest.fixture
def mocked_example(monkeypatch):
    MockedExample = namedtuple("MockedExample", "impl, made_doc, validated")
    with monkeypatch.context() as m:
        made_doc = object()
        m.setattr(Example, "make_document", MagicMock(return_value=made_doc))

        validated = object()
        m.setattr(Example, "validate", MagicMock(return_value=validated))
        yield MockedExample(impl=Example, made_doc=made_doc, validated=validated)

class Cop(MappedClass):
    name = Field("the_name", StringType)
    surname = Field("the_surname", StringType)
    skill_level = Field("skill_level", IntType, required=True, if_missing=0)


from bson.codec_options import CodecOptions
from motor import motor_asyncio
from pymongo.errors import ServerSelectionTimeoutError, OperationFailure

from Bubot.Helpers.ActionDecorator import async_action
from Bubot.Helpers.ExtException import ExtException, ExtTimeoutError, KeyNotFound
from Bubot.Helpers.Helper import get_tzinfo

class Mongo:
    tzinfo = get_tzinfo()

    def __init__(self, **kwargs):
        self.client = kwargs.get('client')

    pass

    @classmethod
    async def connect(cls, device=None, *, url='mongodb://localhost:27017', **kwargs):
        if device:
            url = device.get_param('/oic/con', 'storage_url', 'mongodb://localhost:27017')
        try:
            client = motor_asyncio.AsyncIOMotorClient(url, serverSelectionTimeoutMS=5000, tz_aware=False)
            res = await client.server_info()
        except ServerSelectionTimeoutError as err:
            raise ExtTimeoutError(message='Mongo connection timeout', parent=err)
        except Exception as err:
            raise ExtException(parent=err, message='Storage not connected')
        return cls(client=client)

    async def close(self):
        self.client.close()

    async def exist_database(self, db):
        db_names = await self.client.list_database_names()
        return db in db_names

    async def exist_table(self, db, name):
        if not await self.exist_database(db):
            return False
        try:
            await self.client[db].validate_collection(name)
            return True
        except OperationFailure:
            return False

    def create_table(self, db, name):
        table = self.client[db][name]

    async def create_index(self, db, name_, keys, **kwargs):
        await self.client[db][name_].create_index(keys, **kwargs)

    async def find_data_base(self, name):
        data_bases = await self.client.list_database_names()
        if name in data_bases:
            return self.client[name]
        return None

    @async_action
    async def update(self, db, table, data, create=True, *, where=None, _action=None, **kwargs):
        if data.get('_id') or where:
            if where:
                res = await self.client[db][table].update_many(
                    where,
                    {'$set': data}, upsert=create, **kwargs)
            else:
                res = await self.client[db][table].update_one(
                    dict(_id=data['_id']),
                    {'$set': data}, upsert=create, **kwargs)
            if res.upserted_id:
                data['_id'] = res.upserted_id
        else:
            if create:
                res = await self.client[db][table].insert_one(data)
                data['_id'] = res.inserted_id
            else:
                raise KeyError
        return res

    async def push(self, db, table, uid, field, item, **kwargs):
        res = await self.client[db][table].update_one({'_id': uid}, {'$push': {field: item}}, upsert=False)
        return res

    async def pull(self, db, table, uid, field, item, **kwargs):
        res = await self.client[db][table].update_one({'_id': uid}, {'$pull': {field: item}}, upsert=False)
        return res

    def set_timezone(self, db, table):
        self.client[db][table].with_options(
            codec_options=CodecOptions(
                tz_aware=False,
                # tzinfo=self.tzinfo
            ))

    async def find_one(self, db, table, where, **kwargs):
        self.set_timezone(db, table)
        return await self.client[db][table].find_one(where, **kwargs)

    async def delete_one(self, db, table, where):
        return await self.client[db][table].delete_one(where)

    async def delete_many(self, db, table, where):
        return await self.client[db][table].delete_many(where)

    async def count(self, db, table, **kwargs):
        return await self.client[db][table].count_documents(
            kwargs.get('where', {})
        )

    @staticmethod
    def check_db_and_table(db, table, action):
        if not db:
            raise ExtException(message='db not defined', action=action)
        if not table:
            raise ExtException(message='table not defined', action=action)

    async def list(self, db, table, *, where=None, projection=None, skip=0, limit=1000, order=None, _action=None,
                   **kwargs):
        self.check_db_and_table(db, table, _action)
        self.set_timezone(db, table)
        if where is not None:
            full_text_search = where.pop('_search', None)
            if full_text_search:
                where['$text'] = {'$search': full_text_search}

        cursor = self.client[db][table].find(
            filter=where,
            projection=projection,
            skip=skip,
            limit=limit
        )
        if order:
            cursor.sort(order)
        result = await cursor.to_list(length=1000)
        await cursor.close()
        return result

    @async_action
    async def get_previous(self, db, table, *, where=None, index=None, projection=None, skip=0, limit=1000, order=None,
                           _action=None, **kwargs):
        self.check_db_and_table(db, table, _action)
        self.set_timezone(db, table)

        cursor = self.client[db][table].find(
            filter=where,

        ).sort([(index, -1)]).limit(10)
        result = await cursor.to_list(length=1000)
        await cursor.close()
        return result

    async def pipeline(self, db, table, pipeline, *, projection=None, where=None, skip=0, sort=None, limit=1000,
                       **kwargs):
        self.set_timezone(db, table)
        self.check_db(db)
        _pipeline = []
        if where:
            _pipeline.append({'$match': where})

        _pipeline += pipeline

        if projection:
            _pipeline.append({'$project': projection})
        if sort:
            _pipeline.append({'$sort': sort})
        if skip:
            _pipeline.append({'$skip': skip})
        if limit:
            _pipeline.append({'$limit': limit})

        cursor = self.client[db][table].aggregate(_pipeline)
        result = await cursor.to_list(length=limit)
        return result

    async def find_one_and_update(self, db, table, where, data, **kwargs):
        return await self.client[db][table].find_one_and_update(where, {'$set': data}, **kwargs)

    @classmethod
    def check_db(cls, db):
        if not db:
            raise KeyNotFound(message='Data base not defined')

# #https://www.programmersought.com/article/87956455420/
# def asynchronize(framework, sync_method, doc=None):
#     """Decorate `sync_method` so it accepts a callback or returns a Future.
#
#     The method runs on a thread and calls the callback or resolves
#     the Future when the thread completes.
#
#     :Parameters:
#      - `motor_class`:       Motor class being created, e.g. MotorClient.
#      - `framework`:         An asynchronous framework
#      - `sync_method`:       Unbound method of pymongo Collection, Database,
#                             MongoClient, etc.
#      - `doc`:               Optionally override sync_method's docstring
#     """
#     @functools.wraps(sync_method)
#     def method(self, *args, **kwargs):
#         loop = self.get_io_loop()
#         callback = kwargs.pop('callback', None)
#         future = framework.run_on_executor(loop,
#                                            sync_method,
#                                            self.delegate,
#                                            *args,
#                                            **kwargs)
#
#         return framework.future_or_callback(future, callback, loop)
#
#     # This is for the benefit of motor_extensions.py, which needs this info to
#     # generate documentation with Sphinx.
#     method.is_async_method = True
#     name = sync_method.__name__
#     method.pymongo_method_name = name
#     if doc is not None:
#         method.__doc__ = doc
#
#     return method


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

import json
import uuid
import os
import time
import datetime

from celery import current_task
from django.db import models
from django.conf import settings
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django_celery_beat.models import PeriodicTask

from common.utils import get_signer, get_logger
from orgs.utils import set_to_root_org
from ..celery.utils import delete_celery_periodic_task, \
    create_or_update_celery_periodic_tasks, \
    disable_celery_periodic_task
from ..ansible import AdHocRunner, AnsibleError
from ..inventory import JMSInventory

__all__ = ["Task", "AdHoc", "AdHocRunHistory"]

logger = get_logger(__file__)
signer = get_signer()

class Task(models.Model):
    """
    This task is different ansible task, Task like 'push system user', 'get asset info' ..
    One task can have some versions of adhoc, run a task only run the latest version adhoc
    """
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    name = models.CharField(max_length=128, verbose_name=_('Name'))
    interval = models.IntegerField(verbose_name=_("Interval"), null=True, blank=True, help_text=_("Units: seconds"))
    crontab = models.CharField(verbose_name=_("Crontab"), null=True, blank=True, max_length=128, help_text=_("5 * * * *"))
    is_periodic = models.BooleanField(default=False)
    callback = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Callback"))  # Callback must be a registered celery task
    is_deleted = models.BooleanField(default=False)
    comment = models.TextField(blank=True, verbose_name=_("Comment"))
    created_by = models.CharField(max_length=128, blank=True, default='')
    date_created = models.DateTimeField(auto_now_add=True, db_index=True)
    __latest_adhoc = None
    _ignore_auto_created_by = True

    @property
    def short_id(self):
        return str(self.id).split('-')[-1]

    @property
    def latest_adhoc(self):
        if not self.__latest_adhoc:
            self.__latest_adhoc = self.get_latest_adhoc()
        return self.__latest_adhoc

    @latest_adhoc.setter
    def latest_adhoc(self, item):
        self.__latest_adhoc = item

    @property
    def latest_history(self):
        try:
            return self.history.all().latest()
        except AdHocRunHistory.DoesNotExist:
            return None

    def get_latest_adhoc(self):
        try:
            return self.adhoc.all().latest()
        except AdHoc.DoesNotExist:
            return None

    @property
    def history_summary(self):
        history = self.get_run_history()
        total = len(history)
        success = len([history for history in history if history.is_success])
        failed = len([history for history in history if not history.is_success])
        return {'total': total, 'success': success, 'failed': failed}

    def get_run_history(self):
        return self.history.all()

    def run(self, record=True):
        set_to_root_org()
        if self.latest_adhoc:
            return self.latest_adhoc.run(record=record)
        else:
            return {'error': 'No adhoc'}

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        from ..tasks import run_ansible_task
        super().save(
            force_insert=force_insert, force_update=force_update,
            using=using, update_fields=update_fields,
        )

        if self.is_periodic:
            interval = None
            crontab = None

            if self.interval:
                interval = self.interval
            elif self.crontab:
                crontab = self.crontab

            tasks = {
                self.__str__(): {
                    "task": run_ansible_task.name,
                    "interval": interval,
                    "crontab": crontab,
                    "args": (str(self.id),),
                    "kwargs": {"callback": self.callback},
                    "enabled": True,
                }
            }
            create_or_update_celery_periodic_tasks(tasks)
        else:
            disable_celery_periodic_task(self.__str__())

    def delete(self, using=None, keep_parents=False):
        super().delete(using=using, keep_parents=keep_parents)
        delete_celery_periodic_task(self.__str__())

    @property
    def schedule(self):
        try:
            return PeriodicTask.objects.get(name=self.name)
        except PeriodicTask.DoesNotExist:
            return None

    def __str__(self):
        return self.name + '@' + str(self.created_by)

    class Meta:
        db_table = 'ops_task'
        unique_together = ('name', 'created_by')
        get_latest_by = 'date_created'

class AdHoc(models.Model):
    """
    task: A task reference
    _tasks: [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
    _options: ansible options, more see ops.ansible.runner.Options
    _hosts: ["hostname1", "hostname2"], hostname must be unique key of cmdb
    run_as_admin: if true, then need get every host admin user run it, because every host may be have different admin user, so we choise host level
    run_as: if not run as admin, it run it as a system/common user from cmdb
    _become: May be using become [sudo, su] options. {method: "sudo", user: "user", pass: "pass"]
    pattern: Even if we set _hosts, We only use that to make inventory, We also can set `patter` to run task on match hosts
    """
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    task = models.ForeignKey(Task, related_name='adhoc', on_delete=models.CASCADE)
    _tasks = models.TextField(verbose_name=_('Tasks'))
    pattern = models.CharField(max_length=64, default='{}', verbose_name=_('Pattern'))
    _options = models.CharField(max_length=1024, default='', verbose_name=_('Options'))
    _hosts = models.TextField(blank=True, verbose_name=_('Hosts'))  # ['hostname1', 'hostname2']
    hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host"))
    run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
    run_as = models.ForeignKey('assets.SystemUser', null=True, on_delete=models.CASCADE)
    _become = models.CharField(max_length=1024, default='', verbose_name=_("Become"))
    created_by = models.CharField(max_length=64, default='', null=True, verbose_name=_('Create by'))
    date_created = models.DateTimeField(auto_now_add=True, db_index=True)

    @property
    def tasks(self):
        try:
            return json.loads(self._tasks)
        except:
            return []

    @tasks.setter
    def tasks(self, item):
        if item and isinstance(item, list):
            self._tasks = json.dumps(item)
        else:
            raise SyntaxError('Tasks should be a list: {}'.format(item))

    @property
    def inventory(self):
        if self.become:
            become_info = {
                'become': {
                    self.become
                }
            }
        else:
            become_info = None

        inventory = JMSInventory(
            self.hosts.all(), run_as_admin=self.run_as_admin,
            run_as=self.run_as, become_info=become_info
        )
        return inventory

    @property
    def become(self):
        if self._become:
            return json.loads(signer.unsign(self._become))
        else:
            return {}

    def run(self, record=True):
        set_to_root_org()
        if record:
            return self._run_and_record()
        else:
            return self._run_only()

    def _run_and_record(self):
        try:
            hid = current_task.request.id
        except AttributeError:
            hid = str(uuid.uuid4())
        history = AdHocRunHistory(id=hid, adhoc=self, task=self.task)
        time_start = time.time()
        try:
            date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            print(_("{} Start task: {}").format(date_start, self.task.name))
            raw, summary = self._run_only()
            date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            print(_("{} Task finish").format(date_end))
            history.is_finished = True
            if summary.get('dark'):
                history.is_success = False
            else:
                history.is_success = True
            history.result = raw
            history.summary = summary
            return raw, summary
        except Exception as e:
            return {}, {"dark": {"all": str(e)}, "contacted": []}
        finally:
            history.date_finished = timezone.now()
            history.timedelta = time.time() - time_start
            history.save()

    def _run_only(self):
        runner = AdHocRunner(self.inventory, options=self.options)
        try:
            result = runner.run(
                self.tasks,
                self.pattern,
                self.task.name,
            )
            return result.results_raw, result.results_summary
        except AnsibleError as e:
            logger.warn("Failed run adhoc {}, {}".format(self.task.name, e))
            pass

    @become.setter
    def become(self, item):
        """
        :param item:  {
            method: "sudo",
            user: "user",
            pass: "pass",
        }
        :return:
        """
        # self._become = signer.sign(json.dumps(item)).decode('utf-8')
        self._become = signer.sign(json.dumps(item))

    @property
    def options(self):
        if self._options:
            _options = json.loads(self._options)
            if isinstance(_options, dict):
                return _options
        return {}

    @options.setter
    def options(self, item):
        self._options = json.dumps(item)

    @property
    def short_id(self):
        return str(self.id).split('-')[-1]

    @property
    def latest_history(self):
        try:
            return self.history.all().latest()
        except AdHocRunHistory.DoesNotExist:
            return None

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        super().save(force_insert=force_insert, force_update=force_update,
                     using=using, update_fields=update_fields)

    def __str__(self):
        return "{} of {}".format(self.task.name, self.short_id)

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return False
        fields_check = []
        for field in self.__class__._meta.fields:
            if field.name not in ['id', 'date_created']:
                fields_check.append(field)
        for field in fields_check:
            if getattr(self, field.name) != getattr(other, field.name):
                return False
        return True

    class Meta:
        db_table = "ops_adhoc"
        get_latest_by = 'date_created'

class AdHocRunHistory(models.Model):
    """
    AdHoc running history.
    """
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    task = models.ForeignKey(Task, related_name='history', on_delete=models.SET_NULL, null=True)
    adhoc = models.ForeignKey(AdHoc, related_name='history', on_delete=models.SET_NULL, null=True)
    date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time'))
    date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time'))
    timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
    is_finished = models.BooleanField(default=False, verbose_name=_('Is finished'))
    is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
    _result = models.TextField(blank=True, null=True, verbose_name=_('Adhoc raw result'))
    _summary = models.TextField(blank=True, null=True, verbose_name=_('Adhoc result summary'))

    @property
    def short_id(self):
        return str(self.id).split('-')[-1]

    @property
    def log_path(self):
        dt = datetime.datetime.now().strftime('%Y-%m-%d')
        log_dir = os.path.join(settings.PROJECT_DIR, 'data', 'ansible', dt)
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)
        return os.path.join(log_dir, str(self.id) + '.log')

    @property
    def result(self):
        if self._result:
            return json.loads(self._result)
        else:
            return {}

    @result.setter
    def result(self, item):
        self._result = json.dumps(item)

    @property
    def summary(self):
        if self._summary:
            return json.loads(self._summary)
        else:
            return {"ok": {}, "dark": {}}

    @summary.setter
    def summary(self, item):
        self._summary = json.dumps(item)

    @property
    def success_hosts(self):
        return self.summary.get('contacted', [])

    @property
    def failed_hosts(self):
        return self.summary.get('dark', {})

    def __str__(self):
        return self.short_id

    class Meta:
        db_table = "ops_adhoc_history"
        get_latest_by = 'date_start'