import argparse
import logging

from tokenizers import Tokenizer

from faser.data_utils import fstrs_to_tokeniser_training_data
from faser.eval import FaserGeneralFuncSearchEval, FaserVulnSearchEval
from faser.tokeniser import print_tokeniser_encode_example, train_bpe_tokeniser
from faser.train import FASERTrain

parser = argparse.ArgumentParser(
    prog="cli.py", description="Frontend for training and testing FASER models"
)

sub_parsers = parser.add_subparsers(dest="command")
sub_parsers.required = True

# Creating Tokenisers
parser_tokeniser = sub_parsers.add_parser(
    "tokeniser", help="train a huggingface tokeniser"
)
parser_tokeniser.add_argument(
    "-o", "--output-path", required=True, help="the name of the tokeniser"
)
parser_tokeniser.add_argument(
    "-i",
    "--input-data",
    nargs="+",
    required=True,
    help="the input data corpus to train the tokeniser",
)

parser_tokeniser.add_argument(
    "-m",
    "--max-seq-len",
    required=True,
    help="the max seq length for the tokeniser to make (including specials)",
)

# Testing Tokeniser/Encode
parser_encode = sub_parsers.add_parser(
    "encode", help="encode a string with a given tokeniser"
)
parser_encode.add_argument("input", type=str, help="the input sequence to encode")
parser_encode.add_argument("-t", "--tokeniser-path", help="path to a trained tokeniser")

# Convert function as string data to a text file
parser_fstr2txt = sub_parsers.add_parser(
    "fstr2txt",
    help="convert fstr JSON to newline delimited text file to train tokeniser",
)
parser_fstr2txt.add_argument("data_dir", help="path to data directory")
parser_fstr2txt.add_argument("-o", "--output-name", help="output name", required=True)

# Training a FASER model
parser_train = sub_parsers.add_parser("train", help="Train a FASER model")
parser_train.add_argument("--train_data", help="path to traini data", required=True)
parser_train.add_argument("--test_data", help="path to test data", required=True)
parser_train.add_argument(
    "-t", "--tokeniser_fp", help="path to tokeniser", required=True
)
parser_train.add_argument("-n", "--name", help="name of model", required=True)

## Good Defaults
parser_train.add_argument("-e", "--epochs", help="Num training epochs", default=30)
parser_train.add_argument("-b", "--batch_size", help="Training batch size", default=8)
parser_train.add_argument(
    "-lr", "--learning_rate", help="Training learning rate", default=0.00005
)
parser_train.add_argument(
    "--num_accumlation_steps", help="Steps to do gradient accumlation too", default=512
)
parser_train.add_argument(
    "--gradient_accumulation",
    help="toggle to set gradient accumlation",
    action="store_false",
)
parser_train.add_argument("--num_pos_pairs_in_batch", default=2)
parser_train.add_argument("--filter-str", default=None)

# Evaluating FASER Model - General Function Search
parser_func_search_eval = sub_parsers.add_parser(
    "fseval", help="General function search evaluation for FASER"
)

parser_func_search_eval.add_argument(
    "-n",
    "--num-eval-sp",
    type=int,
    help="Number of Search Pools to eval with",
    default=1400,
)
parser_func_search_eval.add_argument(
    "-i", "--input-dim", type=int, help="Size of input dimension", default=4096
)
parser_func_search_eval.add_argument(
    "-d", "--eval-data", type=str, help="Path to the evaluation data", required=True
)
parser_func_search_eval.add_argument(
    "-t", "--tokeniser", type=str, help="Path to the tokeniser", required=True
)
parser_func_search_eval.add_argument(
    "-m", "--model", type=str, help="Path to the model", required=True
)
parser_func_search_eval.add_argument(
    "-s",
    "--sp-size",
    type=int,
    help="Number of elements in a single search pool",
    default=100,
)
parser_func_search_eval.add_argument(
    "-f",
    "--filter-str",
    type=str,
    help="Filter which training filenames to use",
    required=False,
)

# Evaluating FASER Model - Vulnerability Function Search
parser_func_search_eval.add_argument("-v", "--verbose", action="store_true")

parser_vuln_search_eval = sub_parsers.add_parser(
    "vseval", help="Vulnerability search evaluation for FASER"
)
parser_vuln_search_eval.add_argument(
    "-i", "--input-dim", type=int, help="Size of input dimension", default=4096
)
parser_vuln_search_eval.add_argument(
    "-d", "--eval-data", type=str, help="Path to the evaluation data", required=True
)
parser_vuln_search_eval.add_argument(
    "-t", "--tokeniser", type=str, help="Path to the tokeniser", required=True
)
parser_vuln_search_eval.add_argument(
    "-m", "--model", type=str, help="Path to the model", required=True
)
parser_vuln_search_eval.add_argument(
    "-f",
    "--model-friendly-name",
    type=str,
    help="Model name for output artefacts",
    required=True,
)
args = parser.parse_args()

if args.command == "fstr2txt":
    fstrs_to_tokeniser_training_data(args.data_dir, args.output_name)

elif args.command == "tokeniser":
    tokeniser = train_bpe_tokeniser(
        args.input_data, max_seq_length=int(args.max_seq_len)
    )
    tokeniser.save(f"{args.max_seq_len}-{args.output_path}")

elif args.command == "encode":
    tokeniser = Tokenizer.from_file(args.tokeniser_path)
    print_tokeniser_encode_example(tokeniser, args.input)

elif args.command == "train":
    model = FASERTrain(
        name=args.name,
        train_data_fp=args.train_data,
        test_data_fp=args.test_data,
        tokeniser_fp=args.tokeniser_fp,
        batch_size=args.batch_size,
        num_pos_pairs_in_batch=args.num_pos_pairs_in_batch,
        learning_rate=args.learning_rate,
        num_training_epochs=args.epochs,
        num_accumlation_steps=args.num_accumlation_steps,
        filter_str=args.filter_str,
    )

    model.train()

elif args.command == "fseval":
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)

    eval_run = FaserGeneralFuncSearchEval(
        torch_model_bin_fp=args.model,
        eval_data_fp=args.eval_data,
        max_seq_len=args.input_dim,
        tokeniser_fp=args.tokeniser,
        filter_str=args.filter_str,
        num_eval_search_pools=args.num_eval_sp,
        search_pool_size=args.sp_size,
    )

    eval_run.eval()

elif args.command == "vseval":
    eval_run = FaserVulnSearchEval(
        torch_model_bin_fp=args.model,
        eval_data_dir=args.eval_data,
        max_seq_len=args.input_dim,
        tokeniser_fp=args.tokeniser,
        model_friendly_name=args.model_friendly_name,
    )

    eval_run.rank()


from web3 import Web3
import brownie
from brownie import Contract as bContract
from brownie import accounts as bAccount
from utils.utility import get_w3, get_contract
from eth_account import Account
import numpy as np
from utils.snx_contract import SnxContracts
from utils.prices import Prices
import time
from web3.providers.base import JSONBaseProvider
base_provider = JSONBaseProvider()

