"""
Disclaimer

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Module cash settled swaptions. Primary item contained in this module is class cash_settled_swaptions.Model.

Model
=====

Define Ann(.) as the cash settled annuity formula.

  (1)  Ann(y, s) = df(s, t) * [year fraction] * sum_(cash flow n) (1 + [year fraction] * y) ** -n, 0 <= s <= t
  [See get_annuity(...) for Ann(., t)]

Define the swap-settled forward:

  (2)  y(s) = (y(0) + displacement) * exp{ sigma * W(s) - 1/2 * sigma * sigma * s } - displacement, for 0 <= s <= t
  [See DisplacedLogNormalModel.get_forward(...)]

where W(.) is a Brownian motion.
Analogously, define the cash-settled forward, with its separate displacement and volatility sigma*:

  (3)  y*(s) = (y*(0) + displacement*) * exp{ sigma* * W(s) - 1/2 * sigma* * sigma* * s } - displacement*, for 0 <=s<= t
  [See CashSettledSwaptionModel.get_forward(...)]

Work under the measure associated with the numeraire: Ann(y*(.), .).
To obtain consistency with the cash-settled annuity formula, have y*(0) = y(0).
Under this measure, define the present value of a basis point (aka swap-settled annuity):

  (4)  pvbp(s) = lambda * Ann(y*(s), s) for 0 <= s <= t
  [Does not explicitly occur in the code below]

Note that the numeraire-relative pvbp (pvbp(.) / Ann(y*(.), .))
is a martingale under the chosen measure, by construction.
lambda is chosen to ensure that the value of the pvbp at time zero matches its market value:

  (5)  lambda = pvbp(0) / Ann(y*(0), 0)
  [Does not explicitly occur in the code below]

The numeraire-relative floating leg value y(.) * pvbp(.) is also a martingale under the Ann(y*(.), .)-measure.
The value of a swap-settled payer swaption with strike K is then given by:

  (6)  V(swap settled swaption) = Ann(y*(0), 0) * E[ max(y(t) - K, 0) * pvbp(t) / Ann(y*(t), t) ]
                                = pvbp(0)       * E[ max(y(t) - K, 0) ]
  [See PhysicalSwaptionModel.get_payoff(...)]

Note that this is the standard Black formula under the swap-settled measure.
The swap-settled volatility parameter sigma is therefore naturally equal to the swap settled volatility.
The value of a cash-settled payer swaption is:

  (7)  V(cash settled swaption) = Ann(y*(0), 0) * E[ max(y(t) - K, 0) * Ann(y(t), t) / Ann(y*(t), t) ]
  [See CashSettledSwaptionModel.get_payoff(...) and CashSettledSwaptionModel.diffused_payoff(...)]

which can be efficiently evaluated by numerical quadrature integration (see the calls to integrate.quad routine).

Free parameters
===============

The above model has four freely varying parameters. Denote at-the-money as ATM.
|---------------------------|--------------------------------------------------------------------------------|
| Swap-Settled Volatility	| 1. sigma                                                                       |
|                           | 2. Equal to the market quoted ATM swap settled volatility for ATM              |
|                           | 3. Calibrated to cash settled skew information and put-call parity for non-ATM |
|---------------------------|--------------------------------------------------------------------------------|
| Swap-Settled Displacement | Fixed by the market data                                                       |
|---------------------------|--------------------------------------------------------------------------------|
| Cash-Settled Volatility   | 1. sigma*                                                                      |
|                           | 2. ATM cash-settled vol calibrated to zero-width collar                        |
|                           | 3. Calibrated to cash settled skew information and put-call parity for non-ATM |
|---------------------------|--------------------------------------------------------------------------------|
| Cash-Settled Displacement | 1. Calibrated to the at-the-money zero width collar                            |
|                           | 2. Denoted as displacement*                                                    |
|---------------------------|--------------------------------------------------------------------------------|

Put-call parity
===============

For a given strike K consider long a payer and short a receiver cash settled swaption.
Such package will be referred to as put-call combo (PCC). The payoff of a PCC is:

  (8)  Ann(y(t), t) * (y(t) - K)
  [Does not explicitly occur in the code below]

Now for given strikes K1 and K2 consider a PCC spread, consisting of long a PCC at strike K1 & short a PCC at strike K2.
The payoff of a PCC spread is:

  (9)  Ann(y(t), t) * (K2 - K1)
  [Does not explicitly occur in the code below]

Therefore the value of a PCC spread can be written as:

  (10)  V(PCC spread, K1, K2) = [cash settled convexity adjusted pvbp] * (K2 - K1)
  [See ParityCalibrator.get_parity_error(...)]

where

  (11)  [cash settled convexity adjusted pvbp] = Ann(y*(0), 0) * E[ Ann(y(t), t) / Ann(y*(t), t) ]
  [See CashSettledSwaptionModel.diffused_payoff(...) in which payoff_function(...) is the constant 1.0]

Calibration
===========

The steps to calibrate the model are as follows:

1. Copy swap-settled displacement from the market data.
   See Model.__init__(...)

2. Copy swap-settled ATM volatility from the market data.
   See Model.__init__(...)

3. Calibrate ATM cash-settled volatility and general cash-settled displacement from the ATM zero-width collar value.
   See Model.calibrate(...)

4. Calculate the cash-settled convexity adjusted pvbp according to formula (11).
   See Model.__init__(...): convexity_adjusted_cash_settled_pvbp = integrate(self.atm_model.get_unit_payoff)

5. Calibrate sigma(K) and sigma*(K) to the cash-settled swaption vol & put-call parity relation (10) for other strikes.
   See Model.calibrate_cash_model(...)

Usage Example
=============
import cash_settled_swaptions as css

# calibrate the model using the at-the-money data
model = css.Model(
    forward, displacement, expiry_time, swap_atm_vol, cash_pay_atm_vol,
    cash_receive_atm_vol, cash_annuity_info, cash_pvbp, swap_settled_pvbp,
)

# calibrate to a given cash settled swaption with strike away from ATM
strike_model = model.calibrate_cash_model(strike, cash_strike_vol, css.OptionType.PAYER)

# observe the vols calculated by the strike-model
cash_payer_vol = strike_model.get_implied_vol(css.OptionType.PAYER)
cash_receiver_vol = strike_model.get_implied_vol(css.OptionType.RECEIVER)
swap_payer_vol = strike_model.physical_swaption_model.get_implied_vol(css.OptionType.PAYER)
swap_recv_vol = strike_model.physical_swaption_model.get_implied_vol(css.OptionType.RECEIVER)

Volatility implication for the Black formula
============================================
Otherwise, this module contains a
stand-alone volatility implication function. The stand-alone implementation uses the paper 'By Implication' by
Peter Jaeckel (Wilmott, pages 60-66, November 2006).
"""

import enum
import math
import sys

import matplotlib.pyplot as plt
import numpy as np
import scipy.integrate
import scipy.optimize
import scipy.stats

DBL_DIG = sys.float_info.dig
DBL_EPSILON = sys.float_info.epsilon  # smallest such that 1.0+DBL_EPSILON != 1.0
DBL_MIN = sys.float_info.min
SQRT_DBL_EPSILON = math.sqrt(DBL_EPSILON)
FOURTH_ROOT_DBL_EPSILON = math.sqrt(SQRT_DBL_EPSILON)
ONE_DIV_SQRT_TWO_PI = 0.3989422804014326779399460599343818684758586311649
UNDERLYING_FLOAT_TENOR_TO_YEAR_FRACTION = {
    '3M': 0.25,
    '6M': 0.5,
    '1Y': 1.0,
    '12M': 1.0
}