class BrownieInteractions(SnxContracts,Prices):

    def __init__(self,conf):

        self.w3 = get_w3(conf)
        SnxContracts.__init__(self,conf)
        Prices.__init__(self,conf)
        self.conf=conf
        self.brownie_init()
        self.setup_contracts()

    def setup_contracts(self):

        self.contracts = {}

        wethContract = get_contract(self.conf,'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')
        self.contracts['weth'] = bContract.from_abi(name='weth',address=wethContract.address,abi=wethContract.abi)

        contract = get_contract(self.conf,'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')
        self.contracts['usdc'] = bContract.from_abi(name='usdc',address=contract.address,abi=wethContract.abi)

        contract = get_contract(self.conf,'0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45')
        self.contracts['uni'] = bContract.from_abi(name='uni',address=contract.address,abi=contract.abi)

        #setup synthetix contract
        proxyContract = self.get_snx_contract(contractNameAddress="ProxyERC20")
        snxContract   = self.get_snx_contract(contractNameAddress="Synthetix")
        self.contracts['snx'] = bContract.from_abi(name='snx', address=proxyContract.address, abi=snxContract.abi)

        #setup susd contract
        contract = self.get_snx_contract(contractNameAddress="ProxyERC20sUSD",contractNameAbi='ProxyERC20')
        self.contracts['susd'] = bContract.from_abi(name='susd', address=contract.address, abi=contract.abi)

        #setup seth contract
        contract = self.get_snx_contract(contractNameAddress="ProxysETH",contractNameAbi='ProxyERC20')
        self.contracts['seth'] = bContract.from_abi(name='seth', address=contract.address, abi=contract.abi)

        #setup sEUR contract
        contract = self.get_snx_contract(contractNameAddress="ProxysEUR",contractNameAbi='ProxyERC20')
        self.contracts['seur'] = bContract.from_abi(name='seur', address=contract.address, abi=contract.abi)

        #setup direct integration contract
        contract = self.get_snx_contract('DirectIntegrationManager')
        self.contracts['integration'] = bContract.from_abi(name='di', address=contract.address, abi=contract.abi)

        #setup collateral eth contract
        contract = self.get_snx_contract('CollateralEth')
        self.contracts['collateral_eth'] = bContract.from_abi(name='collateral_eth', address=contract.address, abi=contract.abi)

        #setup collateral eth contract
        contract = self.get_snx_contract('ExchangeRates')
        self.contracts['exchange_rates'] = bContract.from_abi(name='exchange_rates', address=contract.address, abi=contract.abi)

        #setup collateral eth contract
        contract = self.get_snx_contract('SystemSettings')
        self.contracts['settings'] = bContract.from_abi(name='settings', address=contract.address, abi=contract.abi)

        #dex aggregartors
        contract = get_contract(self.conf,'0xf120F029Ac143633d1942e48aE2Dfa2036C5786c')
        self.contracts['dex_agg'] = bContract.from_abi(name='dex_1',address=contract.address,abi=contract.abi)

        contract = get_contract(self.conf,self.conf["contracts"]["dexPriceAggregator"])
        self.contracts['dex_agg_mock'] = bContract.from_abi(name='dex_1',address=contract.address,abi=contract.abi)

        #setup mock aggergator
        address  = self.conf["contracts"]["mockAggregator"]
        contract = get_contract(conf=self.conf,address=address)
        self.contracts["mock"] = bContract.from_abi(name='mock', address=contract.address, abi=contract.abi)

        #Setup CircuitBreaker
        contract = self.get_snx_contract("CircuitBreaker")
        self.contracts['breaker'] = bContract.from_abi(name='breaker', address=contract.address, abi=contract.abi)

    def approve(self,approver,approvee,amount,contractName):
        return self.contracts[contractName].approve(approvee,amount,{"from":approver,'max_fee':int(1e9),'priority_fee':int(1e9)})

    def balance_of(self,contractName,account):
        contract = self.contracts[contractName]
        return contract.balanceOf(account.address)

    def set_integration(self,
                        targetAddress,
                        currencyKey,
                        dexPriceAggregator='0x0000000000000000000000000000000000000000',
                        atomicEquivalentForDexPricing='0x0000000000000000000000000000000000000000',
                        atomicExchangeFeeRate=0,
                        atomicTwapWindow=0,
                        atomicMaxTwapDelta=0,
                        atomicMaxVolumePerBlock=0,
                        atomicVolatilityConsiderationWindow=0,
                        atomicVolatilityTwapSeconds=0,
                        atomicVolatilityUpdateThreshold=0,
                        exchangeFeeRate=0,
                        exchangeMaxDynamicFee=0,
                        exchangeDynamicFeeRounds=0,
                        exchangeDynamicFeeThreshold=0,
                        exchangeDynamicFeeWeightDecay=0):
        currencyKeyHex = self.w3.toHex(text=currencyKey)
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        return self.contracts["integration"].setExchangeParameters(targetAddress,
                                                                   [currencyKeyHex],
                                                                   [currencyKeyHex,
                                                                    dexPriceAggregator,
                                                                    atomicEquivalentForDexPricing,
                                                                    atomicExchangeFeeRate,
                                                                    atomicTwapWindow,
                                                                    atomicMaxTwapDelta,
                                                                    atomicMaxVolumePerBlock,
                                                                    atomicVolatilityConsiderationWindow,
                                                                    atomicVolatilityTwapSeconds,
                                                                    atomicVolatilityUpdateThreshold,
                                                                    exchangeFeeRate,
                                                                    exchangeMaxDynamicFee,
                                                                    exchangeDynamicFeeRounds,
                                                                    exchangeDynamicFeeThreshold,
                                                                    exchangeDynamicFeeWeightDecay],
                                                                   {'from' : bAccount[-1],
                                                                    'max_fee':int(1e9),
                                                                    'priority_fee':int(1e9)})

    def get_integration(self,diAddress,currencyKey):
        headers = ['currencyKey',
                   'dexPriceAggregator',
                   'atomicEquivalentForDexPricing',
                   'atomicExchangeFeeRate',
                   'atomicTwapWindow',
                   'atomicMaxTwapDelta',
                   'atomicMaxVolumePerBlock',
                   'atomicVolatilityConsiderationWindow',
                   'atomicVolatilityTwapSeconds',
                   'atomicVolatilityUpdateThreshold',
                   'exchangeFeeRate',
                   'exchangeMaxDynamicFee',
                   'exchangeDynamicFeeRounds',
                   'exchangeDynamicFeeThreshold',
                   'exchangeDynamicFeeWeightDecay']
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        output = self.contracts["integration"].getExchangeParameters(diAddress,currencyKeyHex)
        outputDict = {header: parameter for header, parameter in zip(headers,output)}
        outputDict["currencyKey"] = self.w3.toText(outputDict["currencyKey"]).replace("\x00",'')
        return outputDict

    def set_exchange_fee(self,currencyKey,fee):
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        return self.contracts["settings"].setExchangeFeeRateForSynths([currencyKeyHex],[fee],{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})

    def set_atomic_exchange_fee(self,currencyKey,fee):
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        return self.contracts["settings"].setAtomicExchangeFeeRate(currencyKeyHex,fee,{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})

    def set_atomic_max_volume_per_block(self,maxVolumePerBlock):
        return self.contracts["settings"].setAtomicMaxVolumePerBlock(maxVolumePerBlock,{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})

    def set_atomic_volatility_threshold(self,currencyKey,volatilityThreshold):
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        return self.contracts["settings"].setAtomicVolatilityUpdateThreshold(currencyKeyHex ,volatilityThreshold,{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})

    def set_atomic_consideration_window(self,currencyKey,window):
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        return self.contracts["settings"].setAtomicVolatilityConsiderationWindow(currencyKeyHex,window,{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})

    def set_dynamic_parameters(self,rounds=None,threshold=None,decay=None,maxFee=None):
        if not rounds is None:
            self.contracts["settings"].setExchangeDynamicFeeRounds(rounds,{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})
        if not threshold is None:
            self.contracts["settings"].setExchangeDynamicFeeThreshold(threshold,{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})
        if not decay is None:
            self.contracts["settings"].setExchangeDynamicFeeWeightDecay(decay,{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})
        if not maxFee is None:
            self.contracts["settings"].setExchangeMaxDynamicFee(maxFee,{'from' : bAccount[-1],'max_fee':int(1e9),'priority_fee':int(1e9)})

    def swap_uni(self,account,fromToken,toToken,fromAmount,fee):
        #create some random account
        randomAccount = Account.create()
        #send random some eth
        bAccount[0].transfer(randomAccount.address,'1 ether',gas_price='1 gwei')

        #send the tokens to the random account
        self.contracts[fromToken].transfer(randomAccount.address,fromAmount,{'from':account,'max_fee':int(1e9),'priority_fee':int(1e9)})

        #have the suspense account approve uni to spend from tokens
        contract = self.w3.eth.contract(address = self.contracts[fromToken].address,
                                        abi = self.contracts[fromToken].abi)
        txPrep = contract.functions.approve(self.contracts["uni"].address,int(fromAmount))
        tx = txPrep.buildTransaction({'chainId': 1,
                                      'gas': int(1e6),
                                      'maxFeePerGas': int(1e9),
                                      'maxPriorityFeePerGas': int(1e9),
                                      'type': 2 ,
                                      'nonce': self.w3.eth.getTransactionCount(randomAccount.address)})
        signedTx = self.w3.eth.account.sign_transaction(tx, randomAccount.key)
        txHash = self.w3.eth.sendRawTransaction(signedTx.rawTransaction)
        time.sleep(10)
        txReceipt = self.w3.eth.getTransactionReceipt(txHash)
        assert txReceipt["status"] == 1,"approve failed"

        #swap the tokens on uniswap
        contract = self.w3.eth.contract(address = self.contracts["uni"].address,
                                        abi = self.contracts["uni"].abi)
        txPrep = contract.functions.exactInputSingle({'tokenIn':self.contracts[fromToken].address,
                                                      'tokenOut':self.contracts[toToken].address,
                                                      'fee':fee,
                                                      'recipient':randomAccount.address,
                                                      'deadline':int(time.time()+500),
                                                      'amountIn':int(fromAmount),
                                                      'amountOutMinimum':0,
                                                      'sqrtPriceLimitX96':0})
        tx = txPrep.buildTransaction({'chainId': 1,
                                      'gas': int(1e6),
                                      'maxFeePerGas': int(1e9),
                                      'maxPriorityFeePerGas': int(1e9),
                                      'type': 2 ,
                                      'nonce': self.w3.eth.getTransactionCount(randomAccount.address)})
        signedTx = self.w3.eth.account.sign_transaction(tx, randomAccount.key)
        txHash = self.w3.eth.sendRawTransaction(signedTx.rawTransaction)
        time.sleep(10)
        txReceipt = self.w3.eth.getTransactionReceipt(txHash)
        assert txReceipt["status"] == 1,"swap failed"

        #send the tokens to account
        contract = self.w3.eth.contract(address = self.contracts[toToken].address,
                                        abi = self.contracts[toToken].abi)

        txPrep = contract.functions.transfer(account.address,contract.functions.balanceOf(randomAccount.address).call())
        tx = txPrep.buildTransaction({'chainId': 1,
                                      'gas': int(1e6),
                                      'maxFeePerGas': int(1e9),
                                      'maxPriorityFeePerGas': int(1e9),
                                      'type': 2 ,
                                      'nonce': self.w3.eth.getTransactionCount(randomAccount.address)})
        signedTx = self.w3.eth.account.sign_transaction(tx, randomAccount.key)
        txHash = self.w3.eth.sendRawTransaction(signedTx.rawTransaction)
        time.sleep(10)
        txReceipt = self.w3.eth.getTransactionReceipt(txHash)
        assert txReceipt["status"] == 1,"sending tokens back to account failed"

    def eth_to_weth(self,account,amount):
        return self.contracts["weth"].deposit({'value':amount,
                                               'from':account,
                                               'max_fee':int(1e9),
                                               'priority_fee':int(1e9)})

    def eth_to_synth(self,account,ethAmount,currencyKey):
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        if currencyKey=='sUSD':
            atomicRate, linkRate = self.get_atomic_link_price('sETH','sUSD')
            amountOut = int(ethAmount*linkRate/1.31)
        elif currencyKey == 'sETH':
            amountOut = int(ethAmount/1.31)
        return self.contracts["collateral_eth"].open(\
                                                     amountOut,\
                                                         currencyKeyHex,\
                                                             {'value':ethAmount,
                                                              'from':account,
                                                              'max_fee':int(1e9),
                                                              'priority_fee':int(1e9)})

    def swap_atomically(self,fromCurrencyKey,toCurrencyKey,fromAmount,account):
        fromAmountPre = self.balance_of(fromCurrencyKey.lower(), account)
        toAmountPre   = self.balance_of(toCurrencyKey.lower(), account)
        atomicRate, linkRate  = self.get_atomic_link_price(fromCurrencyKey=fromCurrencyKey, toCurrencyKey=toCurrencyKey)
        fromHex   = Web3.toHex(text=fromCurrencyKey).ljust(66,"0")
        toHex     = Web3.toHex(text=toCurrencyKey).ljust(66,"0")
        self.contracts["snx"].exchangeAtomically(fromHex,
                                                 fromAmount,
                                                 toHex,
                                                 '0x0000000000000000000000000000000000000000',
                                                 0,
                                                 {'from':account,
                                                  'max_fee':int(1e9),
                                                  'priority_fee':int(1e9)})
        fromAmountPost = self.balance_of(fromCurrencyKey.lower(), account)
        toAmountPost   = self.balance_of(toCurrencyKey.lower(), account)
        return  1 - (toAmountPost-toAmountPre)/((fromAmountPre-fromAmountPost)*atomicRate)

    def get_atomic_swap_signed_tx(self,fromCurrencyKey,toCurrencyKey,fromAmount,account,incrementNonce=0):
        fromHex   = Web3.toHex(text=fromCurrencyKey).ljust(66,"0")
        toHex     = Web3.toHex(text=toCurrencyKey).ljust(66,"0")
        snxContract   = self.get_snx_contract(contractNameAddress="Synthetix")
        txPrep = snxContract.functions.exchangeAtomically(fromHex,
                                                          fromAmount,
                                                          toHex,
                                                          '0x0000000000000000000000000000000000000000',
                                                          0)

        tx = txPrep.buildTransaction({'chainId': 1,
                                      'gas': int(1e6),
                                      'maxFeePerGas': int(1e9),
                                      'maxPriorityFeePerGas': int(1e9),
                                      'type': 2 ,
                                      'nonce': self.w3.eth.getTransactionCount(account.address)+incrementNonce})

        tx = self.w3.eth.account.sign_transaction(tx, account.key)

        return base_provider.encode_rpc_request('eth_sendRawTransaction', [tx.rawTransaction.hex()])

    def swap_classic(self,fromCurrencyKey,toCurrencyKey,fromAmount,account):
        fromAmountPre = self.balance_of(fromCurrencyKey.lower(), account)
        toAmountPre   = self.balance_of(toCurrencyKey.lower(), account)
        atomicRate, linkRate = self.get_atomic_link_price(fromCurrencyKey=fromCurrencyKey, toCurrencyKey=toCurrencyKey)
        fromHex   = Web3.toHex(text=fromCurrencyKey).ljust(66,"0")
        toHex     = Web3.toHex(text=toCurrencyKey).ljust(66,"0")
        self.contracts["snx"].exchangeWithTracking(fromHex,
                                                   fromAmount,
                                                   toHex,
                                                   '0x0000000000000000000000000000000000000000',
                                                   '0x0000000000000000000000000000000000000000',
                                                   {'from':account,
                                                    'max_fee':int(1e9),
                                                    'priority_fee':int(1e9)})
        fromAmountPost = self.balance_of(fromCurrencyKey.lower(), account)
        toAmountPost   = self.balance_of(toCurrencyKey.lower(), account)
        return  1 - (toAmountPost-toAmountPre)/((fromAmountPre-fromAmountPost)*linkRate)

    def find_nearest_to_atomic(self,atomic,twap,spot,cl):
        rateNames = np.array(['twap','spot','cl'])
        deltas = np.array([abs(atomic/rate-1) for rate in [twap,spot,cl]])
        return rateNames[np.min(deltas) == deltas].item()

    def is_close(self,firstNumber,secondNumber,dof=10):
        return abs(firstNumber/secondNumber-1)<1/10**dof

    def set_oracle_to_mock(self,currencyKey):
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        return self.contracts["exchange_rates"].addAggregator(currencyKeyHex,self.contracts["mock"].address,{'from':bAccount[-1],'gas_price':'1 gwei'})

    def set_oracle_to_chainlink(self,currencyKey,address):
        currencyKeyHex = self.w3.toHex(text=currencyKey).ljust(66,"0")
        return self.contracts["exchange_rates"].addAggregator(currencyKeyHex,address,{'from':bAccount[-1],'gas_price':'1 gwei'})

    def set_mock_price(self,newPrice,timestamp):
        return self.contracts["mock"].setLatestAnswer(newPrice,timestamp,{'from':bAccount[0],'gas_price':'1 gwei'})

    def reset_circuit_breaker(self,address):
        return self.contracts["breaker"].resetLastValue([address],[0],{'from':bAccount[-1],'gas_price':'1 gwei'})

    def brownie_revert(self):
        brownie.chain.revert()

    def brownie_init(self):

        self.w3 = get_w3(conf=self.conf)
        self.specialAccounts = []

        if brownie.network.is_connected():
            self.disconnect_brownie()

        #connect
        brownie.network.connect('267-fork')

        #setup few random accounts and fund them
        for x in range(4):
            bAccount.add()
            bAccount[0].transfer(bAccount[x+1],"100 ether",gas_price="1 gwei")
            self.specialAccounts.append(Account.create())

        for account in self.specialAccounts:
            bAccount[0].transfer(account.address,"100 ether",gas_price="1 gwei")

        #unlock the owner
        contract = self.get_snx_contract(contractNameAddress='Issuer')
        ownerAddress = contract.functions.owner().call()
        bAccount.at(ownerAddress,force=True)
        brownie.chain.snapshot()

    def disconnect_brownie(self):
        brownie.network.disconnect()