class ImpliedVolatility:
    """
    Class to calculate the implied volatility using python. See the paper of Jaeckel 'by implication'
    """

    class OptionType(enum.Enum):
        """
        Option type. call or put
        """
        CALL = 1
        PUT = -1

    @staticmethod
    def intrinsic_value(forward, strike, option_type):
        """
        Intrinsic value of the option
        :param forward: float
        :param strike: float
        :param option_type: OptionType
        :return: float
        """
        return max(float(option_type.value) * (forward - strike), 0.0)

    @staticmethod
    def cube(x):
        """
        Little helper function, calculates x * x * x
        :param x: float
        :return: float
        """
        return x * x * x

    @classmethod
    def normalised_black_call(cls, x, sigma):
        """
        The normalized Black call formula is the one where
        1. the argument x = ln(forward / strike)
        2. its value is b(.) = [black formula] / sqrt(forward * strike)
        :param x: float
        :param sigma: float
        :return: float
        """
        x2 = x * x
        s2 = sigma * sigma
        b_max = math.exp(0.5 * x)
        one_over_b_max = 1.0 / b_max
        if x2 < DBL_EPSILON * s2 or (x2 + s2) < DBL_EPSILON:
            if s2 * s2 > DBL_EPSILON:
                b0 = 1.0 - 2.0 * scipy.stats.norm.cdf(-0.5 * sigma)
            else:
                b0 = ONE_DIV_SQRT_TWO_PI * sigma * (
                    1.0 - s2 * (1.0 / 24.0 - s2 * (1.0 / 640.0 - s2 * (1.0 / 21504.0 - s2 / 884736.0)))
                )
            return max(b0 + 0.5 * x, 0.0)
        xi = x / sigma
        if s2 < DBL_EPSILON * x2:
            phi0 = math.exp(-0.5 * xi * xi) * ONE_DIV_SQRT_TWO_PI
            xi2 = xi * xi
            return max(
                phi0 * math.exp(-0.125 * s2) * 4.0 * sigma / cls.cube(4.0 * xi2 - s2) *
                (8.0 * xi2 * (2.0 * xi2 - s2 - 6.0) + s2 * (s2 - 4.0)),
                0.0,
            )
        return max(
            scipy.stats.norm.cdf(xi + 0.5 * sigma) * b_max - scipy.stats.norm.cdf(xi - 0.5 * sigma) * one_over_b_max,
            0.0
        )

    @staticmethod
    def get_sigma_high(beta, b_c, b_max, phi_c):
        """
        The 'high' initial guess vol function
        :param beta: float
        :param b_c: float
        :param b_max: float
        :param phi_c: float
        :return: float
        """
        return -2.0 * scipy.stats.norm.ppf((b_max - beta) / (b_max - b_c) * phi_c)

    @staticmethod
    def get_sigma_low(beta, ln_b_c, two_x_square, ax):
        """
        The 'low' initial guess vol function
        :param beta: float
        :param ln_b_c: float
        :param two_x_square: float
        :param ax: float
        :return: float
        """
        return math.sqrt(two_x_square / (ax - 4.0 * (math.log(beta) - ln_b_c)))

    @classmethod
    def implied_volatility(cls, input_premium, forward, strike, expiry_time, call_put):
        """
        The Black implied volatility function
        :param input_premium: float
        :param forward: float
        :param strike: float
        :param expiry_time: float
        :param call_put: str. 'call' or 'put'
        :return: float
        """
        option_type = cls.OptionType[call_put.upper()]
        intrinsic = cls.intrinsic_value(forward, strike, option_type)
        if input_premium == intrinsic:
            return 0.0
        if intrinsic >= input_premium:
            raise ValueError(
                "Black implied_volatility: cannot calculate implied volatility if specified premium is less than the "
                "minimum possible price. In this case, the given premium was " + str(input_premium) + " and the "
                "intrinsic value is " + str(intrinsic)
            )
        input_premium_upper_bound = strike if option_type == cls.OptionType.PUT else forward
        if input_premium >= input_premium_upper_bound:
            raise ValueError(
                "Black implied volatility: the given premium is higher than the option price for infinite implied "
                "volatility and thus there is no implied volatility that matches this price."
            )

        premium = input_premium - intrinsic
        # We map everything such that we are solving effectively for equivalent out-of-the-money call options.
        # This means, below we have that effective theta==1, effective x = -|x|
        ax = abs(math.log(forward/strike))
        x = -ax
        beta = premium / math.sqrt(forward * strike)
        ln_beta = math.log(beta)
        b_max = math.exp(0.5 * x)
        sigma_c = math.sqrt(2.0 * ax)
        b_c = cls.normalised_black_call(x, sigma_c)
        phi_c = scipy.stats.norm.cdf(-0.5 * sigma_c)
        sigma_high = cls.get_sigma_high(beta, b_c, b_max, phi_c)
        relative_accuracy = SQRT_DBL_EPSILON * FOURTH_ROOT_DBL_EPSILON
        sigma = sigma_high
        previous_sigma = sys.float_info.max
        beta_less_than_b_c = beta < b_c
        if beta_less_than_b_c:
            sigma_star = cls.get_sigma_high(0, b_c, b_max, phi_c)
            b_star = cls.normalised_black_call(x, sigma_star)
            x2 = x * x
            two_x_square = 2.0 * x2
            ln_b_c = math.log(b_c)
            sigma_low_star = cls.get_sigma_low(b_star, ln_b_c, two_x_square, ax)
            sigma_high_star = cls.get_sigma_high(b_star, b_c, b_max, phi_c)
            gamma = float(
                math.log((sigma_star - sigma_low_star) / (sigma_high_star - sigma_low_star)) / math.log(b_star / b_c)
            )
            xi = beta / b_c
            w = pow(xi, gamma)
            sigma *= w
            sigma += (1.0 - w) * cls.get_sigma_low(beta, ln_b_c, two_x_square, ax)
            if cls.normalised_black_call(x, sigma) < DBL_MIN:
                sigma += sigma_star
                sigma *= 0.5
                if cls.normalised_black_call(x, sigma) < DBL_MIN:
                   sigma += sigma_c
                   sigma *= 0.5
        i = 0
        while i < ((DBL_DIG * 10) / 3) and abs(sigma / previous_sigma - 1.0) > relative_accuracy:
            i += 1
            previous_sigma = sigma
            half_sigma = 0.5 * sigma
            xoversigma = x / sigma
            xoversigma2 = xoversigma * xoversigma
            b = cls.normalised_black_call(x, sigma)
            vega = ONE_DIV_SQRT_TWO_PI * math.exp(-0.5*(xoversigma2+half_sigma*half_sigma))
            vannaovervega = xoversigma2 / sigma - 0.25 * sigma
            if beta_less_than_b_c:
                ln_b = math.log(b)
                nu = max((ln_beta - ln_b) * ln_b / ln_beta * b / vega, -half_sigma)
                eta = max(0.5 * nu * (vannaovervega - (2.0 / ln_b + 1.0) * vega / b), -0.75)
            else:
                nu = max((beta - b) / vega, -half_sigma)
                eta = max(0.5 * nu * vannaovervega, -0.75)
            sigma += max(nu / (1.0 + eta), -half_sigma)
        # The only situations observed thus far when the above iteration doesn't converge within (DBL_DIG*10)/3 == 50
        # iterations are when
        # a) the input option price is non-zero but so small that it is stored as a so-called denormalised floating
        #    point number which means that there aren't enough digits of significance in it to be able to converge to
        #    three quarters of machine accuracy (relative) in sigma
        # b) the input option price is so close to its theoretical maximum such that b_max-beta doesn't contain enough
        #    digits of significance to be able to converge to three quarters of machine accuracy (relative) in sigma
        # In those cases, returning whateve was achieved thus far is an acceptable course of action.
        return sigma / math.sqrt(expiry_time)

# Otherwise, this module contains a
# stand-alone volatility implication function. The stand-alone implementation uses the paper 'By Implication' by
# Peter Jaeckel (Wilmott, pages 60-66, November 2006).
implied_volatility_function = ImpliedVolatility.implied_volatility

class OptionType(enum.Enum):
    """
    Option type
    """
    PAYER = 1
    RECEIVER = -1

def get_annuity(forward, year_fraction, number_payments):
    """
    Cash settled annuity formula. See formula (1)
    :param forward: float
    :param year_fraction: float. Typically 1.0 for yearly, 0.5 semi-annual, etc
    :param number_payments: int
    :return: float
    """
    annuity = 0.0
    tmp = year_fraction
    for j in range(0, number_payments):
        tmp /= 1.0 + year_fraction * forward
        annuity += tmp
    return annuity

class CashAnnuityInfo:
    """
    Simple class to contain the information for the annuity formula: the year fraction and the number of payments
    """
    def __init__(self, year_fraction, number_payments):
        """
        Create a CashAnnuityInfo
        :param year_fraction: float, for example 0.5 for semi-annual, 1.0 for annual, etc.
        :param number_payments: int
        """
        self.year_fraction = year_fraction
        self.number_payments = number_payments

    def get_annuity(self, forward):
        """
        Calculate the cash-settled annuity
        :param forward: float
        :return: float
        """
        return get_annuity(forward=forward, year_fraction=self.year_fraction, number_payments=self.number_payments)

def get_implied_vol(premium, displacement, option_type, forward, expiry_time, strike, pvbp):
    """
    Get the implied volatility
    :param premium: float, the premium
    :param displacement: float. Typically > 0.0, e.g. 0.03
    :param option_type: OptionType
    :param forward: float
    :param expiry_time: float, time expressed as ACT/365
    :param strike: float
    :param pvbp: present value of a basis point
    :return: float
    """
    option_type_to_call_put = {OptionType.PAYER: 'call', OptionType.RECEIVER: 'put'}
    call_put = option_type_to_call_put[option_type]
    try:
        implied_vol = implied_volatility_function(  # Calculates implied Black vol consistent with given premium
            premium / pvbp,  # premium. The (undiscounted) value of the option
            forward + displacement,  # forward. The par strike of the associated forward contract
            strike + displacement,  # strike. The strike of the option
            expiry_time,  # T. The expiry time in years
            call_put,  # optionType. "Call" or "Put"
        )
        return implied_vol
    except Exception as _:
        return None

def kernel(x, f, *args):
    """
    The kernel function
    :param x: float. Gaussian deviate
    :param f: function to integrate
    :param args: list. Additional arguments for function f
    :return: float
    """
    return 1.0 / ((2.0 * math.pi) ** 0.5) * f(x, *args) * math.exp(-0.5 * x ** 2)

def integrate(f, *args, **kwargs):
    """
    Integrate the function f over the kernel
    :param f: function
    :param args: list. Additional arguments for function f
    :param kwargs: dict. Key word arguments
    :return: tuple where the first entry represents the resulting integral value
    """
    return scipy.integrate.quad(kernel, -np.inf, np.inf, (f, *args), **kwargs)