import argparse
import re
from multiprocessing import Lock, Process

from bot_factory import BotFactory
from data_storage import DataStorage
from notifier import CHECK_FREQUENCY, start_notifier
from sheets_api import SheetsApi
from tabulate import tabulate
from utils import formatted_link, formatted_link_from_id, id_from_link

def parse_args():
    parser = argparse.ArgumentParser()
    # parser.add_argument("config_path", help="path to the configuration file")
    parser.add_argument("token", help="telegram bot token")
    parser.add_argument("api_key", help="google sheets api key")
    parser.add_argument("db_path", help="notifiers database filepath")
    args = parser.parse_args()
    return args

if __name__ == "__main__":

    launch_args = parse_args()

    bot_factory = BotFactory(launch_args.token)
    bot = bot_factory.create_bot()

    db_path = launch_args.db_path
    db_storage = DataStorage(db_path)
    db_lock = Lock()

    sheets_service = SheetsApi(launch_args.api_key)

    @bot.message_handler(commands=["set"])
    def set_command(message):
        text = message.text
        args_patterns = ['/set "[^"]*"', ' [^" ]*', ' "[^"]*"']
        args = []

        for pattern in args_patterns:
            enter = re.match(pattern, text)
            if enter is None:
                bot.reply_to(message, text="Wrong format")
                return

            enter = enter.group(0)
            args.append(enter)
            text = text[len(enter) :]

        name = args[0].split('"')[-2]
        sheet_id = args[1][1:]
        cell_range = args[2][2:-1]

        if len(sheet_id) > 44:  # interpret as an url
            sheet_id = id_from_link(sheet_id)
            if sheet_id is None:
                bot.reply_to(message, "Invalid URL")
                return

        chat_id = message.chat.id

        db_lock.acquire()

        with_same_props = db_storage.find_by_props(chat_id, sheet_id, cell_range)
        if with_same_props is not None:
            if with_same_props != name:
                message_text = (
                    f"The notifier '{with_same_props}' has the same properties"
                )
            else:
                message_text = "Already set"
            bot.reply_to(message, message_text)
            db_lock.release()
            return
        if db_storage.find_by_name(chat_id, name):
            bot.reply_to(message, "A notifier with this name is already set")
            db_lock.release()
            return

        cur_value_hash = sheets_service.get_range_hash(sheet_id, cell_range)
        if cur_value_hash is None:
            message_text = (
                f"An error occurred trying to set the {name} "
                f"notifier. \n"
                f"Check the Sheet ID and range format. "
                f"Probably the {formatted_link_from_id(sheet_id, 'table')} is "
                f"private or doesn't exist. "
            )
            bot.send_message(
                chat_id=chat_id,
                text=message_text,
                parse_mode="HTML",
                disable_web_page_preview=True,
            )
            db_lock.release()
            return
        # add new notifier
        db_storage.add_record(
            chat_id,
            name,
            sheet_id,
            cell_range,
            cur_value_hash,
        )
        bot.reply_to(message, text="Successfully set")
        db_lock.release()

    @bot.message_handler(commands=["list"])
    def list_command(message):
        db_lock.acquire()

        records = db_storage.records_by_user(message.chat.id)

        headers = ["Name", "Cell Range"]

        data = [[rec["name"], rec["range"]] for rec in records]
        tabulated_text = tabulate(data, headers=headers)

        message_text = ""

        for line, sheet_id in zip(
            tabulated_text.split("\n"), ["", ""] + [rec["sheet_id"] for rec in records]
        ):
            line = "
" + line + "
"
            link = ""
            if sheet_id != "":
                link = formatted_link_from_id(sheet_id)
            message_text += line + "    " + link + "\n"

        bot.send_message(
            chat_id=message.chat.id,
            text=message_text,
            parse_mode="HTML",
            disable_web_page_preview=True,
        )
        db_lock.release()

    @bot.message_handler(commands=["del"])
    def del_command(message):
        if len(message.text) < 6:
            bot.reply_to(message, text="Wrong format")
            return

        db_lock.acquire()

        name = message.text[5:]
        if db_storage.delete_with_name(message.chat.id, name):
            message_text = f"The notifier {name} has been deleted"
        else:
            message_text = f"The notifier {name} not found"
        bot.reply_to(message, text=message_text, parse_mode="HTML")
        db_lock.release()

    @bot.message_handler(commands=["help"])
    def help_command(message):
        a1_examples_link = (
            "https://developers.google.com/sheets/api/guides/concepts#expandable-1"
        )
        r1c1_examples_link = (
            "https://developers.google.com/sheets/api/guides/concepts#expandable-2"
        )

        message_text = (
            "To set a notifier enter a message in the following format:\n"
            '
 /set "{name}" {google_sheet_id | table_url} "{cell range}"
\n'
            f"cell range format - {formatted_link(a1_examples_link, 'A1')} "
            f"or {formatted_link(r1c1_examples_link, 'R1C1')} notation\n"
            "for example:\n"
            '
 /set "test results" 17h7GKuJ1gS7faOiyM7dBaM1XPUHPXPgIb7zCFfFGUOE '
            '"My Sheet!A1:A2"
\n'
            '
 /set "test results" '
            "https://docs.google.com/spreadsheets/d/17h7GKuJ1gS7faOiyM7dBaM1XPUHPXPgIb7zCFfFGUOE "
            '"My Sheet!A1:A2"
\n\n'
            ""
            "To delete a notifier use\n"
            "
 /del {name}
\n"
            "for example:\n"
            "
 /del test results
"
        )
        bot.send_message(
            chat_id=message.chat.id,
            text=message_text,
            parse_mode="HTML",
            disable_web_page_preview=True,
        )

    @bot.message_handler(commands=["check_frequency"])
    def check_freq_command(message):
        bot.send_message(
            chat_id=message.chat.id,
            text=f"Current update frequency is {CHECK_FREQUENCY} seconds",
        )

    notifier_process = Process(
        target=start_notifier, args=(bot_factory, db_path, db_lock, launch_args.api_key)
    )
    notifier_process.start()

    print(f"Bot started with token{launch_args.token}")
    bot.infinity_polling()


import re
from operator import itemgetter

import networkx as nx
import numpy as np
from fuzzywuzzy import process
from scipy.sparse import lil_matrix

ROMAN_CONSTANTS = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XX", "XXX",
                   "XL", "L", "LX", "LXX", "LXXX", "XC", "C", "CC", "CCC", "CD", "D", "DC",
                   "DCC", "DCCC", "CM", "M", "MM", "MMM"]