class DisplacedLogNormalModel:
    """
    Displaced Log-normal model
    """

    def __init__(self, model, displacement, vol):
        """
        Initiate a model
        :param model: Model
        :param displacement: float
        :param vol: float
        """
        self.model = model
        self.forward = model.forward
        self.displacement = displacement
        self.expiry_time = model.expiry_time
        self.vol = vol
        self.integrate_quad_options_dict = model.integrate_quad_options_dict
        self.strike = None  # :param strike: float. Not used in the base class, used in derived classes
        self.pvbp = None  # :param pvbp: float. Not used in the base class, used in derived classes

    def get_forward(self, z):
        """
        Get the diffused forward given a normal deviate z. See formulas (2) and (3).
        :param z: float
        :return: float
        """
        exp_arg = self.vol * math.sqrt(self.expiry_time) * z - 0.5 * self.vol * self.vol * self.expiry_time
        exp_arg = min(exp_arg, math.log(sys.float_info.max))
        exp = math.exp(exp_arg)
        forward = (self.forward + self.displacement) * exp - self.displacement
        return forward

    def get_implied_volatility(self, option_type, value):
        """
        Imply the vol
        :param option_type: OptionType
        :param value: float (premium)
        :return: float
        """
        implied_vol = get_implied_vol(
            premium=value,
            displacement=self.model.displacement,
            option_type=option_type,
            forward=self.forward,
            expiry_time=self.expiry_time,
            strike=self.strike,
            pvbp=self.pvbp,
        )
        return implied_vol

    def get_implied_vol(self, option_type):
        """
        Integrate the model and imply the vol
        :param option_type: OptionType
        :return: float
        """
        model_value = self.integrate(self.get_payoff, option_type)
        result = self.get_implied_volatility(option_type=option_type, value=model_value)
        return result

    def get_payoff(self, *args):
        """
        No payoff in the base class, used in derived classes
        :param args:
        :return:
        """
        raise NotImplementedError()

class PhysicalSwaptionModel(DisplacedLogNormalModel):
    """
    Physical Swaption model using displaced log-normal
    """

    def __init__(self, model, vol, strike):
        """
        Create a Physical swaption model
        :param model: Model
        :param vol: float
        :param strike: float
        """
        DisplacedLogNormalModel.__init__(
            self,
            model=model,
            displacement=model.displacement,
            vol=vol,
        )
        self.strike = strike
        self.pvbp = model.swap_settled_pvbp

    def get_payoff(self, x, option_type):
        """
        Get the diffused payoff given a Gaussian deviate x and a pay/receive code. See formula (6)
        :param x: float, Gaussian deviate
        :param option_type: OptionType
        :return: float
        """
        forward = self.get_forward(x)
        result = max(float(option_type.value) * (forward - self.strike), 0.0) * self.pvbp
        return result

class CashSettledSwaptionModel(DisplacedLogNormalModel):
    """
    Cash settled swaption model using displaced log-normal
    """

    def __init__(
            self,
            model,
            strike,
            vol_swap_settled,
            vol_cash_settled,
            displacement_cash_settled,
    ):
        """
        Create a Cash settled swaption model
        :param model: Model
        :param strike: float
        :param vol_swap_settled: float, volatility for swap settled (forecase vol)
        :param vol_cash_settled: float, cash-settled volatility
        :param displacement_cash_settled: float
        """
        self.model = model
        self.physical_swaption_model = PhysicalSwaptionModel(model, vol_swap_settled, strike)
        DisplacedLogNormalModel.__init__(
            self,
            model=model,
            displacement=displacement_cash_settled,
            vol=vol_cash_settled,
        )
        self.strike = strike
        self.pvbp = model.cash_pvbp

    def diffused_payoff(self, x, payoff_function):
        """
        Diffuse the variables and evaluate the numeraire relative payoff. See formula (7)
        :param x: float, Gaussian deviate
        :param payoff_function: a function which expects a diffused forward as input and returns the payoff
        :return: float
        """
        forward = self.physical_swaption_model.get_forward(x)
        forward_cash_settled = self.get_forward(x)
        annuity = self.model.cash_annuity_info.get_annuity(forward)
        numeraire = self.model.cash_annuity_info.get_annuity(forward_cash_settled)
        result = self.pvbp * payoff_function(forward) * annuity / numeraire
        return result

    def get_payoff(self, x, option_type):
        """
        Cash settled swaption payoff
        :param x: float, Gaussian deviate
        :param option_type: OptionType
        :return: float
        """
        def payoff_function(forward):
            return max(float(option_type.value) * (forward - self.strike), 0.0)
        return self.diffused_payoff(x, payoff_function)

    def get_unit_payoff(self, x):
        """
        Get the unit payoff
        :param x: float, Gaussian deviate
        :return: float
        """
        def payoff_function(_):
            return 1.0
        return self.diffused_payoff(x, payoff_function)

    def get_put_call_combo_value(self):
        """
        Get put-call combo value, long a payer, short a receiver
        :return: float
        """
        payer_value = self.integrate(self.get_payoff, OptionType.PAYER)
        receiver_value = self.integrate(self.get_payoff, OptionType.RECEIVER)
        put_call_combo_value = payer_value - receiver_value
        return put_call_combo_value

    def integrate(self, payoff_function, option_type=None):
        """
        Integrate the payoff times numeraire.
        :param payoff_function: a function, typically get_payoff or get_unit_payoff
        :param option_type: OptionType or None, None to be exclusively used for get_unit_payoff
        :return: float
        """
        assert option_type is not None or payoff_function.__name__ == 'get_unit_payoff'
        args = [option_type] if option_type is not None else []
        result = integrate(payoff_function, *args, **self.model.integrate_quad_options_dict)
        return result[0]

class ParityCalibrator:
    """
    Parity calibrator
    """

    def __init__(self, model, option_type, strike, target_vol):
        """
        Create parity calibrator. Calibrate two variables to a cash settled swaption and the parity condition
        :param model: Model
        :param option_type: OptionType
        :param strike: float
        :param target_vol: float
        """
        self.model = model
        self.option_type = option_type
        self.strike = strike
        self.target_vol = target_vol

    def get_parity_error(self, swap_settled_and_cash_settled_vol):
        """
        Get parity error. This is pair of (1) vol error versus a market level and
        (2) the difference between the model's convexity adjusted cash settled pvbp values
        :param swap_settled_and_cash_settled_vol: np.array with 2 entries, swap-settled & cash-settled vol
        :return: float
        """
        swap_settled_vol, cash_settled_vol = swap_settled_and_cash_settled_vol
        model = self.model.get_cash_model(
            cash_settled_vol=cash_settled_vol,
            cash_settled_displacement=self.model.cash_settled_displacement,
            vol=swap_settled_vol,
            strike=self.strike,
        )
        cash_settled_error = model.get_implied_vol(self.option_type) - self.target_vol
        convexity_adjusted_cash_settled_pvbp = \
            (model.get_put_call_combo_value() - self.model.put_call_combo_atm_value) / \
            (self.model.forward - self.strike)  # uses formula (10)
        pvbp_error = convexity_adjusted_cash_settled_pvbp - self.model.convexity_adjusted_cash_settled_pvbp
        return np.array([cash_settled_error, pvbp_error])

class Model:
    """
    The cash settled swaptions model.
    """

    def __init__(
            self,
            forward,
            displacement,
            expiry_time,
            swap_atm_vol,
            cash_pay_atm_vol,
            cash_receive_atm_vol,
            cash_annuity_info,
            cash_pvbp,
            swap_settled_pvbp,
            tol=None,
            integrate_quad_options_dict=None,
            **kwargs
    ):
        """
        Create a model and calibrate it.
        :param forward: float
        :param displacement: float
        :param expiry_time: float
        :param swap_atm_vol: float
        :param cash_pay_atm_vol: float
        :param cash_receive_atm_vol: float
        :param cash_annuity_info: CashAnnuityInfo
        :param cash_pvbp: float
        :param swap_settled_pvbp: float
        :param tol: tolerance for the solver. See documentation for scipy.optimize.root
        :param integrate_quad_options_dict: dict or None (default). Adds additional options to scipy.integrate.quad
        :param kwargs: dict, key word arguments
        """
        self.forward = forward
        self.displacement = displacement  # Calibration step #1: Copy swap-settled displacement from the market data.
        self.expiry_time = expiry_time
        self.swap_atm_vol = swap_atm_vol  # Calibration step #2: Copy swap settled ATM volatility from the market data.
        self.cash_pay_atm_vol = cash_pay_atm_vol
        self.cash_receive_atm_vol = cash_receive_atm_vol
        self.cash_annuity_info = cash_annuity_info
        self.cash_pvbp = cash_pvbp
        self.swap_settled_pvbp = swap_settled_pvbp
        self.tol = tol
        self.integrate_quad_options_dict = {} if integrate_quad_options_dict is None else integrate_quad_options_dict
        # Calibration step #3: Calibrate ATM cash-settled vol & displacement from ATM zero-width collar value.
        self.cash_settled_vol_atm, self.cash_settled_displacement = self.calibrate()
        self.atm_model = self.get_cash_model(self.cash_settled_vol_atm, self.cash_settled_displacement)
        # Calibration step #4: Calculate the cash settled convexity adjusted pvbp according to formula (11).
        convex_adj_cash_settle_pvbp_res = self.atm_model.integrate(self.atm_model.get_unit_payoff)
        self.convexity_adjusted_cash_settled_pvbp = convex_adj_cash_settle_pvbp_res
        self.put_call_combo_atm_value = self.atm_model.get_put_call_combo_value()

    def get_cash_model(self, cash_settled_vol, cash_settled_displacement=None, vol=None, strike=None):
        """
        Get cash model
        :param cash_settled_vol: float
        :param cash_settled_displacement: float or None (default, uses ATM cash-settled displacement in this case)
        :param vol: float. swap settled vol. If left None, use ATM swap-settled vol
        :param strike: float. If None, use forward.
        :return: CashSettledSwaptionModel
        """
        if cash_settled_displacement is None:
            cash_settled_displacement = self.cash_settled_displacement
        if vol is None:
            vol = self.swap_atm_vol
        if strike is None:
            strike = self.forward
        return CashSettledSwaptionModel(
            model=self,
            strike=strike,
            vol_swap_settled=vol,
            vol_cash_settled=cash_settled_vol,
            displacement_cash_settled=cash_settled_displacement,
        )

    def get_zero_width_collar_error(self, cash_settled_vol_and_displacement):
        """
        Get zero-width collar error: pair of difference (model versus target) between payer & receiver cash settled vols
        :param cash_settled_vol_and_displacement: numpy array with two entries, the cash-settled vol & displacement
        :return: numpy array
        """
        target_vols = [(OptionType.PAYER, self.cash_pay_atm_vol), (OptionType.RECEIVER, self.cash_receive_atm_vol)]
        cash_settled_vol, cash_settled_displacement = cash_settled_vol_and_displacement
        vol_diffs = []
        for option_type, target_vol in target_vols:
            model_cash = self.get_cash_model(cash_settled_vol, cash_settled_displacement)
            implied_vol_cash = model_cash.get_implied_vol(option_type)
            vol_diffs.append(implied_vol_cash - target_vol)
        return np.array(vol_diffs)

    def calibrate(self):
        """
        Solve for cash-settled vol & displacement such that the payer & receiver ATM cash settled vols are matched
        :return: pair (tuple) of cash-settled ATM vol and cash-settled displacement
        """
        calibration_result = scipy.optimize.root(
            self.get_zero_width_collar_error,
            np.array([self.swap_atm_vol, self.displacement]),  # initial guess
            tol=self.tol,
        )
        cash_settled_vol_atm, cash_settled_displacement = calibration_result.x
        return cash_settled_vol_atm, cash_settled_displacement

    def calibrate_cash_model(self, strike, vol_cash, option_type):
        """
        Calibrate a cash model at a given strike.
        This function represents calibration step #5:
        Calibrate sigma(K) and sigma*(K) to cash settled swaption vol & put-call parity relation (10) for other strikes.
        :param strike: float
        :param vol_cash: float
        :param option_type: OptionType
        :return: CashSettledSwaptionModel
        """
        if strike == self.forward:
            return self.atm_model
        parity_calibrator = ParityCalibrator(
            model=self,
            option_type=option_type,
            strike=strike,
            target_vol=vol_cash,
        )
        calib_result = scipy.optimize.root(
            parity_calibrator.get_parity_error,
            np.array([vol_cash, self.cash_settled_vol_atm]),  # initial guess
            tol=self.tol,
        )
        vol_swap_settled, vol_cash_settled = calib_result.x
        cash_model = self.get_cash_model(
            cash_settled_vol=vol_cash_settled,
            vol=vol_swap_settled,
            strike=strike,
        )
        return cash_model