class Feature(object):
    def __init__(self, protein_id, product_id, gene_id, gene_name_id, go_id, enzyme_id, reaction_id,
                 ec_id, pathway_id, compound_id):
        self.protein_id = protein_id
        self.product_id = product_id
        self.gene_id = gene_id
        self.gene_name_id = gene_name_id
        self.go_id = go_id
        self.enzyme_id = enzyme_id
        self.reaction_id = reaction_id
        self.ec_id = ec_id
        self.pathway_id = pathway_id
        self.compound_id = compound_id

    def __reverse_idx(self, value2idx):
        idx2value = {}
        for key, value in value2idx.items():
            idx2value.update({value: key})
        return idx2value

    def __desc(self, display_interval, count: int = 1, total: int = 1):
        desc = '\t\t--> Processed: {0:.2f}%'.format(count / total * 100)
        if count == 1 or count % display_interval == 0:
            print(desc, end="\r")
        if count == total:
            print(desc)

    ##########################################################################################################
    ############################           FEATURES FROM KNOWLEDGE-BASE            ###########################
    ##########################################################################################################

    def pathway_features(self, info_list, ptwy_ec_matrix, ec_idx, num_features=33, text_match_threshold=95,
                         display_interval=-1):
        ## Add the EC to each pathway and define kernel distance metric

        regex = re.compile(r'\(| |\)')
        matrix_features = lil_matrix((len(self.pathway_id), num_features), dtype=np.float32)

        for n, data_item in enumerate(info_list[0].items()):
            idx, item = data_item
            self.__desc(display_interval=display_interval, count=n + 1, total=len(info_list[0].items()))
            # 0. has-orphan-reaction (boolean)
            if item[14][1] > 0:
                matrix_features[self.pathway_id[idx], 0] = 1

            # 1. has-spontaneous-reaction (boolean)
            if item[15][1] > 0:
                matrix_features[self.pathway_id[idx], 1] = 1

            # 2. has-single-reaction (boolean)
            # 3. num-reactions (numeric)
            # 4. multiple-reaction-pathway (boolean)
            if len(item[4][1]) == 1:
                matrix_features[self.pathway_id[idx], 2] = 1
                matrix_features[self.pathway_id[idx], 3] = 1
            else:
                matrix_features[self.pathway_id[idx], 3] = len(item[4])
                matrix_features[self.pathway_id[idx], 4] = 1

            # 5. is-subpathway (boolean)
            if len(item[10][1]) != 0:
                matrix_features[self.pathway_id[idx], 5] = 1

            text = str(item[0][1]) + ' ' + ' '.join(item[1][1]) + ' ' + ' '.join(item[6][1])
            text = text.lower()

            # 6. is-energy-pathway (boolean)
            if 'energy' in text:
                matrix_features[self.pathway_id[idx], 6] = 1

            # 7. is-deg-or-detox-pathway (boolean)
            # 8. is-detoxification-pathway (boolean)
            # 9. is-degradation-pathway (boolean)
            if 'detoxification' in text or 'degradation' in text:
                matrix_features[self.pathway_id[idx], 7] = 1
            else:
                if 'detoxification' in text:
                    matrix_features[self.pathway_id[idx], 8] = 1
                if 'degradation' in text:
                    matrix_features[self.pathway_id[idx], 9] = 1

            # 10. is-biosynthesis-pathway (boolean)
            if 'biosynthesis' in text:
                matrix_features[self.pathway_id[idx], 10] = 1

            # 11. is-variant (boolean)
            for t in text.split():
                if t.upper() in ROMAN_CONSTANTS:
                    matrix_features[self.pathway_id[idx], 11] = 1

            # 12. num-initial-reactions (numeric)
            # 13. num-final-reactions (numeric)
            # 14. first-reaction-is-enzymatic (boolean)
            # 15. last-reaction-is-enzymatic (boolean)
            if item[3][1]:
                lst_rxn = [list(filter(None, regex.split(itm))) for itm in item[3][1]]
                dg = nx.DiGraph()
                dg.add_nodes_from(item[4][1])
                for itm in lst_rxn:
                    if len(itm) == 1:
                        continue
                    else:
                        dg.add_edge(itm[1], itm[0])

                count = 0
                for itm in dg.pred.items():
                    if len(itm[1]) == 0:
                        count += 1
                        itm = ''.join(itm[0]).replace('\"', '')
                        if info_list[1][itm][3][1]:
                            matrix_features[self.pathway_id[idx], 14] = 1
                matrix_features[self.pathway_id[idx], 12] = count

                count = 0
                for itm in dg.succ.items():
                    if len(itm[1]) == 0:
                        count += 1
                        itm = ''.join(itm[0]).replace('\"', '')
                        if info_list[1][itm][3][1]:
                            matrix_features[self.pathway_id[idx], 15] = 1
                matrix_features[self.pathway_id[idx], 13] = count

            # 16. has-unique-reactions (boolean)
            # 17. num-unique-reactions (numeric)
            if item[17][1]:
                matrix_features[self.pathway_id[idx], 16] = 1
                matrix_features[self.pathway_id[idx], 17] = len(item[17][1])

            # 18. has-enzymatic-reactions (boolean)
            # 19. num-enzymatic-reactions (numeric)
            # 20. fraction-unique-enzymes (numeric)
            if item[16][1]:
                matrix_features[self.pathway_id[idx], 18] = 1
                matrix_features[self.pathway_id[idx], 19] = len(item[16][1])
                matrix_features[self.pathway_id[idx], 20] = len(item[16][1]) / len(item[4][1])

            # 21. has-unique-enzymatic-reactions (boolean)
            # 22. num-unique-enzymatic-reactions (numeric)
            # 23. fraction-unique-enzymes (numeric)
            # 24. every-unique-reaction-has-enzyme (boolean)
            unique_ec_lst = list()
            num_ecs = 0
            for rxn in item[17][1]:
                rxn = ''.join(rxn).replace('\"', '')
                if info_list[1][rxn][3][1]:
                    e = [e for e in info_list[1][rxn][3][1]]
                    unique_ec_lst.extend(e)
                    num_ecs += 1
            if unique_ec_lst:
                matrix_features[self.pathway_id[idx], 21] = 1
                matrix_features[self.pathway_id[idx], 22] = len(unique_ec_lst)
                matrix_features[self.pathway_id[idx], 23] = len(unique_ec_lst) / len(item[4][1])
            if num_ecs == len(item[17][1]):
                matrix_features[self.pathway_id[idx], 24] = 1

            # 25. has-key-reactions (boolean)
            # 26. num-key-reactions (numeric)
            if item[6][1]:
                matrix_features[self.pathway_id[idx], 25] = 1
                matrix_features[self.pathway_id[idx], 26] = len(item[6][1])

            # 27. subset-has-same-evidence (boolean)
            # 28. other-pathway-has-more-evidence (boolean)
            # 29. variant-has-more-evidence (boolean)
            lst_ptw_var = list()
            for pidx, pitem in info_list[0].items():
                if pidx != idx:
                    if set.intersection(set(item[4][1]), set(pitem[4][1])):
                        matrix_features[self.pathway_id[idx], 27] = 1
                    if set(item[4][1]) <= set(pitem[4][1]):
                        matrix_features[self.pathway_id[idx], 28] = 1
                    for t in text.split():
                        if t.upper() in ROMAN_CONSTANTS:
                            match = process.extract(pitem[0][1], [item[0][1]])
                            if match[0][1] > text_match_threshold:
                                lst_ptw_var.append(pidx)
                            break
            if lst_ptw_var:
                for id in lst_ptw_var:
                    if set(item[4][1]) <= set(info_list[0][id][4][1]):
                        matrix_features[self.pathway_id[idx], 29] = 1

            # 30. species-range-includes-target (boolean)
            if item[8][1]:
                matrix_features[self.pathway_id[idx], 30] = 1

            # 31. taxonomic-range-includes-target (boolean)
            if item[9][1]:
                matrix_features[self.pathway_id[idx], 31] = 1

            # 32. evidence-info-content-norm-all (numeric)
            m = np.sum(ptwy_ec_matrix[self.pathway_id[idx], :])
            total = 0
            for ec in set(item[16][1]):
                total += 1 / ptwy_ec_matrix[:, np.where(ec_idx == self.ec_id[ec])[0]].nnz
            if m != 0:
                matrix_features[self.pathway_id[idx], 32] = total / m

            # 33. evidence-info-content-unnorm (numeric)
            matrix_features[self.pathway_id[idx], 33] = total

            # TODO: 1)- DEAD-END-COMPOUND
            #       2)-
            # 34. has-genes-in-directon (boolean)
            # 35. has-proximal-genes (boolean)
            # 36. fraction-genes-in-directon (numeric)
            # 37. num-genes-in-directon (numeric)
            # 38. fraction-proximal-genes (numeric)
            # 39. num-proximal-genes (numeric)
        return matrix_features

    def ec_features(self, info_list, matrix_list, ptwy_ec_idx, rxn_ec_idx, num_features=25, initial_rxn=2, last_rxn=2,
                    display_interval=-1):
        regex = re.compile(r'\(| |\)')
        matrix_features = np.zeros(shape=(len(self.ec_id), num_features), dtype=np.object)
        ptw_idx = self.__reverse_idx(self.pathway_id)
        rxn_idx = self.__reverse_idx(self.reaction_id)

        for n, ec in enumerate(self.ec_id):
            self.__desc(display_interval=display_interval, count=n + 1, total=len(self.ec_id))
            ptw_lst = matrix_list[0][:, np.where(ptwy_ec_idx == self.ec_id[ec])[0]].nonzero()[0]
            count_lst = matrix_list[0][ptw_lst, np.where(ptwy_ec_idx == self.ec_id[ec])[0]].toarray()[0]

            # 0. num-pathways (numeric)
            matrix_features[self.ec_id[ec], 0] = len(ptw_lst)

            # 1. list-of-pathways (list)
            matrix_features[self.ec_id[ec], 1] = ptw_lst

            # 2. is-mapped-to-single-pathway (boolean)
            if len(ptw_lst) == 1:
                matrix_features[self.ec_id[ec], 2] = 1

            for idx, pidx in enumerate(ptw_lst):
                ptw = info_list[0][ptw_idx[pidx]]

                # 3. num-contributions-in-mapped-pathways (numeric)
                matrix_features[self.ec_id[ec], 3] += count_lst[idx]

                # 4. contributes-in-subpathway-as-inside-superpathways (boolean)
                # 5. num-contributions-in-subpathway-as-inside-superpathways (numeric)
                if len(ptw[10][1]):
                    matrix_features[self.ec_id[ec], 4] = 1
                    matrix_features[self.ec_id[ec], 5] += len(ptw[9][1])

                text = str(ptw[0][1]) + ' ' + ' '.join(ptw[1][1]) + ' ' + ' '.join(ptw[7][1])
                text = text.lower()
                true_rxn_predecessors = [list(filter(None, regex.split(itm))) for itm in ptw[3][1]]
                for idx, itm in enumerate(true_rxn_predecessors):
                    itm = ' '.join(itm).replace('\"', '')
                    true_rxn_predecessors[idx] = itm.split()
                dg = nx.DiGraph()
                dg.add_nodes_from(ptw[4][1])
                for itm in true_rxn_predecessors:
                    if len(itm) == 1:
                        continue
                    else:
                        dg.add_edge(itm[1], itm[0])

                initial_ec_lst = [n for n, d in sorted(dg.in_degree(), key=itemgetter(1))]
                initial_ec_lst = [j for itm in initial_ec_lst for j in info_list[1][itm][3][1] if
                                  j in ptw[16][1]]
                final_ec_lst = [n for n, d in sorted(dg.out_degree(), key=itemgetter(1))]
                final_ec_lst = [j for itm in final_ec_lst for j in info_list[1][itm][3][1] if
                                j in ptw[16][1]]

                # 6. act-as-initial-reactions (boolean)
                # 7. num-act-as-initial-reactions (numeric)
                if ec in initial_ec_lst[:initial_rxn]:
                    matrix_features[self.ec_id[ec], 6] = 1
                    matrix_features[self.ec_id[ec], 7] += 1

                # 8. act-as-final-reactions (boolean)
                # 9. num-act-as-final-reactions (numeric)
                if ec in final_ec_lst[:last_rxn]:
                    matrix_features[self.ec_id[ec], 8] = 1
                    matrix_features[self.ec_id[ec], 9] += 1

                # 10. act-as-initial-and-final-reactions (boolean)
                # 11. num-act-as-initial-and-final-reactions  (numeric)
                if ec in initial_ec_lst[:initial_rxn] and ec in final_ec_lst[:last_rxn]:
                    matrix_features[self.ec_id[ec], 10] = 1
                    matrix_features[self.ec_id[ec], 11] += 1

                # 12. act-in-deg-or-detox-pathway (boolean)
                # 13. num-act-in-deg-or-detox-pathway (numeric)
                if 'detoxification' in text or 'degradation' in text:
                    matrix_features[self.ec_id[ec], 12] = 1
                    matrix_features[self.ec_id[ec], 13] += 1

                # 14. act-in-biosynthesis-pathway (boolean)
                # 15. num-act-in-biosynthesis-pathway (numeric)
                if 'biosynthesis' in text:
                    matrix_features[self.ec_id[ec], 14] = 1
                    matrix_features[self.ec_id[ec], 15] += 1

                # 16. act-in-energy-pathway (boolean)
                # 17. num-act-in-energy-pathway (numeric)
                if 'energy' in text:
                    matrix_features[self.ec_id[ec], 16] = 1
                    matrix_features[self.ec_id[ec], 17] += 1

            rxn_lst = matrix_list[1][:, np.where(rxn_ec_idx == self.ec_id[ec])[0]].nonzero()[0]

            # 18. num-reactions (numeric)
            matrix_features[self.ec_id[ec], 18] = len(rxn_lst)

            # 19. list-of-reactions (list)
            matrix_features[self.ec_id[ec], 19] = rxn_lst

            # 20. act-in-unique-reaction (boolean)
            if len(rxn_lst) == 1:
                matrix_features[self.ec_id[ec], 20] = 1

            # 21. reactions-orphaned (boolean)
            # 22. num-reactions-orphaned (numeric)
            # 23. reactions-has-species (boolean)
            # 24. reactions-has-taxonomic-range (boolean)
            for idx, ridx in enumerate(rxn_lst):
                rxn = info_list[1][rxn_idx[ridx]]
                if ec in rxn[3][1]:
                    if rxn[5][1] != False:
                        matrix_features[self.ec_id[ec], 21] = 1
                        matrix_features[self.ec_id[ec], 22] += 1
                    if len(rxn[8][1]):
                        matrix_features[self.ec_id[ec], 23] = 1
                    if len(rxn[9][1]):
                        matrix_features[self.ec_id[ec], 24] = 1

        return matrix_features

    ##########################################################################################################
    ##########################           FEATURES FROM EXPERIMENTAL DATA            ##########################
    ##########################################################################################################

    def ec_evidence_features(self, instance, info_list, matrix_list, col_idx, num_features=82, num_pathway_features=28,
                             initial_reaction=2, last_reaction=2, threshold=0.5, beta=0.45):
        '''

        :param instance:
        :param info_list:
        :param matrix_list:
        :param num_features:
        :param initial_reaction:
        :param last_reaction:
        :param threshold:
        :return:
        '''
        one_hot = np.copy(instance.toarray()[0])
        one_hot[one_hot > 0] = 1
        ec_idx = col_idx[np.nonzero(one_hot)[0]]
        selected_list = matrix_list[1][ec_idx]
        m_features = np.zeros(shape=(1, num_features), dtype=np.float32)
        possible_pathways = np.zeros(shape=(1, len(info_list[0])), dtype=np.float32)
        ratio_possible_pathways = np.zeros(shape=(1, len(info_list[0])), dtype=np.float32)
        pathway_features = np.zeros(shape=(len(info_list[0]), num_pathway_features), dtype=np.float32)

        '''
        Extracting Various EC Features from Experimental Data
        '''
        # 0. fraction-total-ecs-to-distinct-ecs (numeric)
        if len(selected_list) != 0:
            m_features[0, 0] = np.sum(instance) / len(selected_list)

        # 1. fraction-total-possible-pathways-to-distinct-pathways (numeric)
        if len(np.unique(np.concatenate(selected_list[:, 1]))) != 0:
            m_features[0, 1] = np.sum(selected_list[:, 0]) / len(np.unique(np.concatenate(selected_list[:, 1])))

        # 2. fraction-total-ecs-to-ecs-mapped-to-single-pathways (numeric)
        if np.sum(instance) != 0:
            m_features[0, 2] = np.sum(selected_list[:, 2]) / np.sum(instance)

        # 3. fraction-total-ecs-mapped-to-pathways (numeric)
        if np.sum(selected_list[:, 3]) != 0:
            m_features[0, 3] = np.sum(instance) / np.sum(selected_list[:, 3])

        # 4. fraction-total-distinct-ecs-contribute-in-subpathway-as-inside-superpathways (numeric)
        if np.sum(instance) != 0:
            m_features[0, 4] = np.sum(selected_list[:, 4]) / np.sum(instance)

        # 5. fraction-total-ecs-contribute-in-subpathway-as-inside-superpathways (numeric)
        if np.sum(instance) != 0:
            m_features[0, 5] = np.sum(selected_list[:, 5]) / np.sum(instance)

        # 6. fraction-total-distinct-ecs-act-as-initial-reactions (numeric)
        if np.sum(instance) != 0:
            m_features[0, 6] = np.sum(selected_list[:, 6]) / np.sum(instance)

        # 7. fraction-total-ecs-act-as-initial-reactions (numeric)
        if np.sum(instance) != 0:
            m_features[0, 7] = np.sum(selected_list[:, 7]) / np.sum(instance)

        # 8. fraction-total-distinct-ecs-act-as-final-reactions (numeric)
        if np.sum(instance) != 0:
            m_features[0, 8] = np.sum(selected_list[:, 8]) / np.sum(instance)

        # 9. fraction-total-ecs-act-as-final-reactions (numeric)
        if np.sum(instance) != 0:
            m_features[0, 9] = np.sum(selected_list[:, 9]) / np.sum(instance)

        # 10. fraction-total-distinct-ecs-act-as-initial-and-final-reactions (numeric)
        if np.sum(instance) != 0:
            m_features[0, 10] = np.sum(selected_list[:, 10]) / np.sum(instance)

        # 11. fraction-total-ecs-act-as-initial-and-final-reactions  (numeric)
        if np.sum(instance) != 0:
            m_features[0, 11] = np.sum(selected_list[:, 11]) / np.sum(instance)

        # 12. fraction-total-distinct-ecs-act-in-deg-or-detox-pathway (numeric)
        if np.sum(instance) != 0:
            m_features[0, 12] = np.sum(selected_list[:, 12]) / np.sum(instance)

        # 13. fraction-total-ecs-act-in-deg-or-detox-pathway (numeric)
        if np.sum(instance) != 0:
            m_features[0, 13] = np.sum(selected_list[:, 13]) / np.sum(instance)

        # 14. fraction-total-distinct-ec-act-in-biosynthesis-pathway (numeric)
        if np.sum(instance) != 0:
            m_features[0, 14] = np.sum(selected_list[:, 14]) / np.sum(instance)

        # 15. fraction-total-ec-act-in-biosynthesis-pathway (numeric)
        if np.sum(instance) != 0:
            m_features[0, 15] = np.sum(selected_list[:, 15]) / np.sum(instance)

        # 16. fraction-total-distinct-ec-act-in-energy-pathway (numeric)
        if np.sum(instance) != 0:
            m_features[0, 16] = np.sum(selected_list[:, 16]) / np.sum(instance)

        # 17. fraction-total-ec-act-in-energy-pathway (numeric)
        if np.sum(instance) != 0:
            m_features[0, 17] = np.sum(selected_list[:, 17]) / np.sum(instance)

        # 18. fraction-total-ecs-to-total-reactions (numeric)
        if np.sum(selected_list[:, 18]) != 0:
            m_features[0, 18] = np.sum(instance) / np.sum(selected_list[:, 18])

        # 19. fraction-total-distinct-ecs-to-total-distinct-reactions (numeric)
        if len(np.unique(np.concatenate(selected_list[:, 19]))) != 0:
            m_features[0, 19] = len(selected_list) / len(np.unique(np.concatenate(selected_list[:, 19])))

        # 20. fraction-total-ec-contribute-in-unique-reaction (numeric)
        if np.sum(instance) != 0:
            m_features[0, 20] = np.sum(selected_list[:, 20]) / np.sum(instance)

        # 21. fraction-total-distinct-ec-contribute-to-reactions-has-taxonomic-range (numeric)
        if np.sum(instance) != 0:
            m_features[0, 21] = np.sum(selected_list[:, 24]) / np.sum(instance)

        # 22. fraction-total-pathways-over-total-ecs (numeric)
        if np.sum(instance) != 0:
            m_features[0, 22] = np.sum(selected_list[:, 0]) / np.sum(instance)

        # 23. fraction-total-pathways-over-distinct-ec (numeric)
        if len(selected_list) != 0:
            m_features[0, 23] = np.sum(selected_list[:, 0]) / len(selected_list)

        # 24. fraction-total-distinct-pathways-over-distinct-ec (numeric)
        if len(selected_list) != 0:
            m_features[0, 24] = len(np.unique(np.concatenate(selected_list[:, 1]))) / len(selected_list)

        # 25. fraction-distinct-ec-contributes-in-subpathway-over-distinct-pathways (numeric)
        if len(np.unique(np.concatenate(selected_list[:, 1]))) != 0:
            m_features[0, 25] = np.sum(selected_list[:, 4]) / len(np.unique(np.concatenate(selected_list[:, 1])))

        # 26. fraction-ec-contributes-in-subpathway-over-total-pathways (numeric)
        if np.sum(selected_list[:, 0]) != 0:
            m_features[0, 26] = np.sum(selected_list[:, 5]) / np.sum(selected_list[:, 0])

        # 27. fraction-distinct-ec-act-in-deg-or-detox-pathway-over-distinct-pathways (numeric)
        if len(np.unique(np.concatenate(selected_list[:, 1]))) != 0:
            m_features[0, 27] = np.sum(selected_list[:, 12]) / len(np.unique(np.concatenate(selected_list[:, 1])))

        # 28. fraction-distinct-ec-act-in-deg-or-detox-pathway-over-total-pathways (numeric)
        if np.sum(selected_list[:, 0]) != 0:
            m_features[0, 28] = np.sum(selected_list[:, 12]) / np.sum(selected_list[:, 0])

        # 29. fraction-ec-act-in-deg-or-detox-pathway-over-total-pathways (numeric)
        if np.sum(selected_list[:, 0]) != 0:
            m_features[0, 29] = np.sum(selected_list[:, 13]) / np.sum(selected_list[:, 0])

        # 30. fraction-distinct-ec-act-in-biosynthesis-pathway-over-distinct-pathways (numeric)
        if len(np.unique(np.concatenate(selected_list[:, 1]))) != 0:
            m_features[0, 30] = np.sum(selected_list[:, 14]) / len(np.unique(np.concatenate(selected_list[:, 1])))

        # 31. fraction-distinct-ec-act-in-biosynthesis-pathway-over-total-pathways (numeric)
        if np.sum(selected_list[:, 0]) != 0:
            m_features[0, 31] = np.sum(selected_list[:, 14]) / np.sum(selected_list[:, 0])

        # 32. fraction-ec-act-in-biosynthesis-pathway-over-total-pathways (numeric)
        if np.sum(selected_list[:, 0]) != 0:
            m_features[0, 32] = np.sum(selected_list[:, 15]) / np.sum(selected_list[:, 0])

        # 33. fraction-distinct-ec-act-in-energy-pathway-over-distinct-pathways (numeric)
        if len(np.unique(np.concatenate(selected_list[:, 1]))) != 0:
            m_features[0, 33] = np.sum(selected_list[:, 16]) / len(np.unique(np.concatenate(selected_list[:, 1])))

        # 34. fraction-distinct-ec-act-in-energy-pathway-over-total-pathways (numeric)
        if np.sum(selected_list[:, 0]) != 0:
            m_features[0, 34] = np.sum(selected_list[:, 16]) / np.sum(selected_list[:, 0])

        # 35. fraction-ec-act-in-energy-pathway-over-total-pathways (numeric)
        if np.sum(selected_list[:, 0]) != 0:
            m_features[0, 35] = np.sum(selected_list[:, 17]) / np.sum(selected_list[:, 0])

        # 36. fraction-total-reactions-over-total-pathways (numeric)
        if np.sum(selected_list[:, 0]) != 0:
            m_features[0, 36] = np.sum(selected_list[:, 18]) / np.sum(selected_list[:, 0])

        # 37. fraction-total-reactions-over-distinct-pathways (numeric)
        if len(np.unique(np.concatenate(selected_list[:, 1]))) != 0:
            m_features[0, 37] = np.sum(selected_list[:, 18]) / len(np.unique(np.concatenate(selected_list[:, 1])))

        # 38. fraction-distinct-reaction-over-distinct-pathways (numeric)
        if len(np.unique(np.concatenate(selected_list[:, 1]))) != 0:
            m_features[0, 38] = len(np.unique(np.concatenate(selected_list[:, 19]))) / len(
                np.unique(np.concatenate(selected_list[:, 1])))

        '''
        Extracting Pathway Features from Experimental Data
        '''

        regex = re.compile(r'\(| |\)')
        ptw_idx = self.__reverse_idx(self.pathway_id)

        for pidx in np.unique(np.concatenate(selected_list[:, 1])):
            bin_ptw = matrix_list[0][pidx]
            bin_ptw[bin_ptw > 0] = 1
            num_common_items = (one_hot & bin_ptw.toarray()[0]).sum()
            if int(num_common_items) != 0:
                ratio_possible_pathways[0, pidx] = np.divide(num_common_items, bin_ptw.sum())
            pid = ptw_idx[pidx]
            ptw = info_list[0][pid]
            text = str(ptw[0][1]) + ' ' + ' '.join(ptw[1][1]) + ' ' + ' '.join(ptw[7][1])
            text = text.lower()
            true_rxn_predecessors = [list(filter(None, regex.split(itm))) for itm in info_list[0][pid][3][1]]
            for idx, itm in enumerate(true_rxn_predecessors):
                itm = ' '.join(itm).replace('\"', '')
                true_rxn_predecessors[idx] = itm.split()
            dg = nx.DiGraph()
            dg.add_nodes_from(info_list[0][pid][4][1])
            for itm in true_rxn_predecessors:
                if len(itm) == 1:
                    continue
                else:
                    dg.add_edge(itm[1], itm[0])

            true_prev_ec_lst = [n for n, d in sorted(dg.in_degree(), key=itemgetter(1))]
            true_prev_ec_lst = [self.ec_id[j] for itm in true_prev_ec_lst for j in info_list[1][itm][3][1] if
                                j in info_list[0][pid][16][1]]
            true_succ_ec_lst = [n for n, d in sorted(dg.out_degree(), key=itemgetter(1))]
            true_succ_ec_lst = [self.ec_id[j] for itm in true_succ_ec_lst for j in info_list[1][itm][3][1] if
                                j in info_list[0][pid][16][1]]

            unique_ec_lst = list()
            orphan_ec_lst = list()
            for rxn in ptw[17][1]:
                if info_list[1][rxn][3][1]:
                    e = [self.ec_id[e] for e in info_list[1][rxn][3][1]]
                    unique_ec_lst.extend(e)
                if info_list[1][rxn][5][1] != False:
                    orphan_ec_lst.extend(e)

            '''
            Extracting Info from Knowledge Data
            '''

            sample_ec_lst = [ec for ec in true_prev_ec_lst if ec in ec_idx]
            sample_unique_ec_lst = [ec for ec in sample_ec_lst if ec in unique_ec_lst]
            if true_prev_ec_lst:
                sample_initial_ec_lst = [1 for ec in true_prev_ec_lst[:initial_reaction] if ec not in sample_ec_lst]
            if true_succ_ec_lst:
                sample_final_ec_lst = [1 for ec in true_succ_ec_lst[:last_reaction] if ec not in sample_ec_lst]

            '''
            Continue Pathway Features Extracting
            '''
            # 39. ecs-in-energy-pathways-mostly-missing (numeric)
            if 'energy' in text:
                if len(sample_ec_lst) < int(threshold * len(true_prev_ec_lst)):
                    m_features[0, 39] += 1

            # 40. ecs-in-pathways-mostly-present (numeric)
            # 0. ecs-mostly-present-in-pathway (boolean)
            # 1. prob-ecs-mostly-present-in-pathway (numeric)
            if sample_ec_lst:
                if len(sample_ec_lst) > int(threshold * len(true_prev_ec_lst)):
                    m1 = len(sample_ec_lst) + 1 == len(ptw[16][1])
                    m2 = (len(sample_ec_lst) / len(ptw[16][1])) >= threshold
                    if m1 and m2:
                        m_features[0, 40] += 1
                        pathway_features[pidx, 0] = 1
                        if len(ptw[16][1]) != 0:
                            pathway_features[pidx, 1] = len(sample_ec_lst) / len(ptw[16][1])

            # 41. all-initial-ecs-present-in-pathways (numeric)
            # 2. all-initial-ecs-present-in-pathway (boolean)
            # 3. prob-initial-ecs-present-in-pathway (numeric)
            if sample_initial_ec_lst:
                if len(sample_initial_ec_lst) == len(true_prev_ec_lst[:initial_reaction]):
                    m_features[0, 41] += 1
                    pathway_features[pidx, 2] = 1
                else:
                    if len(true_prev_ec_lst[:initial_reaction]) != 0:
                        pathway_features[pidx, 3] = len(sample_initial_ec_lst) / len(
                            true_prev_ec_lst[:initial_reaction])

            # 42. all-final-ecs-present-in-pathways (numeric)
            # 4.  all-final-ecs-present-in-pathway (boolean)
            # 5.  prob-final-ecs-present-in-pathway (numeric)
            if sample_final_ec_lst:
                if len(sample_final_ec_lst) == len(true_succ_ec_lst[:last_reaction]):
                    m_features[0, 42] += 1
                    pathway_features[pidx, 4] = 1
                else:
                    if len(true_succ_ec_lst[:last_reaction]) != 0:
                        pathway_features[pidx, 5] = len(sample_final_ec_lst) / len(true_succ_ec_lst[:last_reaction])

            # 43. all-initial-and-final-ecs-present-in-pathways (numeric)
            # 6.  all-initial-and-final-ecs-present-in-pathway (boolean)
            # 7.  prob-all-initial-and-final-ecs-present-in-pathway (numeric)
            if sample_initial_ec_lst and sample_final_ec_lst:
                if len(sample_initial_ec_lst) == len(true_prev_ec_lst[:initial_reaction]) and len(
                        sample_final_ec_lst) == len(
                    true_succ_ec_lst[:last_reaction]):
                    m_features[0, 43] += 1
                    pathway_features[pidx, 6] = 1
                    totalPECs = len(sample_initial_ec_lst) + len(sample_final_ec_lst)
                    totalTECs = len(true_prev_ec_lst[:initial_reaction]) + len(true_succ_ec_lst[:last_reaction])
                    pathway_features[pidx, 7] = totalPECs / totalTECs

            # 44. all-initial-ecs-present-in-deg-or-detox-pathways (numeric)
            # 45. all-final-ecs-present-in-deg-or-detox-pathways (numeric)
            # 8. all-initial-ecs-present-in-deg-or-detox-pathway (boolean)
            # 9. prob-all-initial-ecs-present-in-deg-or-detox-pathway (numeric)
            if 'detoxification' in text or 'degradation' in text:
                if len(sample_initial_ec_lst) == len(true_prev_ec_lst[:initial_reaction]):
                    m_features[0, 44] += 1
                    pathway_features[pidx, 8] = 1
                if len(sample_final_ec_lst) == len(true_succ_ec_lst[:last_reaction]):
                    m_features[0, 45] += 1
                    pathway_features[pidx, 9] = 1

            # 46. all-initial-ecs-present-in-biosynthesis-pathways (numeric)
            # 47. all-final-ecs-present-in-biosynthesis-pathways (numeric)
            # 10. all-initial-ecs-present-in-biosynthesis-pathway (boolean)
            # 11. prob-all-initial-ecs-present-in-biosynthesis-pathway (numeric)
            if 'biosynthesis' in text:
                if len(sample_initial_ec_lst) == len(true_prev_ec_lst[:initial_reaction]):
                    m_features[0, 46] += 1
                    pathway_features[pidx, 10] = 1
                if len(sample_final_ec_lst) == len(true_succ_ec_lst[:last_reaction]):
                    m_features[0, 47] += 1
                    pathway_features[pidx, 11] = 1

            # 48. most-ecs-absent-in-pathways (numeric)
            # 12. most-ecs-absent-in-pathway (boolean)
            if len(sample_ec_lst) == 1:
                if sample_ec_lst[0] in unique_ec_lst:
                    m_features[0, 48] += 1
                    pathway_features[0, 12] = 1

            # 49. most-ecs-absent-not-distinct-in-pathways (numeric)
            # 13. most-ecs-absent-not-distinct-in-pathway (boolean)
            if len(sample_ec_lst) < int(threshold * len(true_prev_ec_lst)):
                if unique_ec_lst:
                    m_features[0, 49] += 1
                    pathway_features[0, 13] = 1
                    for e in unique_ec_lst:
                        if e in sample_ec_lst:
                            m_features[0, 49] -= 1
                            pathway_features[0, 13] = 0
                            break

            # 50. one-ec-present-but-in-minority-in-pathways (numeric)
            # 14. one-ec-present-but-in-minority-in-pathway (boolen)
            if len(sample_ec_lst) == 1:
                if sample_ec_lst[0] in unique_ec_lst:
                    m_features[0, 50] += 1
                    pathway_features[0, 14] = 1

            # 51. all-distinct-ec-present-in-pathways (numeric)
            # 52. all-ecs-present-in-pathways (numeric)
            # 53. all-distinct-ec-present-or-orphaned-in-pathways (numeric)
            # 54. all-ec-present-or-orphaned-in-pathways (numeric)

            # 15. all-distinct-ec-present-in-pathway (boolean)
            # 16. all-ecs-present-in-pathway (boolean)
            # 17. all-distinct-ec-present-or-orphaned-in-pathway (boolean)
            # 18. all-ec-present-or-orphaned-in-pathway (boolean)

            m_features[0, 51] += 1
            m_features[0, 52] += 1
            m_features[0, 53] += 1
            m_features[0, 54] += 1

            pathway_features[pidx, 15] = 1
            pathway_features[pidx, 16] = 1
            pathway_features[pidx, 17] = 1
            pathway_features[pidx, 18] = 1

            if sample_unique_ec_lst != unique_ec_lst:
                m_features[0, 51] -= 1
                pathway_features[pidx, 15] = 0

            if sample_ec_lst != true_prev_ec_lst:
                m_features[0, 52] -= 1
                pathway_features[pidx, 16] = 0
            u = 0
            for ec in unique_ec_lst:
                if ec in sample_unique_ec_lst or ec in orphan_ec_lst:
                    u += 1
            if u != len(unique_ec_lst):
                m_features[0, 53] -= 1
                pathway_features[pidx, 17] = 0
            a = 0
            for ec in true_prev_ec_lst:
                if ec in sample_ec_lst or ec in orphan_ec_lst:
                    a += 1
            if a != len(true_prev_ec_lst):
                m_features[0, 54] -= 1
                pathway_features[pidx, 18] = 0

            # 55. majority-of-ecs-absent-in-pathways (numeric)
            # 56. majority-of-ecs-present-in-pathways (numeric)
            # 19. majority-of-ecs-absent-in-pathway (boolean)
            # 20. majority-of-ecs-present-in-pathway (boolean)
            if len(sample_ec_lst) < int(threshold * len(true_prev_ec_lst)):
                m_features[0, 55] += 1
                pathway_features[pidx, 19] = 1
            else:
                m_features[0, 56] += 1
                pathway_features[pidx, 20] = 1

            # 57. majority-of-distinct-ecs-present-in-pathways (numeric)
            # 21. majority-of-distinct-ecs-present-in-pathway (boolean)
            if len(sample_unique_ec_lst) > int(threshold * len(unique_ec_lst)):
                m_features[0, 57] += 1
                pathway_features[pidx, 21] = 1

            # 58. majority-of-reactions-present-distinct-in-pathways (numeric)
            # 22. majority-of-reactions-present-distinct-in-pathway (boolean)
            if len(sample_ec_lst) > int(threshold * len(true_prev_ec_lst)):
                m_features[0, 58] += 1
                pathway_features[pidx, 22] = 1
                for ec in sample_ec_lst:
                    if ec not in unique_ec_lst:
                        m_features[0, 58] -= 1
                        pathway_features[pidx, 22] = 0
                        break

            # 59.  missing-at-most-one-ec-in-pathways (numeric)
            # 23.  missing-at-most-one-ec-in-pathway (boolean)
            if len(sample_ec_lst) + 1 == len(true_prev_ec_lst):
                ec = set.difference(set(sample_ec_lst), set(true_prev_ec_lst))
                if ec not in orphan_ec_lst:
                    m_features[0, 59] += 1
                    pathway_features[pidx, 23] = 1

            # 60.  has-distinct-ecs-present-in-pathways (numeric)
            # 24.  has-distinct-ecs-present-in-pathway (boolean)
            if sample_unique_ec_lst:
                m_features[0, 60] += 1
                pathway_features[pidx, 24] = 1

            # 62.  fraction-reactions-present-or-orphaned-distinct-in-pathways (numeric)
            # 26.  fraction-reactions-present-or-orphaned-distinct-in-pathway (numeric)
            sample_rxn_unique_orphand = set.union(set(sample_unique_ec_lst), set(orphan_ec_lst))
            true_ec_orphand = set.union(set(true_prev_ec_lst), set(orphan_ec_lst))
            if len(true_ec_orphand) != 0:
                m_features[0, 61] += len(sample_rxn_unique_orphand) / len(true_ec_orphand)
                pathway_features[pidx, 25] = len(sample_rxn_unique_orphand) / len(true_ec_orphand)

            # 61.  fraction-distinct-ecs-present-or-orphaned-in-pathways (numeric)
            # 25.  fraction-distinct-ecs-present-or-orphaned-in-pathway (numeric)
            if len(unique_ec_lst):
                true_ec_unique_orphand = set.union(set(unique_ec_lst), set(orphan_ec_lst))
                if len(true_ec_unique_orphand) != 0:
                    m_features[0, 62] += len(sample_rxn_unique_orphand) / len(true_ec_unique_orphand)
                    pathway_features[pidx, 26] = len(sample_rxn_unique_orphand) / len(true_ec_unique_orphand)

            # 63.  fraction-reactions-present-or-orphaned-in-pathways (numeric)
            # 27.  fraction-reactions-present-or-orphaned-in-pathway (numeric)
            sample_rxn_orphand = set.union(set(sample_ec_lst), set(orphan_ec_lst))
            if len(true_ec_orphand) != 0:
                m_features[0, 63] += len(sample_rxn_orphand) / len(true_ec_orphand)
                pathway_features[pidx, 27] = len(sample_rxn_orphand) / len(true_ec_orphand)

            # 64.  num-distinct-reactions-present-or-orphaned-in-pathways (numeric)
            # 28.  num-distinct-reactions-present-or-orphaned-in-pathway (numeric)
            m_features[0, 64] += len(sample_rxn_unique_orphand)
            pathway_features[pidx, 28] = len(sample_rxn_unique_orphand)

            # 65.  num-reactions-present-or-orphaned-in-pathways (numeric)
            # 29.  num-reactions-present-or-orphaned-in-pathway (numeric)
            m_features[0, 65] += len(sample_rxn_orphand)
            pathway_features[pidx, 29] = len(sample_rxn_orphand)

            # 66.  evidence-info-content-norm-present-in-pathways (numeric)
            # 67.  evidence-info-content-present-in-pathways (numeric)
            # 30.  evidence-info-content-norm-present-in-pathway (numeric)
            # 31.  evidence-info-content-present-in-pathway (numeric)
            total = 0
            for ec in set(sample_ec_lst):
                total += 1 / matrix_list[0][:, np.where(col_idx == ec)[0]].nnz
            if sample_ec_lst:
                if len(true_prev_ec_lst) != 0:
                    m_features[0, 66] += total / len(true_prev_ec_lst)
                    pathway_features[pidx, 30] = total / len(true_prev_ec_lst)
                m_features[0, 67] += total
                pathway_features[pidx, 31] = total

        m_features[0, 39:] = m_features[0, 39:] / pathway_features.shape[0]
        pathway_features = pathway_features.reshape(1, pathway_features.shape[0] * pathway_features.shape[1])
        # 68.  possible-pathways-present (boolean)
        max_val = np.max(ratio_possible_pathways) * beta
        possible_pathways[ratio_possible_pathways >= max_val] = 1
        possible_pathways[ratio_possible_pathways >= threshold] = 1
        # 69.  prob-possible-pathways-present (numeric)
        ratio_possible_pathways = ratio_possible_pathways
        total_features = np.hstack((m_features, possible_pathways, ratio_possible_pathways, pathway_features))
        return total_features

    def reaction_evidence_features(self, instance, info_list, ptwy_ec_matrix, num_features=42, initial_reaction=2,
                                   last_reaction=2, threshold=0.5):
        m_features = np.zeros(shape=(len(self.pathway_id), num_features), dtype=np.float32)
        regex = re.compile(r'\(| |\)')

        for id in self.pathway_id:
            ptw = info_list[0][id]
            text = str(ptw[0][1]) + ' ' + ' '.join(ptw[1][1]) + ' ' + ' '.join(ptw[6][1])
            text = text.lower()
            true_rxn_predecessors = [list(filter(None, regex.split(itm))) for itm in info_list[0][id][3][1]]
            for idx, itm in enumerate(true_rxn_predecessors):
                itm = ' '.join(itm).replace('\"', '')
                true_rxn_predecessors[idx] = itm.split()
            dg = nx.DiGraph()
            dg.add_nodes_from(info_list[0][id][4][1])
            for itm in true_rxn_predecessors:
                if len(itm) == 1:
                    continue
                else:
                    dg.add_edge(itm[1], itm[0])
            true_prev_ec_lst = sorted(dg.in_degree(), key=dg.in_degree().get)
            true_prev_ec_lst = [j for itm in true_prev_ec_lst for j in info_list[1][itm][3][1] if
                                j in info_list[0][id][15][1]]
            true_succ_ec_lst = sorted(dg.out_degree(), key=dg.out_degree().get)
            true_succ_ec_lst = [j for itm in true_succ_ec_lst for j in info_list[1][itm][3][1] if
                                j in info_list[0][id][15][1]]

            unique_ec_lst = list()
            orphan_ec_lst = list()
            for rxn in ptw[16][1]:
                if info_list[1][rxn][3][1]:
                    e = [e for e in info_list[1][rxn][3][1]]
                    unique_ec_lst.extend(e)
                if info_list[1][rxn][5][1] != False:
                    orphan_ec_lst.extend(e)

            #######################################################################################################
            ################################ Extracting Info from Experimental Data ###############################
            #######################################################################################################

            sample_ec_lst = [ec for ec in ptw[15][1] if instance[:, self.ec_id[ec]] != 0]
            sample_unique_ec_lst = [ec for ec in sample_ec_lst if ec in unique_ec_lst]
            sample_orphan_ec_lst = [ec for ec in sample_ec_lst if ec in orphan_ec_lst]
            if true_prev_ec_lst:
                sample_initial_ec_lst = [1 for ec in true_prev_ec_lst[:initial_reaction] if ec not in sample_ec_lst]
            if true_succ_ec_lst:
                sample_final_ec_lst = [1 for ec in true_succ_ec_lst[:last_reaction] if ec not in sample_ec_lst]

            #######################################################################################################
            ############################## Extracting Features from Experimental Data #############################
            #######################################################################################################

            # 0. energy-pathway-mostly-missing (boolean)
            if 'energy' in text:
                if len(sample_ec_lst) < int(threshold * len(true_prev_ec_lst)):
                    m_features[self.pathway_id[id], 0] = 1

            # 1. mostly-present (boolean)
            if len(sample_ec_lst) > int(threshold * len(true_prev_ec_lst)):
                m1 = len(sample_ec_lst) + 1 == len(ptw[15][1])
                m2 = (len(sample_ec_lst) / len(ptw[15][1])) >= threshold
                if m1 and m2:
                    m_features[self.pathway_id[id], 1] = 1

            # 2.   some-initial-reactions-present (boolean)
            if sample_initial_ec_lst:
                m_features[self.pathway_id[id], 2] = 1

            # 3.  all-initial-reactions-present (boolean)
            if sample_initial_ec_lst:
                if len(sample_initial_ec_lst) == len(true_prev_ec_lst[:initial_reaction]):
                    m_features[self.pathway_id[id], 3] = 1

            # 4.   some-final-reactions-present (boolean)
            if sample_final_ec_lst:
                m_features[self.pathway_id[id], 4] = 1

            # 5.   all-final-reactions-present (boolean)
            if sample_final_ec_lst:
                if len(sample_final_ec_lst) == len(true_succ_ec_lst[:last_reaction]):
                    m_features[self.pathway_id[id], 5] = 1

            # 6.   some-initial-and-final-reactions-present (boolean)
            if sample_initial_ec_lst and sample_final_ec_lst:
                m_features[self.pathway_id[id], 6] = 1

            # 7.   all-initial-and-final-reactions-present  (boolean)
            if sample_initial_ec_lst and sample_final_ec_lst:
                if len(sample_initial_ec_lst) == len(true_prev_ec_lst[:initial_reaction]) and len(
                        sample_final_ec_lst) == len(
                    true_succ_ec_lst[:last_reaction]):
                    m_features[self.pathway_id[id], 7] = 1

            # 8.   deg-or-detox-pathway-all-initial-reactions-present (boolean)
            # 9.   deg-or-detox-pathway-all-final-reactions-present (boolean)
            if 'detoxification' in text or 'degradation' in text:
                if len(sample_initial_ec_lst) == len(true_prev_ec_lst[:initial_reaction]):
                    m_features[self.pathway_id[id], 8] = 1
                if len(sample_final_ec_lst) == len(true_succ_ec_lst[:last_reaction]):
                    m_features[self.pathway_id[id], 9] = 1

            # 10.   biosynthesis-pathway-all-initial-reactions-present (boolean)
            # 11.   biosynthesis-pathway-all-final-reactions-present (boolean)
            if 'biosynthesis' in text:
                if len(sample_initial_ec_lst) == len(true_prev_ec_lst[:initial_reaction]):
                    m_features[self.pathway_id[id], 10] = 1
                if len(sample_final_ec_lst) == len(true_succ_ec_lst[:last_reaction]):
                    m_features[self.pathway_id[id], 11] = 1

            # 12.  mostly-absent (boolean)
            if len(sample_ec_lst) == 0:
                m_features[self.pathway_id[id], 12] = 1
            elif len(sample_ec_lst) == 1:
                if sample_ec_lst[0] in unique_ec_lst:
                    m_features[self.pathway_id[id], 12] = 1

            # 13.  mostly-absent-not-unique (boolean)
            if len(sample_ec_lst) < int(threshold * len(true_prev_ec_lst)):
                if unique_ec_lst:
                    m_features[self.pathway_id[id], 13] = 1
                    for e in unique_ec_lst:
                        if e in sample_ec_lst:
                            m_features[self.pathway_id[id], 13] = 0
                            break

            # 14.  one-reaction-present-but-in-minority (boolean)
            if len(sample_ec_lst) == 1:
                if sample_ec_lst[0] in unique_ec_lst:
                    m_features[self.pathway_id[id], 14] = 1

            # 15.  every-unique-reaction-present (boolean)
            # 16.  every-reaction-present (boolean)
            # 17.  every-unique-reaction-present-or-orphaned (boolean)
            # 18.  every-reaction-present-or-orphaned (boolean)
            if sample_ec_lst:
                m_features[self.pathway_id[id], 15] = 1
                m_features[self.pathway_id[id], 16] = 1
                m_features[self.pathway_id[id], 17] = 1
                m_features[self.pathway_id[id], 18] = 1
                if sample_ec_lst != unique_ec_lst:
                    m_features[self.pathway_id[id], 15] = 0
                if sample_ec_lst != true_prev_ec_lst:
                    m_features[self.pathway_id[id], 16] = 0
                u = 0
                for ec in unique_ec_lst:
                    if ec in sample_unique_ec_lst or ec in orphan_ec_lst:
                        u += 1
                if u != len(unique_ec_lst):
                    m_features[self.pathway_id[id], 17] = 0
                a = 0
                for ec in true_prev_ec_lst:
                    if ec in sample_ec_lst or ec in orphan_ec_lst:
                        a += 1
                if a != len(true_prev_ec_lst):
                    m_features[self.pathway_id[id], 18] = 0

            # 19.  majority-of-reactions-absent (boolean)
            # 20.  majority-of-reactions-present (boolean)
            if len(sample_ec_lst) < int(threshold * len(true_prev_ec_lst)):
                m_features[self.pathway_id[id], 19] = 1
            else:
                m_features[self.pathway_id[id], 20] = 1

            # 21.  majority-of-unique-reactions-present (boolean)
            if len(sample_unique_ec_lst) > int(threshold * len(unique_ec_lst)):
                m_features[self.pathway_id[id], 21] = 1

            # 22.  majority-of-reactions-present-unique (boolean)
            if len(sample_ec_lst) > int(threshold * len(true_prev_ec_lst)):
                m_features[self.pathway_id[id], 22] = 1
                for ec in sample_ec_lst:
                    if ec not in unique_ec_lst:
                        m_features[self.pathway_id[id], 22] = 0
                        break

            # 23.  missing-at-most-one-reaction (boolean)
            if len(sample_ec_lst) + 1 == len(true_prev_ec_lst):
                ec = set.difference(set(sample_ec_lst), set(true_prev_ec_lst))
                if ec not in orphan_ec_lst:
                    m_features[self.pathway_id[id], 23] = 1

            # 24.  has-unique-reactions-present (boolean)
            if sample_ec_lst:
                if sample_unique_ec_lst:
                    m_features[self.pathway_id[id], 24] = 1

            # 25.  has-reactions-present (boolean)
            if sample_ec_lst:
                m_features[self.pathway_id[id], 25] = 1

            # 26.  fraction-final-reactions-present (numeric)
            if len(true_prev_ec_lst):
                m_features[self.pathway_id[id], 26] = len(sample_final_ec_lst) / len(true_prev_ec_lst)

            # 27.  fraction-initial-reactions-present (numeric)
            if len(true_prev_ec_lst):
                m_features[self.pathway_id[id], 27] = len(sample_initial_ec_lst) / len(true_prev_ec_lst)

            # 28.  num-final-reactions-present (numeric)
            m_features[self.pathway_id[id], 28] = len(sample_final_ec_lst)

            # 29.  num-initial-reactions-present (numeric)
            m_features[self.pathway_id[id], 29] = len(sample_initial_ec_lst)

            # 30.  fraction-unique-reactions-present-or-orphaned (numeric)
            sample_rxn_unique_orphand = set.union(set(sample_unique_ec_lst), set(orphan_ec_lst))
            if len(true_prev_ec_lst):
                m_features[self.pathway_id[id], 30] = len(sample_rxn_unique_orphand) / len(true_prev_ec_lst)

            # 31.  fraction-reactions-present-or-orphaned-unique (numeric)
            if len(unique_ec_lst):
                m_features[self.pathway_id[id], 31] = len(sample_rxn_unique_orphand) / len(unique_ec_lst)

            # 32.  fraction-reactions-present-or-orphaned (numeric)
            sample_rxn_orphand = set.union(set(sample_ec_lst), set(orphan_ec_lst))
            if len(true_prev_ec_lst):
                m_features[self.pathway_id[id], 32] = len(sample_rxn_orphand) / len(true_prev_ec_lst)

            # 33.  fraction-unique-reactions-present (numeric)
            if len(true_prev_ec_lst):
                m_features[self.pathway_id[id], 33] = len(sample_unique_ec_lst) / len(true_prev_ec_lst)

            # 34.  fraction-reactions-present-unique (numeric)
            if len(unique_ec_lst):
                m_features[self.pathway_id[id], 34] = len(sample_unique_ec_lst) / len(unique_ec_lst)

            # 35.  fraction-reactions-present (numeric)
            if len(true_prev_ec_lst):
                m_features[self.pathway_id[id], 35] = len(sample_ec_lst) / len(true_prev_ec_lst)

            # 36.  num-unique-reactions-present-or-orphaned (numeric)
            m_features[self.pathway_id[id], 36] = len(sample_rxn_unique_orphand)

            # 37.  num-reactions-present-or-orphaned (numeric)
            m_features[self.pathway_id[id], 37] = len(sample_rxn_orphand)

            # 38.  num-unique-reactions-present (numeric)
            m_features[self.pathway_id[id], 38] = len(sample_unique_ec_lst)

            # 39.  num-reactions-present (numeric)
            m_features[self.pathway_id[id], 39] = len(sample_ec_lst)

            # 40.  evidence-info-content-norm-present (numeric)
            total = 0
            for ec in set(sample_ec_lst):
                total += 1 / np.count_nonzero(ptwy_ec_matrix[:, self.ec_id[ec]])
            if sample_ec_lst:
                m_features[self.pathway_id[id], 40] = total / len(sample_ec_lst)

            # 41.  evidence-info-content-unnorm-present (numeric)
            m_features[self.pathway_id[id], 41] = total

        return m_features


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