FAVOURITE_STRIKE_OFFSETS = [0.0,
    0.000625, 0.00125, 0.0025, 0.0050, 0.0075, 0.01, 0.015, 0.02, 0.03,
    -0.000625, -0.00125, -0.0025, -0.0050, -0.0075, -0.01, -0.015, -0.02, -0.03
]

class SettleType(enum.Enum):
    """
    Settlement type.
    """
    PAY = 1  # cash-settled payer
    RECV = -1  # cash-settled receiver
    PHYSICAL = 0  # swap-settled

def plot_vols(strikes, model, market_cash_payer, market_cash_receiver):
    """
    Plot the model vols
    :param model: Model
    :param market_cash_payer: list of float or None, containing the market cash-settled payer vols
    :param market_cash_receiver: list of float or None, containing the market cash-settled receiver vols
    :return:
    """
    plt.style.use('ggplot')
    model_vols = []
    model_cash_vols = {option_type: [] for option_type in OptionType}
    market_vols = {SettleType.PAY: market_cash_payer, SettleType.RECV: market_cash_receiver}
    for count, strike in enumerate(strikes):

        is_payer = strike > model.forward
        option_type = OptionType.PAYER if is_payer else OptionType.RECEIVER
        settle_type = SettleType.PAY if is_payer else SettleType.RECV

        vol_cash_to_calibrate_to = market_vols[settle_type][count]
        model_cash = model.calibrate_cash_model(strike, vol_cash_to_calibrate_to, option_type)

        implied_vol = model_cash.physical_swaption_model.vol
        model_vols.append(implied_vol)

        for my_option_type in OptionType:
            implied_vol_cash = model_cash.get_implied_vol(my_option_type)
            model_cash_vols[my_option_type].append(implied_vol_cash)

    plt.plot(strikes, model_vols, label='model vols ' + SettleType.PHYSICAL.name)
    for option_type, cash_vols_model in model_cash_vols.items():
        plt.plot(strikes, cash_vols_model, label='model cash {type} vols'.format(type=option_type.name.lower()))

    for settle_type in market_vols.keys():
        plt.plot(strikes, market_vols[settle_type], label='market vols ' + settle_type.name)
    plt.legend()
    plt.title('Swaption volatilities')
    plt.show()

def get_strikes(forward, displacement):
    """
    Get some example set of strikes based on the FAVOURITE_STRIKE_OFFSETS constant
    :param forward: float
    :param displacement: float
    :return: list of float
    """
    strikes = sorted([
        forward + strike_offset
        for strike_offset in FAVOURITE_STRIKE_OFFSETS
        if forward + strike_offset > -displacement
    ])
    return strikes

def main():
    """
    The main function which gets called when this module is ran as a script.
    This main function will set some example values and then it will run the model on the example.
    Subsequently the results are plotted in a figure.
    :return:
    """
    forward = 0.01100343969
    displacement = 0.007  # displacement aka shift
    expiry_time = 30.019178082191782
    vol_physical_measure_atm = 0.2130896096958366  # physical aka swap-settled
    atm_pay_vol = 0.22212766305824355
    atm_rec_vol = 0.1994018397861978
    cash_annuity_info = CashAnnuityInfo(year_fraction=1.0, number_payments=30)
    cash_pvbp = 17.81653454617611
    physical_pvbp = 17.994421329395927
    market_cash_payer = [
        None, None, None, None, None, None, None, 0.22212766305824355, 0.2191166853454672, 0.21625128088540194,
        0.21092449139042832, 0.2016940138299131, 0.19406310229906248, 0.18774926787352228, 0.17821763702783086,
        0.1717397903524143, 0.16444139694677906,
    ]
    market_cash_receiver = [
        0.3779182774820147, 0.2759240484072475, 0.24887266885973108, 0.22840424850890914,
        0.21231444837760344, 0.20551432485314422, 0.20237753128037136, 0.1994018397861978,
        None, None, None, None, None, None, None, None, None,
    ]
    model = Model(
        forward=forward,
        displacement=displacement,
        expiry_time=expiry_time,
        swap_atm_vol=vol_physical_measure_atm,
        cash_pay_atm_vol=atm_pay_vol,
        cash_receive_atm_vol=atm_rec_vol,
        cash_annuity_info=cash_annuity_info,
        cash_pvbp=cash_pvbp,
        swap_settled_pvbp=physical_pvbp,
    )
    strikes = get_strikes(forward=forward, displacement=displacement)
    plot_vols(strikes, model, market_cash_payer, market_cash_receiver)

if __name__ == '__main__':
    main()


#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Thu Dec 28 09:49:58 2017