"""
Sub-command for Changes.

In all cases ``data = ctx.params`` when calling the appropriate action method
on ``ctx.obj``. (e.g. ``ctx.obj.add(ctx.params)``)

Also, ``action = ctx.info_name`` *might* reliably contain the name of the
action function, but still not sure about that. If so, every function could be
fundamentally simplified to this::

    getattr(ctx.obj, ctx.info_name)(ctx.params)
"""

from __future__ import unicode_literals

from __future__ import absolute_import
import click
from . import callbacks

__author__ = 'Jathan McCollum'
__maintainer__ = 'Jathan McCollum'
__email__ = 'jathan@dropbox.com'
__copyright__ = 'Copyright (c) 2015 Dropbox, Inc.'

# Ordered list of 2-tuples of (field, display_name) used to translate object
# field names oto their human-readable form when calling .print_list().
DISPLAY_FIELDS = (
    ('id', 'ID'),
    ('change_at', 'Change At'),
    ('user', 'User'),
    ('event', 'Event'),
    ('resource_name', 'Resource'),
    ('resource_id', 'Obj'),
    # ('site_id', 'Site ID'),
    # ('site', 'Site'),
)

# Fields to display when viewing a single record.
VERBOSE_FIELDS = (
    ('change_at', 'Change At'),
    ('user', 'User'),
    ('event', 'Event'),
    ('resource_name', 'Resource'),
    ('resource_id', 'ID'),
    ('resource', 'Data'),
)

# Main group
@click.group()
@click.pass_context
def cli(ctx):
    """
    Change events.

    All add, remove, and update events are logged as a Change. A Change
    includes information such as the change time, user, and the full resource
    after modification. Changes are immutable and can only be removed by
    deleting the entire Site.
    """

# List
@cli.command()
@click.option(
    '-e',
    '--event',
    metavar='EVENT',
    help='Filter result to specific event.',
    callback=callbacks.transform_event,
)
@click.option(
    '-i',
    '--id',
    metavar='ID',
    type=int,
    help='Unique ID of the Change being retrieved.',
)
@click.option(
    '-l',
    '--limit',
    metavar='LIMIT',
    type=int,
    help='Limit result to N resources.',
)
@click.option(
    '-o',
    '--offset',
    metavar='OFFSET',
    type=int,
    help='Skip the first N resources.',
)
@click.option(
    '-R',
    '--resource-id',
    metavar='RESOURCE_ID',
    type=int,
    help='Filter to Changes for a specific resource ID (e.g. Network ID 1)',
)
@click.option(
    '-r',
    '--resource-name',
    metavar='RESOURCE_NAME',
    help='Filter to Changes for a specific resource name (e.g. Network)',
    callback=callbacks.transform_resource_name,
)
@click.option(
    '-s',
    '--site-id',
    metavar='SITE_ID',
    type=int,
    help='Unique ID of the Site this Change is under.  [required]',
    callback=callbacks.process_site_id,
)
@click.pass_context
def list(ctx, event, id, limit, offset, resource_id, resource_name, site_id):
    """
    List Change events for a Site.

    You must provide a Site ID using the -s/--site-id option.

    When listing Changes, all events are displayed by default. You may
    optionally lookup a single Change by ID using the -i/--id option.

    You may limit the number of results using the -l/--limit option.
    """
    data = ctx.params

    # If we provide ID, be verbose
    if id is not None:
        display_fields = VERBOSE_FIELDS
    else:
        display_fields = DISPLAY_FIELDS

    ctx.obj.list(data, display_fields=display_fields)