@author: tgadfort
"""

from sklearn.preprocessing import Imputer

from logger import info, error
from colInfo import getNcols

def replaceTargetNA(pddata, colname, strategy = 'zero'):
    info("Replace NA values for target ["+colname+"]", ind=4)
    if strategy == 'zero':
        pddata[colname].fillna(0, inplace=True)
    if strategy == 'mean':
        pddata[colname].fillna(pddata[colname].mean(), inplace=True)

def replaceFeatureNA(pddata, targetcol, strategy):
    info("Replace NA values for features", ind=4)

    nadata = pddata.loc[:, pddata.isnull().any()]
    info("Found "+getNcols(nadata, asStr=True)+" columns with an NA", ind=6)

    for dtype,dstrategy in strategy.iteritems():
        if dstrategy == None:
            continue
        columns = list(nadata.select_dtypes(include=[dtype]).columns)
        info("Replacing NA values for "+str(len(columns))+" "+dtype+" features", ind=6)
        if dstrategy == 'zero':
            for colname in columns:
                pddata[colname].fillna(0, inplace=True)
        if dstrategy == 'mean':
            for colname in columns:
                pddata[colname].fillna(pddata[colname].mean(), inplace=True)
        if dstrategy == 'dummy':
            for colname in columns:
                pddata[colname].fillna('dummy', inplace=True)
        if dstrategy == 'targetmean':
            for colname in columns:
                pddata.groupby(colname)[targetcol].transform('mean')

    nadata = pddata.loc[:, pddata.isnull().any()]
    ncols  = getNcols(nadata)
    if ncols > 0:
        info("Replaced all NAs", ind=4)
    else:
        info("There are still "+str(ncols)+" columns with an NA", ind=4)

def replaceNA(pddata, targetcol = None, impute = True, strategy='mean'):
    info("Replacing NA values in dataset.",ind=2)

    if impute:
        # The imputation strategy.
        # If “mean”, then replace missing values using the mean along the axis.
        # If “median”, then replace missing values using the median along the axis.
        # If “most_frequent”, then replace missing using the most frequent value along the axis.

        imputer = Imputer(strategy=strategy)
        if targetcol:
            pddata = imputer.fit_transform(X=pddata, y=pddata[targetcol])
        else:
            pddata = imputer.fit_transform(X=pddata)

    else:
        if strategy == 'zero':
            pddata['amount'].fillna(0, inplace=True)
        #if strategy == 'mean':
        #    pddata['amount'].fillna(pd, inplace=True)

    ## Test results
    numNA = sum(pddata.isnull().sum())
    if numNA != 0:
        error("There are still NA values in dataset after Imputer.", ind=0, finish=True)


from collections import deque

n = int(input())
students = deque(map(int, input().split()))
stack = []
target = 1

while students:
    if students[0] == target:
        students.popleft()
        target += 1
    else:
        stack.append(students.popleft())
    while stack and stack[-1] == target:
        stack.pop()
        target += 1

print("Nice" if not stack else "Sad")

12789_도키도키 간식드리미.py


# Implements Dataset class
import math
import os
from random import shuffle

class Dataset:
 """ Class Dataset """
 def __init__(self, fileName = None):
  self.data = [];
  self.variablesQuantity = 0;
  self.variablesCardinality = "";
  self.name = "";
  self.pathToFiles = "";
  if(fileName is not None):
   self.ParseFile(fileName)

 def ParseFile(self, fileName):
  self.name = os.path.splitext(os.path.basename(fileName))[0];
  with open(fileName, "r") as datasetFile:
   self.variablesQuantity = int(datasetFile.readline());
   self.variablesCardinality = [int(number) for number in datasetFile.readline().strip().split()];
   datasetFile.readline();
   self.data = [[int(number) for number in line.strip().split()] for line in datasetFile];
   self.data2 = [line for line in self.data if sum(line) != 0]

 def CloneWithoutData(self):
  cloneDataset = Dataset();
  cloneDataset.name = self.name;
  cloneDataset.variablesQuantity = self.variablesQuantity;
  cloneDataset.variablesCardinality = self.variablesCardinality;
  return cloneDataset;

 def KFoldGenerator(self, K, randomise = True):
  data = self.data;
  if randomise:
   data = list(data);
   shuffle(data);

  training = self.CloneWithoutData();
  validation = self.CloneWithoutData();
  dataPerFold = math.ceil(len(data) / K);

  for k in xrange(K):
   training.data = [line for i, line in enumerate(data) if math.floor(i/dataPerFold) != k]
   training.name = self.name + "." + str(k) + ".training";
   validation.data = [line for i, line in enumerate(data) if math.floor(i/dataPerFold) == k]
   validation.name = self.name + "." + str(k) + ".validation";
   yield training, validation;

 def WriteToFile(self, path = "./"):
  datasetFileName = path + "/" + self.name + ".data";
  with open(datasetFileName, "w") as datasetFile:
   datasetFile.write(str(self.variablesQuantity) + "\n");
   datasetFile.write(" ".join(map(str, self.variablesCardinality)) + "\n");
   datasetFile.write(str(len(self.data)) + "\n");
   datasetFile.write("\n".join(" ".join(map(str, dataLine)) for dataLine in self.data));
   datasetFile.write("\n");
  return datasetFileName;


'''
Resource quota validation for AWS accounts
'''

from libs_aws.aws_configuration_helper import AWSConfigurationHelper
from libs_aws.aws_generic_helper import AWSGenericHelper
from libs_aws.iam_helper import IAMHelper

import os
import boto3
import argparse

from pprint import pprint

import sys

def _parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('--file', '-f', required=True, help='Name of the file containing the permissions to be tested.')
    parser.add_argument('--credentials_from_file', '-c', required=False, action='store_true', help='If set, the credentials will be taken from file.')
    parser.add_argument('--profile', '-p', required=False, default='', help='Profile to be used from the AWS credentials file.')
    return parser.parse_args()

def get_terraform_configuration():
    print("\nCluster configuration")
    print("=====================")

    tf_var_file = os.path.dirname(os.path.abspath(__file__)) + '/../variables.tf'
    print("  The cluster configuration will be derived from terraform " +
          f"configuration: '{tf_var_file}'\n")

    tf_config = {}

    tf_config_json = AWSGenericHelper.get_terraform_config_json(tf_var_file)

    tf_config['region'] = tf_config_json['variable']['region']['default']
    pprint(tf_config)

    return tf_config

def main():
    args = _parse_args()
    actions_file = args.file
    cred = args.credentials_from_file
    profile = args.profile

    # Get resource related values from terraform config variables file
    tf_config = get_terraform_configuration()

    # Get the AWS configuration (credentials, region)
    aws_config = AWSConfigurationHelper.get_config(tf_config['region'], cred, profile)

    iam_helper = IAMHelper(aws_config)

    # Show user data
    user_name = iam_helper.get_user_name()
    print("Going to check the permissions for user: " + user_name)

    groups = iam_helper.get_groups_from_username(user_name)
    print("The user " + user_name + " is member of the groups:")
    print(groups)

    policies = iam_helper.get_user_and_group_policies(user_name, groups)
    print("The following policies are applied to the user " + user_name + ":")
    print(policies)

    # Test the permissions
    resource_actions = []
    try:
        with open(actions_file) as f:
            resource_actions = f.read().splitlines()
    except IOError as e:
        print(e)
        print("Check specified actions file path name and try again.")
        sys.exit(1)

    resource_actions = list(filter(None, resource_actions))
    resource_actions = list(filter(lambda name : name.strip(), resource_actions))
    resource_actions = list(filter(lambda x : "#" not in x, resource_actions))
    print("Going to verify the permissions for the following resource actions:")
    print(*resource_actions, sep='\n')
    print("")

    blocked_actions = iam_helper.blocked(user_name, actions=resource_actions)

    if blocked_actions != []:
        print(" * The user " + user_name + " has unsufficient permissions to exexute the following actions:")
        print(*blocked_actions, sep='\n')
        sys.exit(1)
    print("")

if __name__ == '__main__':
    sys.exit(main())


# https://www.acmicpc.net/problem/1747 : 소수&팰린드롬 (python3)
# 2023-06-19 11:49
from sys import stdin

# input runtime 최적화
input = stdin.readline

n = int(input())
# 1은 소수가 아니므로 2부터 시작
if n == 1:
    n = 2
while 1:
    # 소수인지 확인
    isPrime = True
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            isPrime = False
            break
    # 소수이면서 팰린드롬인지 확인
    if isPrime:
        if str(n) == str(n)[::-1]:
            print(n)
            break
    n += 1


from i2_slack_modules.common import ts_to_date, parse_relative_date, my_own_function_name
from i2_slack_modules.slack_helper import *
from i2_slack_modules.icinga_connection import *
from datetime import datetime

# noinspection PyUnusedLocal
def chat_with_user(
        config=None,
        bot_commands=None,
        slack_message=None,
        slack_user=None,
        *args, **kwargs):
    """
    Have a conversation with the user about the action the user wants to perform

    Parameters
    ----------
    config : dict
        dictionary with items parsed from config file
    bot_commands: BotCommands
        class with bot commands to avoid circular imports
    slack_message : string
        slack message to parse
    slack_user : SlackUser
        SlackUser object
    args, kwargs: None
        used to hold additional args which are just ignored

    Returns
    -------
    BotResponse: questions about the action, confirmations or errors
    """

    # set properties for available action commands
    action_commands = {
        "acknowledge": {
            "filter_end_marker": "until",
            "need_start_date": False,
            "need_end_date": True,
            "need_comment": True,
            "filter_question": "What do you want acknowledge?"
        },
        "downtime": {
            "filter_end_marker": "from",
            "need_start_date": True,
            "need_end_date": True,
            "need_comment": True,
            "filter_question": "What do you want to set a downtime for?"
        },
        "comment": {
            "filter_end_marker": "with",
            "need_start_date": False,
            "need_end_date": False,
            "need_comment": True,
            "filter_question": "What do you want to add a comment to?"
        },
        "reschedule": {
            "filter_end_marker": None,
            "need_start_date": False,
            "need_end_date": False,
            "need_comment": False,
            "filter_question": "What do you want to reschedule?"
        },
        "send notification": {
            "filter_end_marker": "with",
            "need_start_date": False,
            "need_end_date": False,
            "need_comment": True,
            "filter_question": "What do you want to send notifications for?"
        },
        "delay notification": {
            "filter_end_marker": "until",
            "need_start_date": False,
            "need_end_date": True,
            "need_comment": False,
            "filter_question": "What do you want to delay notifications for?"
        },
        "remove": {
            "filter_end_marker": None,
            "need_start_date": False,
            "need_end_date": False,
            "need_comment": False,
            "has_sub_commands": True,
            "filter_question": "For which object do you want to remove {}s?"
        }
    }

    # set defaults control vars
    filter_end_marker = None
    need_start_date = False
    need_end_date = False
    need_comment = False
    has_sub_commands = False
    filter_question = None

    if slack_message is None:
        logging.error("Parameter '%s' missing while calling function '%s'" % ("slack_message", my_own_function_name()))

    if slack_user is None:
        logging.error("Parameter '%s' missing while calling function '%s'" % ("slack_user_id", my_own_function_name()))

    if slack_message is None or slack_user is None:
        return slack_error_response()

    # New conversation
    if slack_user.conversation is None:
        slack_user.start_conversation()

    conversation = slack_user.conversation

    # check or command
    if conversation.command is None:
        logging.debug("Command not set, parsing: %s" % slack_message)
        conversation.command = bot_commands.get_command_called(slack_message)

        if conversation.command.name not in action_commands.keys():
            conversation.command = None
            return None

        logging.debug("Command parsed: %s" % conversation.command.name)

        slack_message = conversation.command.strip_command(slack_message)

    if conversation.command is not None:

        command_data = action_commands.get(conversation.command.name)

        if command_data is not None:
            # update control vars
            filter_end_marker = command_data.get("filter_end_marker", None)
            need_start_date = command_data.get("need_start_date", False)
            need_end_date = command_data.get("need_end_date", False)
            need_comment = command_data.get("need_comment", False)
            has_sub_commands = command_data.get("has_sub_commands", False)
            filter_question = command_data.get("filter_question", None)

    if has_sub_commands is True and conversation.sub_command is None:

        if len(slack_message) != 0:
            # we got a filter
            logging.debug("Sub command not set, parsing: %s" % slack_message)

            if conversation.command.has_sub_commands():
                conversation.sub_command = \
                    conversation.command.sub_commands.get_command_called(slack_message)

                if conversation.sub_command:
                    slack_message = conversation.sub_command.strip_command(slack_message)
                    logging.debug("Sub command parsed: %s" % conversation.sub_command.name)

    # check for filter
    if conversation.filter is None:
        if len(quoted_split(string_to_split=slack_message)) != 0:
            # we got a filter
            logging.debug("Filter not set, parsing: %s" % slack_message)

            split_slack_message = quoted_split(string_to_split=slack_message, preserve_quotations=True)

            #  everything left of the index string will be parsed as filter
            if filter_end_marker is not None and filter_end_marker in [s.lower() for s in split_slack_message]:

                index = [s.lower() for s in split_slack_message].index(filter_end_marker)

                # get end of filter list
                filter_list = split_slack_message[0:index]

                # strip index string from slack message on comments
                if filter_end_marker == "with":
                    index += 1

                # strip the filter from the supplied string
                slack_message = " ".join(split_slack_message[index:])

            else:
                # index string not found
                # assuming the whole message is meant to be a filter
                filter_list = split_slack_message
                slack_message = ""

            filter_list = slack_user.get_last_user_filter_if_requested(filter_list)

            logging.debug("Filter parsed: %s" % filter_list)

            if len(filter_list) > 0:
                conversation.filter = filter_list
                slack_user.add_last_filter(conversation.filter)

    # split slack_message into an array (chat message array)
    cma = quoted_split(string_to_split=slack_message, preserve_quotations=True)

    # try to find objects based on filter
    if conversation.filter and conversation.filter_result is None:

        logging.debug("Filter result list empty. Query Icinga for objects.")

        host_filter = list()
        service_filter = list()
        if conversation.command.name == "acknowledge":
            host_filter = ["host.state != 0"]
            service_filter = ["service.state != 0"]

        # query hosts and services
        if len(conversation.filter) == 1:

            object_type = "Host"
            if conversation.sub_command is not None:
                if conversation.sub_command.name == "downtime":
                    object_type = "HostDowntime"
                else:
                    object_type = "HostComment"

            i2_result = get_i2_object(config, object_type, host_filter, conversation.filter)

            if i2_result.error is None and len(i2_result.data) == 0:
                object_type = "Service"
                if conversation.sub_command is not None:
                    if conversation.sub_command.name == "downtime":
                        object_type = "ServiceDowntime"
                    else:
                        object_type = "ServiceComment"

                i2_result = get_i2_object(config, object_type, service_filter, conversation.filter)

        # just query services
        else:
            object_type = "Service"
            if conversation.sub_command is not None:
                if conversation.sub_command.name == "downtime":
                    object_type = "ServiceDowntime"
                else:
                    object_type = "ServiceComment"

            i2_result = get_i2_object(config, object_type, service_filter, conversation.filter)

        # encountered Icinga request issue
        if i2_result.error:
            logging.debug("No icinga objects found for filter: %s" % conversation.filter)

            return slack_error_response(
                header="Icinga request error while trying to find matching hosts/services",
                fallback_text="Icinga Error",
                error_message=i2_result.error
            )

        # we can set a downtime for all objects no matter their state
        if conversation.sub_command is not None:

            if conversation.sub_command.name == "downtime" and len(i2_result.data) > 0:
                conversation.filter_result = i2_result.data
            else:
                # filter results based on sub command name
                ack_filter_result = list()
                for result in i2_result.data:
                    if conversation.sub_command.name == "comment" and result.get("entry_type") == 1:
                        ack_filter_result.append(result)
                    if conversation.sub_command.name == "acknowledgement" and result.get("entry_type") == 4:
                        ack_filter_result.append(result)

                if len(ack_filter_result) > 0:
                    conversation.filter_result = ack_filter_result

        elif conversation.command.name == "acknowledge" and len(i2_result.data) > 0:

            # only objects which are not acknowledged can be acknowledged
            ack_filter_result = list()
            for result in i2_result.data:
                # only add results which are not acknowledged
                if result.get("acknowledgement") == 0:
                    ack_filter_result.append(result)

            if len(ack_filter_result) > 0:
                conversation.filter_result = ack_filter_result

        else:
            conversation.filter_result = i2_result.data

        # save current conversation state if filter returned any objects
        if conversation.filter_result and len(conversation.filter_result) > 0:
            sub_command_name = ""
            if conversation.sub_command is not None:
                sub_command_name = f" {conversation.sub_command.name}"

            logging.debug("Found %d objects for command %s%s" %
                          (len(conversation.filter_result), conversation.command.name, sub_command_name))

            conversation.object_type = object_type
        else:
            conversation.filter_result = None

    # parse start time information for downtime
    if need_start_date is True and conversation.start_date is None and conversation.filter_result is not None:

        if len(cma) != 0:

            logging.debug("Start date not set, parsing: %s" % " ".join(cma))

            date_string_parse = " ".join(cma)

            cma_lower = [s.lower() for s in cma]

            from_index = None
            until_index = None
            if "from" in cma_lower:
                from_index = cma_lower.index("from")

                if "until" in cma_lower:
                    until_index = cma_lower.index("until")

            if from_index is not None and len(cma) > from_index + 1:
                cma = cma[from_index + 1:]

                if until_index is not None:
                    until_index -= 1
                    date_string_parse = " ".join(cma[0:until_index])
                    cma = cma[until_index:]

            start_date_data = parse_relative_date(date_string_parse)

            if start_date_data:

                logging.debug("Start date successfully parsed")

                # get timestamp from returned datetime object
                if start_date_data.get("dt"):
                    conversation.start_date = start_date_data.get("dt").timestamp()

                if len(cma) >= 1 and cma[0].lower() != "until":
                    cma = date_string_parse[start_date_data.get("mend"):].strip().split(" ")
            else:
                conversation.start_date_parsing_failed = date_string_parse

    # parse end time information
    if need_end_date is True and conversation.end_date is None and conversation.filter_result is not None:

        if len(cma) != 0:

            logging.debug("End date not set, parsing: %s" % " ".join(cma))

            cma_lower = [s.lower() for s in cma]

            until_index = None
            if "until" in cma_lower:
                until_index = cma_lower.index("until")

            if until_index is not None and len(cma) > until_index + 1:
                cma = cma[until_index + 1:]

            if len(cma) >= 1 and cma[0].lower() in ["never", "infinite"]:
                # add rest of message as description
                conversation.end_date = -1
                del cma[0]

            else:
                string_parse = " ".join(cma)
                end_date_data = parse_relative_date(string_parse)

                if end_date_data:

                    # get timestamp from returned datetime object
                    if end_date_data.get("dt"):
                        conversation.end_date = end_date_data.get("dt").timestamp()

                    # add rest of string back to cma
                    cma = string_parse[end_date_data.get("mend"):].strip().split(" ")
                else:
                    conversation.end_date_parsing_failed = string_parse

    if need_comment is True and conversation.description is None and conversation.filter_result is not None:

        if len(cma) != 0 and len("".join(cma).strip()) != 0:
            logging.debug("Description not set, parsing: %s" % " ".join(cma))

            conversation.description = " ".join(cma)
            cma = list()

    # ask for sub command
    if has_sub_commands is True and conversation.sub_command is None:

        logging.debug("Sub command not set, asking for it")

        response_text = \
            "%sSorry, I wasn't able to parse your sub command. Check `help %s` to get available sub commands" % \
            (conversation.get_path(), conversation.command.name)

        return BotResponse(text=response_text)

    # ask for missing info
    if conversation.filter is None:

        logging.debug("Filter not set, asking for it")

        if has_sub_commands is True:
            filter_question = filter_question.format(conversation.sub_command.name)

        filter_question = "%s%s" % (conversation.get_path(), filter_question)

        return BotResponse(text=filter_question)

    # no objects found based on filter
    if conversation.filter_result is None:
        problematic = ""

        logging.debug("Icinga2 object request returned empty, asking for a different filter")

        if conversation.command.name == "acknowledge":
            problematic = " problematic"

        object_text = "hosts or services"
        if conversation.sub_command is not None:
            object_text = conversation.sub_command.name

        response_text = "%sSorry, I was not able to find any%s %s for your search '%s'. Try again." \
                        % (conversation.get_path(), problematic, object_text, " ".join(conversation.filter))

        conversation.filter = None
        return BotResponse(text=response_text)

    # ask for not parsed start time
    if need_start_date is True and conversation.start_date is None:

        if not conversation.start_date_parsing_failed:
            logging.debug("Start date not set, asking for it")
            response_text = "When should the downtime start?"
        else:
            logging.debug("Failed to parse start date, asking again for it")
            response_text = "Sorry, I was not able to understand the start date '%s'. Try again please." \
                            % conversation.start_date_parsing_failed

        response_text = "%s%s" % (conversation.get_path(), response_text)

        return BotResponse(text=response_text)

    # ask for not parsed end date
    if need_end_date is True and conversation.end_date is None:

        if not conversation.end_date_parsing_failed:

            logging.debug("End date not set, asking for it")

            if conversation.command.name == "acknowledge":
                response_text = "When should the acknowledgement expire? Or never?"
            else:
                response_text = "When should the downtime end?"
        else:
            logging.debug("Failed to parse end date, asking again for it")
            response_text = "Sorry, I was not able to understand the end date '%s'. Try again please." \
                            % conversation.end_date_parsing_failed

        response_text = "%s%s" % (conversation.get_path(), response_text)

        return BotResponse(text=response_text)

    if conversation.end_date and conversation.end_date != -1 and \
            conversation.end_date - 60 < datetime.now().timestamp():
        logging.debug("End date is already in the past. Ask user again for end date")

        response_text = "Sorry, end date '%s' lies (almost) in the past. Please define a valid end/expire date." % \
                        ts_to_date(conversation.end_date)

        response_text = "%s%s" % (conversation.get_path(), response_text)

        conversation.end_date = None

        return BotResponse(text=response_text)

    if need_start_date is True and conversation.start_date > conversation.end_date:

        logging.debug("Start date is after end date for downtime. Ask user again for start date.")

        response_text = "Sorry, start date '%s' can't be after and date '%s'. When should the downtime start?" % \
                        (ts_to_date(conversation.start_date), ts_to_date(conversation.end_date))

        response_text = "%s%s" % (conversation.get_path(), response_text)

        conversation.start_date = None

        return BotResponse(text=response_text)

    if need_comment is True and conversation.description is None:

        logging.debug("Description not set, asking for it")

        response_text = "%sPlease add a comment." % conversation.get_path()

        return BotResponse(text=response_text)

    # now we seem to have all information and ask user if that's what the user wants
    if not conversation.confirmed:

        if conversation.confirmation_sent:
            if cma[0].startswith("y") or cma[0].startswith("Y"):
                conversation.confirmed = True
            elif cma[0].startswith("n") or cma[0].startswith("N"):
                conversation.canceled = True
            else:
                # see if user tried to filter the selection (i.e.: 1,2)
                if conversation.sub_command is not None:
                    selection_list = [x.strip() for x in " ".join(cma).split(",")]
                    if len(selection_list) > 0:
                        objects_to_keep = list()
                        for selection in selection_list:
                            try:
                                objects_to_keep.append(conversation.filter_result[int(selection) - 1])
                            except Exception:
                                pass

                        if len(objects_to_keep) > 0:
                            conversation.filter_result = objects_to_keep

                conversation.confirmation_sent = False

        if not conversation.confirmation_sent:

            # get object type
            if conversation.command.name == "acknowledge":
                command = "Acknowledgement"
            else:
                command = conversation.command.name.capitalize()

            confirmation_type = conversation.object_type
            if conversation.sub_command is not None:
                confirmation_type = conversation.sub_command.name

            confirmation = {
                "Command": command,
                "Type": confirmation_type
            }
            if need_start_date is True:
                confirmation["Start"] = ts_to_date(conversation.start_date)
                confirmation["End"] = ts_to_date(conversation.end_date)

            elif conversation.command.name == "acknowledge":
                confirmation["Expire"] = "Never" if conversation.end_date == -1 else ts_to_date(
                    conversation.end_date)

            elif conversation.command.name == "delay notification":
                confirmation["Delayed until"] = "Never" if conversation.end_date == -1 else ts_to_date(
                    conversation.end_date)

            if need_comment is True:
                confirmation["Comment"] = conversation.description

            confirmation["Objects"] = ""

            response = BotResponse(text="Confirm your action")

            confirmation_fields = list()
            for title, value in confirmation.items():
                confirmation_fields.append(">*%s*: %s" % (title, value))

            object_num = 0
            for i2_object in conversation.filter_result[0:10]:
                object_num += 1
                host_name = service_name = comment_text = author = None
                bullet_text = "•"

                if conversation.object_type == "Service":
                    host_name = i2_object.get("host_name")
                    service_name = i2_object.get("name")

                elif "Comment" in conversation.object_type or "Downtime" in conversation.object_type:

                    bullet_text = f"{object_num}."
                    host_name = i2_object.get("host_name")
                    if len(i2_object.get("service_name", "")) > 0:
                        service_name = i2_object.get("service_name")

                    if i2_object.get("comment") is not None:
                        comment_text = i2_object.get("comment")
                    else:
                        comment_text = i2_object.get("text")

                    author = i2_object.get("author")

                else:  # host
                    host_name = i2_object.get("name")

                host_url = get_web2_slack_url(host_name, web2_url=config["icinga.web2_url"])
                service_url = get_web2_slack_url(host_name, service_name, web2_url=config["icinga.web2_url"])

                if service_name is not None:
                    object_text = "%s | %s" % (host_url, service_url)
                else:
                    object_text = host_url

                if comment_text is not None:
                    object_text += f" - {comment_text}"
                if comment_text is not None:
                    object_text += f" (by: {author})"

                confirmation_fields.append(u">\t%s %s" % (bullet_text, object_text))

            if len(conversation.filter_result) > 10:
                confirmation_fields.append(">\t... and %d more" % (len(conversation.filter_result) - 10))
            response.add_block("\n".join(confirmation_fields))

            if conversation.sub_command is not None:
                response.add_block("Do you want to confirm this action (yes|no)\n"
                                   "or do you want to select single/multiple %s (i.e.: 1,2)?:" %
                                   conversation.sub_command.name)
            else:
                response.add_block("Do you want to confirm this action?:")

            conversation.confirmation_sent = True

            return response

    if conversation.canceled:
        slack_user.reset_conversation()
        return BotResponse(text="%sOk, action has been canceled!" % conversation.get_path())

    if conversation.confirmed:

        # delete conversation history
        slack_user.reset_conversation()

        i2_handle, i2_error = setup_icinga_connection(config)

        if not i2_handle:
            if i2_error is not None:
                error_message = i2_error
            else:
                error_message = "Unknown error while setting up Icinga2 connection"

            return slack_error_response(header="Icinga request error", error_message=error_message)

        # define filters
        filter_list = list()
        if conversation.object_type == "Host":
            for i2_object in conversation.filter_result:
                filter_list.append('host.name=="%s"' % i2_object.get("name"))
        else:
            for i2_object in conversation.filter_result:
                filter_list.append('( host.name=="%s" && service.name=="%s" )' %
                                   (i2_object.get("host_name"), i2_object.get("name")))

        success_message = None
        i2_error = None

        # get username to add as comment
        this_user_info = slack_user.data

        author_name = "Anonymous Slack user"
        if this_user_info is not None and this_user_info.get("real_name"):
            author_name = this_user_info.get("real_name")

        icinga2_filters = '(' + ' || '.join(filter_list) + ')'

        try:

            if conversation.command.name == "downtime":

                logging.debug("Sending Downtime to Icinga2")

                success_message = "Successfully scheduled downtime!"

                i2_response = i2_handle.actions.schedule_downtime(
                    object_type=conversation.object_type,
                    filters=icinga2_filters,
                    author=author_name,
                    comment=conversation.description,
                    start_time=conversation.start_date,
                    end_time=conversation.end_date,
                    duration=conversation.end_date - conversation.start_date,
                    all_services=True
                )

            elif conversation.command.name == "acknowledge":
                logging.debug("Sending Acknowledgement to Icinga2")

                success_message = "Successfully acknowledged %s problem%s!" % \
                                  (conversation.object_type, plural(len(filter_list)))

                i2_response = i2_handle.actions.acknowledge_problem(
                    object_type=conversation.object_type,
                    filters=icinga2_filters,
                    author=author_name,
                    comment=conversation.description,
                    expiry=None if conversation.end_date == -1 else conversation.end_date,
                    sticky=True
                )

            elif conversation.command.name == "comment":
                logging.debug("Sending Comment to Icinga2")

                success_message = "Successfully added %s comment%s!" % \
                                  (conversation.object_type, plural(len(filter_list)))

                i2_response = i2_handle.actions.add_comment(
                    object_type=conversation.object_type,
                    filters=icinga2_filters,
                    author=author_name,
                    comment=conversation.description
                )

            elif conversation.command.name == "reschedule":
                logging.debug("Sending reschedule check to Icinga2")

                success_message = "Successfully rescheduled %s check%s!" % \
                                  (conversation.object_type, plural(len(filter_list)))

                i2_response = i2_handle.actions.reschedule_check(
                    object_type=conversation.object_type,
                    filters=icinga2_filters,
                )

            elif conversation.command.name == "send notification":
                logging.debug("Sending custom notification to Icinga2")

                success_message = "Successfully sent %s notification%s!" % \
                                  (conversation.object_type, plural(len(filter_list)))

                i2_response = i2_handle.actions.send_custom_notification(
                    object_type=conversation.object_type,
                    filters=icinga2_filters,
                    author=author_name,
                    comment=conversation.description
                )

            elif conversation.command.name == "delay notification":
                logging.debug("Sending delay notification to Icinga2")

                success_message = "Successfully delayed %s notification%s!" % \
                                  (conversation.object_type, plural(len(filter_list)))

                i2_response = i2_handle.actions.delay_notification(
                    object_type=conversation.object_type,
                    filters=icinga2_filters,
                    timestamp=conversation.end_date
                )
            elif conversation.command.name == "remove":

                logging.debug(f"Sending remove {conversation.sub_command.name} to Icinga2")

                success_message = f"Successfully removed {conversation.sub_command.name}!"

                if conversation.sub_command.name == "acknowledgement":

                    for acknowledgement in conversation.filter_result:

                        this_object_type = "Host"
                        this_filter = 'host.name=="%s"' % acknowledgement.get("host_name")
                        if len(acknowledgement.get("service_name", "")) > 0:
                            this_object_type = "Service"
                            this_filter += ' && service.name=="%s"' % acknowledgement.get("service_name")

                        i2_response = i2_handle.actions.remove_acknowledgement(
                            object_type=this_object_type,
                            filters=this_filter,
                        )

                if conversation.sub_command.name == "comment":

                    for comment in conversation.filter_result:
                        name = "!".join(
                            [comment.get("host_name"), comment.get("service_name"), comment.get("name")]
                        ).replace("!!", "!")
                        i2_response = i2_handle.actions.remove_comment(
                            object_type="Comment",
                            name=name,
                            filters=None  # bug in icinga2apic
                        )

                if conversation.sub_command.name == "downtime":

                    for downtime in conversation.filter_result:
                        name = "!".join(
                            [downtime.get("host_name"), downtime.get("service_name"), downtime.get("name")]
                        ).replace("!!", "!")
                        i2_response = i2_handle.actions.remove_downtime(
                            object_type="Downtime",
                            name=name,
                            filters=None
                        )

        except Exception as e:
            i2_error = str(e)
            logging.error("Unable to perform Icinga2 action: %s" % i2_error)
            pass

        if i2_error:
            return slack_error_response(header="Icinga request error", error_message=i2_error)

        return BotResponse(text=success_message)

    return None


from os import write
from telegram.ext.conversationhandler import ConversationHandler
import config
import queries
from googlesearch import search
import telegram
from telegram import (
	InlineQueryResultArticle,
	InputTextMessageContent,
	InlineKeyboardButton,
	InlineKeyboardMarkup,
)
from telegram.ext import (
	CommandHandler,
	MessageHandler,
	Filters,
	CallbackQueryHandler,
)

def start(update, context):
	queries.sign_up_user(update.message.from_user.id)
	help_function(update, context)

def help_function(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	update.message.reply_text(
	text=phrases['help'])

def change_bot_lang(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	update.message.reply_text(
	phrases['bot_lang_choise'], reply_markup = InlineKeyboardMarkup(
		[
			[
				InlineKeyboardButton('Русский',callback_data='bl;ru'),
				InlineKeyboardButton('English',callback_data='bl;en')
			]
		]
	))

def find(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	update.message.reply_text(
	text=phrases['news_query'])
	return 1

def find_results(update, context):
	q = update.message.text
	phrases = queries.get_phrases(update.message.from_user.id)
	message = update.message.reply_text(
	text=phrases['news_query_searching'])
	print(q)
	results = []
	for url in search(q, tld='com.ru', lang=queries.get_lang(update.message.from_user.id), stop=6):
		results.append(url)
	if len(results) < 6:
		message.edit_text(phrases['news_query_not_found'])
	choice = 0
	queries.save_results(update.message.from_user.id, results)
	message.edit_text(results[choice],reply_markup = get_article_markup(results,choice))
	return ConversationHandler.END

def get_article_markup(results, choice):
	markup = InlineKeyboardMarkup(
		[
			[InlineKeyboardButton('Сохранить эту статью', callback_data=f's;{choice}')],
		[
			InlineKeyboardButton('(1)' if choice == 0 else '1', callback_data='a;0'),
			InlineKeyboardButton('(2)' if choice == 1 else '2', callback_data='a;1'),
			InlineKeyboardButton('(3)' if choice == 2 else '3', callback_data='a;2'),
		],
		[
			InlineKeyboardButton('(4)' if choice == 3 else '4', callback_data='a;3'),
			InlineKeyboardButton('(5)' if choice == 4 else '5', callback_data='a;4'),
			InlineKeyboardButton('(6)' if choice == 5 else '6', callback_data='a;5')
		]
		]
	)
	return markup

def change_article_button(update,context):
	query = update.callback_query
	query.answer()
	data = query.data.split(';')
	choice = int(data[1])
	results = queries.get_results(update.callback_query.message.chat.id)
	query.edit_message_text(results[choice], reply_markup = get_article_markup(results,choice))

def save_button(update, context):
	query = update.callback_query
	query.answer()
	phrases = queries.get_phrases(query.message.chat.id)
	data = query.data.split(';')
	choice = int(data[1])
	queries.add_article(query.message.chat.id, queries.get_results(query.message.chat.id)[choice])
	categories = queries.get_categories(query.message.chat.id)
	categories = '\n'.join([str(i)+'. '+categories[i] for i in range(len(categories))])
	if not categories:
		categories = phrases['no_categories']
	query.message.reply_text(phrases['save_article_category']+categories)
	return 1

def save_first(update, context):
	categories = queries.get_categories(update.message.from_user.id)
	phrases = queries.get_phrases(update.message.from_user.id)
	answer = update.message.text
	if answer.isnumeric():
		answer = int(answer)
		if len(categories) <= answer or answer < 0:
			categories = '\n'.join([str(i)+'. '+categories[i] for i in range(len(categories))])
			update.message.reply_text(phrases['wrong_category']+categories)
			return 1
		else:
			queries.add_article_cat(update.message.from_user.id, categories[answer])
			update.message.reply_text(phrases['save_article_name'])
			return 2
	queries.add_article_cat(update.message.from_user.id, answer)
	update.message.reply_text(phrases['save_article_name'])
	return 2

def save_second(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	queries.add_article_name(update.message.from_user.id, update.message.text)
	update.message.reply_text(phrases['save_article_complete'])
	return ConversationHandler.END

def list_categories(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	update.message.reply_text(
	phrases['list_categories']+'\n'.join(queries.get_categories(update.message.from_user.id)))

def list_articles(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	categories = queries.get_categories(update.message.from_user.id)
	text = []
	for c in categories:
		articles = queries.get_articles(update.message.from_user.id, c)
		text += [c] + list(map(lambda x: x[0]+': '+x[1], articles))
	update.message.reply_text('\n'.join(text))

def rename_article(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	update.message.reply_text(
	phrases['rename_article_choose_category']+'\n'.join(queries.get_categories(update.message.from_user.id)))
	return 1

def rename_article_cat(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	return 2

def rename_article_name(update, context):
	return 3

def rename_article_new_name(update, context):
	return ConversationHandler.END

def rename_category(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	return 1

def rename_category_cat(update, context):
	return 2

def rename_category_new_name(update, context):
	return ConversationHandler.END

def delete_article(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	update.message.reply_text(
	text=phrases['indev'])

def delete_article_cat(update, context):
	return 2

def delete_article_name(update, context):
	return ConversationHandler.END

def delete_category(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	update.message.reply_text(
	text=phrases['indev'])

def delete_category_cat(update, context):
	return ConversationHandler.END

def unknown_command(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	update.message.reply_text(
	text=phrases['unknown'])

def stop(update, context):
	phrases = queries.get_phrases(update.message.from_user.id)
	queries.cancel_save(update.message.from_user.id)
	update.message.reply_text(
	text=phrases['stop'])
	return ConversationHandler.END

def bot_lang_button(update, context):
	query = update.callback_query
	query.answer()
	data = query.data.split(';')
	queries.change_bot_lang(query.message.chat.id, data[1])
	phrases = queries.get_phrases(query.message.chat.id)
	query.message.reply_text(phrases['bot_lang_choise_done'])

handlers = [
	ConversationHandler(
		entry_points=[CommandHandler('find', find)],
		states={
			1:[MessageHandler(Filters.text & ~Filters.command, find_results)],
		},
		fallbacks=[CommandHandler('cancel', stop)]),
	ConversationHandler(
		entry_points=[CallbackQueryHandler(save_button, pattern='^s;*', pass_user_data=True, pass_chat_data=True)],
		states={
			1:[MessageHandler(Filters.text & ~Filters.command, save_first)],
			2:[MessageHandler(Filters.text & ~Filters.command, save_second)],
		},
		fallbacks=[CommandHandler('cancel', stop)]),

	CommandHandler('start', start),
	CommandHandler('cancel', stop),
	CommandHandler('help', help_function),
	CommandHandler('botlang', change_bot_lang),
	CommandHandler('listcat', list_categories),
	CallbackQueryHandler(change_article_button,pattern='^a;*', pass_user_data=True, pass_chat_data=True),
	CallbackQueryHandler(save_button, pattern='^s;*', pass_user_data=True, pass_chat_data=True),
	CallbackQueryHandler(bot_lang_button, pattern='^bl;*'),
	CommandHandler('listart', list_articles),
	MessageHandler(Filters.command, unknown_command),
]


#!/usr/bin/env python3

from socket import socket, gethostbyname, AF_INET, SOCK_DGRAM
import sys
from BinarioCrip import criptografia
from hdb3 import hdb
import matplotlib.pyplot as plt

PORT_NUMBER = 5004
SIZE = 1024

hostName = gethostbyname( '0.0.0.0' )

mySocket = socket( AF_INET, SOCK_DGRAM )
mySocket.bind( (hostName, PORT_NUMBER) )

print ("Test server listening on port {0}\n".format(PORT_NUMBER))

while True:
    (data,addr) = mySocket.recvfrom(SIZE)

    msgdec1 = criptografia(str(data))
    binario = ' '.join(format(ord(x), 'b') for x in str(data))
    hd = hdb(binario)
    print("\nMensagem recebida:\n{0}\n\nMensagem binaria:\n{1}\n\nMensagem decodificada:\n{2}\n\nMensagem original:\n{3}".format(hd,binario,data,msgdec1))
    plt.step(range(1, len(hd)+1), hd)
    plt.show()

sys.exit()