import numpy as np
import h5py
import torch
from datasets import ExtractDataset

def extract_collate(batch):
    assert len(batch) == 1
    x = batch[0][0]
    spk_id = batch[0][1]
    utt_id = batch[0][2]
    return [x[None, ...], spk_id, utt_id]

def sequential_extract(model, mfcc_file, save_xvec_to):
    print(f"Reading mfcc from {mfcc_file}")
    with h5py.File(mfcc_file, "r") as fr:
        dset = ExtractDataset(fr)
        loader = torch.utils.data.DataLoader(
            dset, batch_size=1, shuffle=False, collate_fn=extract_collate
        )
        print(f"Saving xvectors to {save_xvec_to}")
        with h5py.File(save_xvec_to, "w") as fw:
            fw["X"], fw["spk_ids"], fw["spk_path"] = _extraction(model, loader)

def _extraction(model, loader):
    unicode = h5py.special_dtype(vlen=str)
    model.eval()
    X, spk_ids, utt_ids = [], [], []
    with torch.no_grad():
        for batch_idx, (mfcc, spk_id, utt_id) in enumerate(loader):
            mfcc = mfcc.to(torch.device(torch.cuda.current_device()))
            x = model.extract(mfcc)
            spk_ids.append(spk_id)
            utt_ids.append(utt_id)
            X.append(x)

        X = torch.cat(X).to("cpu").numpy()
        spk_ids = np.stack(spk_ids).astype(unicode)
        utt_ids = np.stack(utt_ids).astype(unicode)
    return X, spk_ids, utt_ids


from random import randint
import random

max = 10000
value_field = []
for i in range(20):
    value_field.append(max)
for i in range(20, 101):
    value_field.append(i)

size = 10
map = []
for i in range(size):
    map.append([0]*size)

for i in range(size):
    for j in range(i, size):
        if i == j:
            map[i][j] = max
            map[j][i] = map[i][j]
        else:
            # r = randint(0, 2)
            # if r == 0:
            #     map[i][j] = "max"
            #     map[j][i] = map[i][j]
            # if r == 1:
            #     map[i][j] = randint(1, 50)
            #     map[j][i] = map[i][j]
            # if r == 2:
            #     map[i][j] = randint(50, 100)
            #     map[j][i] = map[i][j]
            map[i][j] = random.choice(value_field)
            map[j][i] = map[i][j]

for i in map:
    print(i)

with open('map.txt', 'w', encoding='utf-8') as w:
    w.write(str(size) + '\n')
    for i in map:
        for j in i:
            w.write(str(j) + " ")
        w.write('\n')
    w.close()


numArr = []
for i in range(9):
    numArr.append(list(map(int,input().split(" "))))

resultMap = dict(maxNumber = 0, y = 0, x = 0)
for i in range(len(numArr)):
    if max(numArr[i]) >= resultMap["maxNumber"]:
        resultMap["maxNumber"] = max(numArr[i])
        resultMap["y"] = i + 1
        resultMap["x"] = numArr[i].index(max(numArr[i])) + 1

print(resultMap["maxNumber"])
print(resultMap["y"], resultMap["x"])


#
# @lc app=leetcode id=804 lang=python3
#
# [804] Unique Morse Code Words
#
# https://leetcode.com/problems/unique-morse-code-words/description/
#
# algorithms
# Easy (80.04%)
# Likes:    1991
# Dislikes: 1348
# Total Accepted:    284.1K
# Total Submissions: 344.5K
# Testcase Example:  '["gin","zen","gig","msg"]'
#
# International Morse Code defines a standard encoding where each letter is
# mapped to a series of dots and dashes, as follows:
#
#
# 'a' maps to ".-",
# 'b' maps to "-...",
# 'c' maps to "-.-.", and so on.
#
#
# For convenience, the full table for the 26 letters of the English alphabet is
# given below:
#
#
#
# [".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."]
#
# Given an array of strings words where each word can be written as a
# concatenation of the Morse code of each letter.
#
#
# For example, "cab" can be written as "-.-..--...", which is the concatenation
# of "-.-.", ".-", and "-...". We will call such a concatenation the
# transformation of a word.
#
#
# Return the number of different transformations among all words we have.
#
#
# Example 1:
#
#
# Input: words = ["gin","zen","gig","msg"]
# Output: 2
# Explanation: The transformation of each word is:
# "gin" -> "--...-."
# "zen" -> "--...-."
# "gig" -> "--...--."
# "msg" -> "--...--."
# There are 2 different transformations: "--...-." and "--...--.".
#
#
# Example 2:
#
#
# Input: words = ["a"]
# Output: 1
#
#
#
# Constraints:
#
#
# 1 <= words.length <= 100
# 1 <= words[i].length <= 12
# words[i] consists of lowercase English letters.
#
#
#
from typing import List
from algo_input import run
from types import MethodType

# @lc code=start
class Solution:

    def uniqueMorseRepresentations(self, words: List[str]) -> int:
        lookup = [
            ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..",
            ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.",
            "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."
        ]
        return len(
            set("".join(map(lambda c: lookup[ord(c) - ord('a')], w))
                for w in words))

# @lc code=end
if __name__ == "__main__":
    run(
        MethodType(Solution.uniqueMorseRepresentations, Solution()),
        [
            [[["gin", "zen", "gig", "msg"]], 2],
            [[["a"]], 1],
        ],
    )