import numpy as np
import feature_extraction
from sklearn.ensemble import RandomForestClassifier as rfc
#from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression as lr
from flask import jsonify

def getResult(url):
    # url="http://www.facebook.com"

    #Importing dataset
    data = np.loadtxt( r"C:\Users\Maymol\Downloads\dos\dos\dos detection loan_server\src\dataset.csv", delimiter = ",")
    # print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!_____________________________________________________________________")

    # print(data)

    #Seperating features and labels
    X = data[: , :-1]
    y = data[: , -1]

       #Seperating training features, testing features, training labels & testing labels
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)
    clf = rfc()
    clf.fit(X_train, y_train)
    score = clf.score(X_test, y_test)
    # print(score*100)
    # print("hello")
    accuracy=str(score*100)

    X_new = []

    X_input = url
    ress=""
    try:
        X_new=feature_extraction.generate_data_set(X_input)
        X_new = np.array(X_new).reshape(1,-1)

        try:
            prediction = clf.predict(X_new)
            print(prediction,"-------------------------")
            if prediction == -1:
                ress="Phishing Url"
                return ress,accuracy
            else:
                ress="Legitimate Url"
                return ress,accuracy
        except Exception as r:
            print(r,"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$")
            ress="Legitimate Url"
            return ress,accuracy
    except Exception as e:
        print(e,"===========")
        ress="Phishing Url"
        return ress,accuracy

print (getResult("xxxx"))


"""Module for reading / converting pollyxt data."""
import logging
import glob
from typing import Optional, Union

import netCDF4
import numpy as np
from numpy import ma
from numpy.testing import assert_array_equal
import csv
from datetime import datetime
from scipy.interpolate import interp1d

from cloudnetpy import output, utils
from cloudnetpy.instruments import instruments
from cloudnetpy.instruments.ceilometer import Ceilometer
from cloudnetpy.metadata import MetaData
from cloudnetpy.utils import Epoch

WAVELEGNTHS = [1064,532,355]

def pollyxt2nc(
    input_folder: str,
    output_file: str,
    site_meta: dict,
    uuid: Optional[str] = None,
    date: Optional[str] = None,
) -> str:
    """
    Converts PollyXT Raman lidar data into Cloudnet Level 1b netCDF file.

    Args:
        input_folder: Path to pollyxt netCDF files.
        output_file: Output filename.
        site_meta: Dictionary containing information about the site with keys:

            - `name`: Name of the site (mandatory)
            - `altitude`: Site altitude in [m] (mandatory).
            - `latitude` (optional).
            - `longitude` (optional).
            - `zenith_angle`: If not the default 5 degrees (optional).
            - `snr_limit`: If not the default 2 (optional).
        uuid: Set specific UUID for the file.
        date: Expected date of the measurements as YYYY-MM-DD.

    Returns:
        UUID of the generated file.

    Examples:
        >>> from cloudnetpy.instruments import pollyxt2nc
        >>> site_meta = {'name': 'Mindelo', 'altitude': 13, 'zenith_angle': 6, 'snr_limit': 3}
        >>> pollyxt2nc('/path/to/files/', 'pollyxt.nc', site_meta)

    """
    snr_limit = site_meta.get("snr_limit", 2)
    track_file = site_meta.get("track_file", "")
    polly = PollyXt(site_meta, date)
    epoch = polly.fetch_data(input_folder)
    polly.get_date_and_time(epoch)
    if track_file != "":
        polly.add_coordinate_array(track_file)
    polly.fetch_zenith_angle()
    polly.calc_screened_products(snr_limit)
    polly.mask_nan_values()
    polly.prepare_data()
    polly.data_to_cloudnet_arrays()
    attributes = output.add_time_attribute(ATTRIBUTES, polly.date)
    output.update_attributes(polly.data, attributes)
    polly.add_snr_info("beta", snr_limit)
    uuid = output.save_level1b(polly, output_file, uuid)
    return uuid

class PollyXt(Ceilometer):
    def __init__(self, site_meta: dict, expected_date: Union[str, None]):
        super().__init__()
        self.site_meta = site_meta
        self.expected_date = expected_date
        self.instrument = instruments.POLLYXT

    def mask_nan_values(self):
        for array in self.data.values():
            if getattr(array, "ndim", 0) > 0:
                array[np.isnan(array)] = ma.masked

    def calc_screened_products(self, snr_limit: float = 5.0):
        for wavelength in WAVELEGNTHS:
            beta_key = "beta_%i" %wavelength
            snr_key = "snr_%i" %wavelength
            snr_key_nr = "snr_%i_nr" %wavelength
            if f"{beta_key}_raw" in self.data.keys():
                self.data[beta_key] = ma.masked_where(self.data[snr_key] < snr_limit, self.data[f"{beta_key}_raw"])
            beta_nr_key = "beta_%i_nr" %wavelength
            if f"{beta_nr_key}_raw" in self.data.keys():
                self.data[beta_nr_key] = ma.masked_where(self.data[snr_key_nr] < snr_limit, self.data[f"{beta_nr_key}_raw"])
            if wavelength == 1064:
                continue
            depol_key = "depolarisation_%i" %wavelength
            if f"{depol_key}_raw" in self.data.keys():
                self.data[depol_key] = ma.masked_where(self.data[snr_key] < snr_limit, self.data[f"{depol_key}_raw"])
                self.data[depol_key][self.data[depol_key] > 1] = ma.masked
                self.data[depol_key][self.data[depol_key] < 0] = ma.masked

    def fetch_zenith_angle(self) -> None:
        default = 5
        self.data["zenith_angle"] = float(self.metadata.get("zenith_angle", default))

    def fetch_data(self, input_folder: str) -> Epoch:
        """Read input data."""
        bsc_files = glob.glob(f"{input_folder}/*[0-9]_att*.nc")
        depol_files = glob.glob(f"{input_folder}/*[0-9]_vol*.nc")
        if not bsc_files:
            raise RuntimeError("No pollyxt files found")
        if len(bsc_files) != len(depol_files):
            raise RuntimeError("Inconsistent number of pollyxt bsc / depol files")
        bsc_nr_files = glob.glob(f'{input_folder}/*[0-9]_NR_att*.nc')
        if len(bsc_nr_files) > 0:
            if len(bsc_files) != len(bsc_nr_files):
                raise RuntimeError("Inconsistent number of pollyxt bsc / pollyxt bsc nr files")
            bsc_nr_files.sort()
            self.data["range"] = _read_array_from_three_filelists(bsc_files, bsc_nr_files, depol_files, "height")
            epoch = _include_nr_data(self, input_folder)
            return epoch
        bsc_files.sort()
        depol_files.sort()
        calibration_factors: np.ndarray = np.array([])
        self.data["range"] = _read_array_from_multiple_files(bsc_files, depol_files, "height")
        for (bsc_file, depol_file) in zip(bsc_files, depol_files):
            nc_bsc = netCDF4.Dataset(bsc_file, "r")
            nc_depol = netCDF4.Dataset(depol_file, "r")
            epoch = utils.get_epoch(nc_bsc["time"].unit)
            try:
                time = np.array(_read_array_from_file_pair(nc_bsc, nc_depol, "time"))
            except AssertionError:
                _close(nc_bsc, nc_depol)
                continue
            for idx_wavelength,wavelength in enumerate(WAVELEGNTHS):
                bsc_key = "attenuated_backscatter_%inm" %wavelength
                if bsc_key in nc_bsc.variables:
                    bsc_out_key = "beta_%i_raw" %wavelength
                    self.data = utils.append_data(self.data, bsc_out_key, nc_bsc.variables[bsc_key][:])
                    cal_fac_key = "calibration_factor_%i" %wavelength
                    calibration_factor = nc_bsc.variables[bsc_key].Lidar_calibration_constant_used
                    calibration_factor = np.repeat(calibration_factor, len(time))
                    self.data = utils.append_data(self.data, cal_fac_key, calibration_factor)
                snr_key = "SNR_%inm" %wavelength
                if snr_key in nc_bsc.variables:
                    snr_out_key = "snr_%i" %wavelength
                    self.data = utils.append_data(self.data, snr_out_key, nc_bsc.variables[snr_key][:])
                if wavelength == 1064:
                    continue
                depol_key = "volume_depolarisation_ratio_%inm" %wavelength
                if depol_key in nc_depol.variables:
                    depol_out_key = "depolarisation_%i_raw" %wavelength
                    self.data = utils.append_data(self.data, depol_out_key, nc_depol.variables[depol_key][:])
            self.data = utils.append_data(self.data, "time", time)
            _close(nc_bsc, nc_depol)
        return epoch

    def add_coordinate_array(self,track_file):
        nc_track = netCDF4.Dataset(track_file,"r")
        lat = nc_track['lat']
        lon = nc_track['lon']
        track_time = nc_track['time'][:]
        if max(track_time)>25:
            track_time = utils.seconds2hours(nc_track['time'])
        lat_int_f = interp1d(track_time,lat,fill_value='extrapolate')
        lon_int_f = interp1d(track_time,lon,fill_value='extrapolate')
        latitude = lat_int_f(self.data["time"].data)
        longitude = lon_int_f(self.data["time"].data)
        self.data = utils.append_data(self.data, "latitude", latitude)
        self.data = utils.append_data(self.data, "longitude", longitude)

def _include_nr_data(self, input_folder: str) -> Epoch:
    bsc_files = glob.glob(f"{input_folder}/*[0-9]_att*.nc")
    bsc_nr_files = glob.glob(f"{input_folder}/*[0-9]_NR_att*.nc")
    depol_files = glob.glob(f"{input_folder}/*[0-9]_vol*.nc")
    bsc_files.sort()
    bsc_nr_files.sort()
    depol_files.sort()
    calibration_factors: np.ndarray = np.array([])
    for (bsc_file, bsc_nr_file, depol_file) in zip(bsc_files, bsc_nr_files, depol_files):
        nc_bsc = netCDF4.Dataset(bsc_file, "r")
        nc_bsc_nr = netCDF4.Dataset(bsc_nr_file, "r")
        nc_depol = netCDF4.Dataset(depol_file, "r")
        epoch = utils.get_epoch(nc_bsc["time"].unit)
        try:
            time = np.array(_read_array_from_file_triplet(nc_bsc, nc_bsc_nr,  nc_depol, "time"))
        except AssertionError:
            _close(nc_bsc, nc_bsc_nr, nc_depol)
            continue
        cal_facts = {"calibration_factor_1064":[],"calibration_factor_532":[],"calibration_factor_355":[]}
        for idx_wavelength,wavelength in enumerate(WAVELEGNTHS):
            bsc_key = "attenuated_backscatter_%inm" %wavelength
            if bsc_key in nc_bsc.variables:
                bsc_out_key = "beta_%i_raw" %wavelength
                self.data = utils.append_data(self.data, bsc_out_key, nc_bsc.variables[bsc_key][:])
                cal_fac_key = "calibration_factor_%i" %wavelength
                calibration_factor = nc_bsc.variables[bsc_key].Lidar_calibration_constant_used
                calibration_factor = np.repeat(calibration_factor, len(time))
                self.data = utils.append_data(self.data, cal_fac_key, calibration_factor)
            if bsc_key in nc_bsc_nr.variables:
                bsc_out_key = "beta_%i_nr_raw" %wavelength
                self.data = utils.append_data(self.data, bsc_out_key, nc_bsc_nr.variables[bsc_key][:])
                cal_fac_key = "calibration_factor_%i_nr" %wavelength
                calibration_factor = nc_bsc_nr.variables[bsc_key].Lidar_calibration_constant_used
                calibration_factor = np.repeat(calibration_factor, len(time))
                self.data = utils.append_data(self.data, cal_fac_key, calibration_factor)
            snr_key = "SNR_%inm" %wavelength
            if snr_key in nc_bsc.variables:
                snr_out_key = "snr_%i" %wavelength
                self.data = utils.append_data(self.data, snr_out_key, nc_bsc.variables[snr_key][:])
            if snr_key in nc_bsc_nr.variables:
                snr_out_key = "snr_%i_nr" %wavelength
                self.data = utils.append_data(self.data, snr_out_key, nc_bsc_nr.variables[snr_key][:])
            if wavelength == 1064:
                continue
            depol_key = "volume_depolarization_ratio_%inm" %wavelength
            if depol_key in nc_depol.variables:
                depol_out_key = "depolarisation_%i_raw" %wavelength
                self.data = utils.append_data(self.data, depol_out_key, nc_depol.variables[depol_key][:])
        self.data = utils.append_data(self.data, "time", time)
        _close(nc_bsc, nc_bsc_nr, nc_depol)
    return epoch

def _read_array_from_three_filelists(files1: list, files2: list, files3: list, key) -> np.ndarray:
    array: np.ndarray = np.array([])
    for ind, (file1, file2, file3) in enumerate(zip(files1, files2, files3)):
        nc1 = netCDF4.Dataset(file1, "r")
        nc2 = netCDF4.Dataset(file2, "r")
        nc3 = netCDF4.Dataset(file3, "r")
        array1 = _read_array_from_file_triplet(nc1, nc2, nc3, key)
        if ind == 0:
            array = array1
        _close(nc1, nc2, nc3)
        assert_array_equal(array, array1)
    return np.array(array)

def _read_array_from_file_triplet(
    nc_file1: netCDF4.Dataset, nc_file2: netCDF4.Dataset, nc_file3: netCDF4.Dataset,key: str
) -> np.ndarray:
    array1 = nc_file1.variables[key][:]
    array2 = nc_file2.variables[key][:]
    array3 = nc_file3.variables[key][:]
    assert_array_equal(array1, array2)
    assert_array_equal(array1, array3)
    return array1

def _read_array_from_multiple_files(files1: list, files2: list, key) -> np.ndarray:
    array: np.ndarray = np.array([])
    for ind, (file1, file2) in enumerate(zip(files1, files2)):
        nc1 = netCDF4.Dataset(file1, "r")
        nc2 = netCDF4.Dataset(file2, "r")
        array1 = _read_array_from_file_pair(nc1, nc2, key)
        if ind == 0:
            array = array1
        _close(nc1, nc2)
        assert_array_equal(array, array1)
    return np.array(array)

def _read_array_from_file_pair(
    nc_file1: netCDF4.Dataset, nc_file2: netCDF4.Dataset, key: str
) -> np.ndarray:
    array1 = nc_file1.variables[key][:]
    array2 = nc_file2.variables[key][:]
    assert_array_equal(array1, array2)
    return array1

def _close(*args) -> None:
    for arg in args:
        arg.close()

ATTRIBUTES = {
    "beta_1064": MetaData(
        long_name="Attenuated backscatter coefficient",
        units="sr-1 m-1",
        comment="SNR-screened attenuated backscatter coefficient at 1064 nm. SNR threshold applied: 2.",
    ),
    "beta_532": MetaData(
        long_name="Attenuated backscatter coefficient",
        units="sr-1 m-1",
        comment="SNR-screened attenuated backscatter coefficient at 532 nm. SNR threshold applied: 2.",
    ),
    "beta_532_nr": MetaData(
        long_name="Attenuated backscatter coefficient (near range)",
        units="sr-1 m-1",
        comment="SNR-screened attenuated backscatter coefficient at 532 nm (near range). SNR threshold applied: 2.",
    ),
    "beta_355": MetaData(
        long_name="Attenuated backscatter coefficient",
        units="sr-1 m-1",
        comment="SNR-screened attenuated backscatter coefficient at 355 nm. SNR threshold applied: 2.",
    ),
    "beta_355_nr": MetaData(
        long_name="Attenuated backscatter coefficient (near range)",
        units="sr-1 m-1",
        comment="SNR-screened attenuated backscatter coefficient at 355 nm (near range). SNR threshold applied: 2.",
    ),
    "beta_1064_raw": MetaData(
        long_name="Attenuated backscatter coefficient",
        units="sr-1 m-1",
        comment="Non-screened attenuated backscatter coefficient at 1064 nm.",
    ),
    "beta_532_raw": MetaData(
        long_name="Attenuated backscatter coefficient",
        units="sr-1 m-1",
        comment="Non-screened attenuated backscatter coefficient at 532 nm.",
    ),
    "beta_532_nr_raw": MetaData(
        long_name="Attenuated backscatter coefficient (near range)",
        units="sr-1 m-1",
        comment="Non-screened attenuated backscatter coefficient at 532 nm (near range).",
    ),
    "beta_355_raw": MetaData(
        long_name="Attenuated backscatter coefficient",
        units="sr-1 m-1",
        comment="Non-screened attenuated backscatter coefficient at 355 nm.",
    ),
    "beta_355_nr_raw": MetaData(
        long_name="Attenuated backscatter coefficient (near range)",
        units="sr-1 m-1",
        comment="Non-screened attenuated backscatter coefficient at 355 nm (near range).",
    ),
    "depolarisation_532": MetaData(
        long_name="Lidar volume linear depolarisation ratio",
        units="1",
        comment="SNR-screened lidar volume linear depolarisation ratio at 532 nm.",
    ),
    "depolarisation_355": MetaData(
        long_name="Lidar volume linear depolarisation ratio",
        units="1",
        comment="SNR-screened lidar volume linear depolarisation ratio at 355 nm.",
    ),
    "depolarisation_1064_raw": MetaData(
        long_name="Lidar volume linear depolarisation ratio",
        units="1",
        comment="Non-screened lidar volume linear depolarisation ratio at 1064 nm.",
    ),
    "depolarisation_532_raw": MetaData(
        long_name="Lidar volume linear depolarisation ratio",
        units="1",
        comment="Non-screened lidar volume linear depolarisation ratio at 532 nm.",
    ),
    "depolarisation_355_raw": MetaData(
        long_name="Lidar volume linear depolarisation ratio",
        units="1",
        comment="Non-screened lidar volume linear depolarisation ratio at 355 nm.",
    ),
    "snr_1064": MetaData(
        long_name="Signal-to-Noise Ratio (1064 nm)",
        units="1",
        comment="SNR of respective channel calculated according to Heese et al., 2010, ACP: \n Ceilometer lidar comparison: backscatter coefficient retrieval and signal-to-noise ratio determination.",
    ),
    "snr_532": MetaData(
        long_name="Signal-to-Noise Ratio (532 nm)",
        units="1",
        comment="SNR of respective channel calculated according to Heese et al., 2010, ACP: \n Ceilometer lidar comparison: backscatter coefficient retrieval and signal-to-noise ratio determination.",
    ),
    "snr_355": MetaData(
        long_name="Signal-to-Noise Ratio (355 nm)",
        units="1",
        comment="SNR of respective channel calculated according to Heese et al., 2010, ACP: \n Ceilometer lidar comparison: backscatter coefficient retrieval and signal-to-noise ratio determination.",
    ),
    "snr_532_nr": MetaData(
        long_name="Signal-to-Noise Ratio (532 nm near range)",
        units="1",
        comment="SNR of respective channel calculated according to Heese et al., 2010, ACP: \n Ceilometer lidar comparison: backscatter coefficient retrieval and signal-to-noise ratio determination.",
    ),
    "snr_355_nr": MetaData(
        long_name="Signal-to-Noise Ratio (355 nm near range)",
        units="1",
        comment="SNR of respective channel calculated according to Heese et al., 2010, ACP: \n Ceilometer lidar comparison: backscatter coefficient retrieval and signal-to-noise ratio determination.",
    ),
    "calibration_factor_1064": MetaData(
        long_name="Attenuated backscatter at 1064 calibration factor",
        units="1",
        comment="Calibration factor applied.",
    ),
    "calibration_factor_532": MetaData(
        long_name="Attenuated backscatter at 532 calibration factor",
        units="1",
        comment="Calibration factor applied.",
    ),
    "calibration_factor_355": MetaData(
        long_name="Attenuated backscatter at 355 calibration factor",
        units="1",
        comment="Calibration factor applied.",
    ),
    "calibration_factor_532_nr": MetaData(
        long_name="Attenuated backscatter at 532 near range calibration factor",
        units="1",
        comment="Calibration factor applied.",
    ),
    "calibration_factor_355_nr": MetaData(
        long_name="Attenuated backscatter at 355 near range calibration factor",
        units="1",
        comment="Calibration factor applied.",
    ),
}


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

# file with periodical tasks
from __future__ import absolute_import

from django.db.models import Q
from celery.utils.log import get_task_logger
from celery import shared_task
from decimal import Decimal
from joebot_at.taskapp.celery import app as celery_app
from datetime import datetime
from dateutil.relativedelta import relativedelta
from tabulate import tabulate

from .ots import OTS
from trader.models import Wallet, MARKETS, get_coin_labels
from trader.btc_trader import BTCTrader

from ticker.api.polo import Poloniex2, API
from configobj import ConfigObj

logger = get_task_logger(__name__)

@shared_task
def test_task():
    print('Hello! It is {}'.format(datetime.utcnow()))
    return True

@shared_task
def get_ticker_task():
    exchange = API()
    data = exchange.returnTicker()
    result = []
    for name in data:
        if name in [m[0] for m in MARKETS]:
            result.append({name: data[name]})
            # print('{}: {}\n'.format(name, data[name]))
    return result

@shared_task
def get_price_task():
    exchange = API()
    data = exchange.returnTicker()
    result = []
    for name in data:
        if name in [m[0] for m in MARKETS]:
            result.append({name: data[name]['last']})
            # print('Last price {}: {}\n'.format(name, data[name]['last']))
    return result

@shared_task
def process_month_avg_task():
    # it is need to import in runtime!
    from ticker.strategies.month_average import MonthAverageStrategy
    strat = MonthAverageStrategy('BTC_BCH')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_ETH')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_ZEC')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_XRP')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_DASH')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_LTC')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_STR')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_XMR')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_STRAT')
    strat.check_for_trading()

    strat = MonthAverageStrategy('BTC_GNO')
    strat.check_for_trading()

    return True

@shared_task
def get_balance_task():

    config = ConfigObj('config.ini')
    api_key = bytes(config['api_key'], encoding='utf8')
    secret = bytes(config['secret'], encoding='utf8')
    polo = API(dict(api_key=api_key, secret=secret))
    balance = polo.returnBalances()
    if balance:
        result = []
        for name in balance.keys():
            if name in get_coin_labels():
                result.append({name: balance[name]})
                ##print('Balance {}: {}\n'.format(name, balance[name]))
        return result
    else:
        return 'ERROR: Balance call failed.'


#!/usr/bin/env python2
# -*- coding: utf-8 -*-

"""
Demo to illustrate Dialog (Dlg) classes and usage.
"""

from __future__ import division

from psychopy import gui

# create a DlgFromDict
info = {'Observer':'jwp', 'gender':['male', 'female'],
    'GratingOri':45, 'ExpVersion': 1.1, 'Debug Mode': True}
infoDlg = gui.DlgFromDict(dictionary=info, title='TestExperiment',
    order=['ExpVersion', 'Observer'],
    tip={'Observer': 'trained visual observer, initials'},
    fixed=['ExpVersion'])  # this attribute can't be changed by the user
if infoDlg.OK:  # this will be True (user hit OK) or False (cancelled)
    print(info)
else:
    print('User Cancelled')

# This alternative uses a gui.Dlg and you manually extract the data.
# This approach gives more control, eg, text color.
dlg = gui.Dlg(title="My experiment", pos=(200, 400))
dlg.addText('Subject Info', color='Blue')
dlg.addField('Name:', tip='or subject code')
dlg.addField('Age:', 21)
dlg.addText('Experiment Info', color='Blue')
dlg.addField('', 45)

dlg.show()  # you have to call show() for a Dlg (automatic with a DlgFromDict)
if dlg.OK:
    thisInfo = dlg.data  # this will be a list of data returned from each field
    print(thisInfo)
else:
    print('user cancelled')

# The contents of this file are in the public domain.


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

import pickle

from sklearn.metrics import classification_report,confusion_matrix

def test_results(df):

    print('***********TEST RESULTS***********')

    models=[]
    mod_name = ['Decision Trees','Random Forest','Neural Network']

    #print('TEST shape:', df.shape,'\n','TEST Columns:' ,df.columns)

    X_test = df.drop('less than 50K',axis=1)
    y_test = df['less than 50K']

    #print("X shape = " ,X_test.shape)

    # -- loading decision Tree model

    file = open("DT_gs.model",'rb')
    DT_gs = pickle.load(file)
    file.close()
    models.append(DT_gs)

    # -- loading Random Forest model

    # RandomForest_gs.model is in a rar ,
    # its around 200mb so it needs to be unzipped

    file2 = open("RandomForest_gs.model",'rb')
    rf_gs = pickle.load(file2)
    file2.close()
    models.append(rf_gs)

     #-- Loading Neural Network model
    file3 = open("NeuralNetwrok_GsCv.model",'rb')
    clf_NN = pickle.load(file3)
    file3.close()
    models.append(clf_NN)

    #model_dict = dict(zip(mod_name,models))

    for i,name in enumerate(mod_name):
        print(i+1,name)

    for i,name in enumerate(mod_name):
        print('Scores for ',name,' model :',end='\n\n')
        prediction = models[i].predict(X_test)
        score = models[i].score(X_test,y_test)
        print('classification report : ',classification_report(y_test,prediction))
        print(confusion_matrix(y_test,prediction))
        print('Score: ',score)
        print('=========================================','\n\n')


#    recording = AudioSegment.from_file(BytesIO(song_byte), format="mp3")
#    recording.export('new.mp3', format='mp3')  # for export
#    play(recording)  # for play

import socket
import selectors
from pydub import AudioSegment
from io import BytesIO

sel = selectors.DefaultSelector()
mp3_segments = []
mp3_dic = {}
iterator = 0

def joint_segments(segment_bytelist):

    songbyte = b""

    for byte_segment in segment_bytelist:
        songbyte = songbyte + byte_segment

    return songbyte

def accept(sock_a, mask_a):
    sock_conn, addr = sock_a.accept()  # Should be ready
    print('aceptado', sock_conn, ' de', addr)
    sock_conn.setblocking(False)
    sel.register(sock_conn, selectors.EVENT_READ | selectors.EVENT_WRITE, read_write)
    # sel.register(sock_conn,selectors.EVENT_WRITE, read_write)

def read_write(sock_c, mask_c):
    if mask & selectors.EVENT_READ:
        data = sock_c.recv(1024)  # Should be ready
        global mp3_dic
        # global mp3_segments
        global iterator

        if data:
            print('recibido', repr(data), 'a', sock_c)

            if str(sock_c.fileno()) in mp3_dic:
                mp3_dic[str(sock_c.fileno())].append(data)
            else:
                mp3_dic[str(sock_c.fileno())] = []
                mp3_dic[str(sock_c.fileno())].append(data)
            # print(sock_c.fileno())
            # mp3_segments.append(data)
            print('respondiendo', repr(data), 'a', sock_c)
            sock_c.sendall(data)  # Hope it won't block
        else:
            #newsong_byte = joint_segments(mp3_segments)
            newsong_byte = joint_segments(mp3_dic[str(sock_c.fileno())])

            newsong_name = "new_mp3_" + str(iterator) + ".mp3"

            newsong_mp3 = AudioSegment.from_file(BytesIO(newsong_byte), format="mp3")
            newsong_mp3.export(newsong_name, format='mp3')

            iterator = iterator + 1
            # mp3_segments = []

            print('cerrando', sock_c)
            sel.unregister(sock_c)
            sock_c.close()
    if mask & selectors.EVENT_WRITE:
        print("enviando datos")

with socket.socket() as sock_accept:
    sock_accept.bind(('localhost', 12345))
    sock_accept.listen(100)
    sock_accept.setblocking(False)
    sel.register(sock_accept, selectors.EVENT_READ, accept)
    while True:
        print("Esperando evento...")
        events = sel.select()
        for key, mask in events:
            callback = key.data
            callback(key.fileobj, mask)


import time
from enum import Enum
from smbus2 import SMBus, i2c_msg
from MainControlLoop.lib.devices import Device

class AntennaDeployerCommand(Enum):
    SYSTEM_RESET = 0xAA
    WATCHDOG_RESET = 0xCC

    ARM_ANTS = 0xAD
    DISARM_ANTS = 0xAC

    DEPLOY_1 = 0xA1
    DEPLOY_2 = 0xA2
    DEPLOY_3 = 0xA3
    DEPLOY_4 = 0xA4

    AUTO_DEPLOY = 0xA5
    CANCEL_DEPLOY = 0xA9

    DEPLOY_1_OVERRIDE = 0xBA
    DEPLOY_2_OVERRIDE = 0xBB
    DEPLOY_3_OVERRIDE = 0xBC
    DEPLOY_4_OVERRIDE = 0xBD

    GET_TEMP = 0xC0
    GET_STATUS = 0xC3

    GET_COUNT_1 = 0xB0
    GET_COUNT_2 = 0xB1
    GET_COUNT_3 = 0xB2
    GET_COUNT_4 = 0xB3

    GET_UPTIME_1 = 0xB4
    GET_UPTIME_2 = 0xB5
    GET_UPTIME_3 = 0xB6
    GET_UPTIME_4 = 0xB7

class AntennaDeployer(Device):
    BUS_NUMBER = 1
    PRIMARY_ADDRESS = 0x31
    SECONDARY_ADDRESS = 0x32
    EXPECTED_BYTES = {
        AntennaDeployerCommand.SYSTEM_RESET: 0,
        AntennaDeployerCommand.WATCHDOG_RESET: 0,

        AntennaDeployerCommand.ARM_ANTS: 0,
        AntennaDeployerCommand.DISARM_ANTS: 0,

        AntennaDeployerCommand.DEPLOY_1: 0,
        AntennaDeployerCommand.DEPLOY_2: 0,
        AntennaDeployerCommand.DEPLOY_3: 0,
        AntennaDeployerCommand.DEPLOY_4: 0,

        AntennaDeployerCommand.AUTO_DEPLOY: 0,
        AntennaDeployerCommand.CANCEL_DEPLOY: 0,

        AntennaDeployerCommand.DEPLOY_1_OVERRIDE: 0,
        AntennaDeployerCommand.DEPLOY_2_OVERRIDE: 0,
        AntennaDeployerCommand.DEPLOY_3_OVERRIDE: 0,
        AntennaDeployerCommand.DEPLOY_4_OVERRIDE: 0,

        AntennaDeployerCommand.GET_TEMP: 2,
        AntennaDeployerCommand.GET_STATUS: 2,

        AntennaDeployerCommand.GET_COUNT_1: 1,
        AntennaDeployerCommand.GET_COUNT_2: 1,
        AntennaDeployerCommand.GET_COUNT_3: 1,
        AntennaDeployerCommand.GET_COUNT_4: 1,

        AntennaDeployerCommand.GET_UPTIME_1: 2,
        AntennaDeployerCommand.GET_UPTIME_2: 2,
        AntennaDeployerCommand.GET_UPTIME_3: 2,
        AntennaDeployerCommand.GET_UPTIME_4: 2,
    }

    def __init__(self):
        super().__init__("AntennaDeployer")
        self.bus = SMBus()

    def write(self, command: AntennaDeployerCommand, parameter: int) -> bool or None:
        """
        Wrapper for SMBus write word data
        :param command: (AntennaDeployerCommand) The antenna deployer command to run
        :param parameter: (int) The parameter to pass in to the command (usually 0x00)
        :return: (bool or None) success
        """
        if type(command) != AntennaDeployerCommand:
            return

        try:
            self.bus.open(self.BUS_NUMBER)
            self.bus.write_word_data(self.PRIMARY_ADDRESS, command.value, parameter)
            self.bus.close()
        except:
            return False

        return True

    def read(self, command: AntennaDeployerCommand) -> bytes or None:
        """
        Wrapper for SMBus to read from AntennaDeployer
        :param command: (AntennaDeployerCommand) The antenna deployer command to run
        :return: (ctypes.LP_c_char, bool) buffer, success
        """
        if type(command) != AntennaDeployerCommand:
            return

        success = self.write(command, 0x00)
        if not success:
            return None, False

        time.sleep(0.5)
        try:
            i2c_obj = i2c_msg.read(self.PRIMARY_ADDRESS, self.EXPECTED_BYTES[command])
            self.bus.open(self.BUS_NUMBER)
            self.bus.i2c_rdwr(i2c_obj)
            self.bus.close()
            return i2c_obj.buf, True
        except:
            return None, False

    def functional(self):
        """
        :return: (bool) i2c file opened by SMBus
        """
        return self.bus.fd is not None

    def reset(self):
        """
        Resets the Microcontroller on the ISIS Antenna Deployer
        :return: (bool) no error
        """
        try:
            self.bus.open(self.BUS_NUMBER)
            self.write(AntennaDeployerCommand.SYSTEM_RESET, 0x00)
            self.bus.close()
            return True
        except:
            return False

    def disable(self):
        """
        Disarms the ISIS Antenna Deployer
        """
        try:
            self.bus.open(self.BUS_NUMBER)
            self.write(AntennaDeployerCommand.DISARM_ANTS, 0x00)
            self.bus.close()
            return True
        except:
            return False

    def enable(self):
        """
        Arms the ISIS Antenna Deployer
        """
        try:
            self.bus.open(self.BUS_NUMBER)
            self.write(AntennaDeployerCommand.ARM_ANTS, 0x00)
            self.bus.close()
            return True
        except:
            return False


from collections import defaultdict

def operation(color_dict):
    sorted_dict = dict(sorted(color_dict.items()))
    return min(sorted_dict, key=sorted_dict.get)

def main():
    count = int(input())
    color_dict = defaultdict(lambda: 0)
    for i in range(count):
        color_dict[input()] += 1
    print(operation(color_dict))

main()
# tests
assert operation({'1': 2, '2': 1}) == '2', "test 1 failed"
assert operation({'5': 1, '1': 2, '2': 1, '3': 1, '4': 1}) == '2', "test 2 failed"
assert operation({'6': 1, '4': 1, '8': 1}) == '4', "test 3 failed"


def get_index_of_tuple(l, index, value):
    for pos,t in enumerate(l):
        if t[index] == value:
            return pos

    # Matches behavior of list.index
    raise ValueError("list.index(x):", value, "not in list")

def construct_path(complete_closed_list, goal, start):
    current = goal  # construct the path backwards
    current_index = get_index_of_tuple(complete_closed_list, 2, current)
    parent_tuple = complete_closed_list[current_index]
    parent = parent_tuple[3]
    path = [parent]

    while parent != start:
        current_index = get_index_of_tuple(complete_closed_list, 2, parent)
        parent_tuple = complete_closed_list[current_index]
        parent = parent_tuple[3]
        path.append(parent)

    return path

def construct_path_from_dict(parents, goal, start):
    current = goal
    parent = parents[current]
    path = [parent]

    while parent is not start:
        temp = parent
        parent = parents[temp]
        path.append(parent)

    return path

def color_explored_cells(closed_list, maze, start, goal):
    #  copy = list(maze)
    for cell in closed_list:
        if cell is not start and cell is not goal:
            x = cell[0]
            y = cell[1]
            maze[x][y] = 7
    return maze

def color_shortest_path(path, maze, start):
    #  copy = list(maze)
    for cell in path:
        if cell is not start:
            x = cell[0]
            y = cell[1]
            maze[x][y] = - 1
    return maze


# -*- coding: utf-8 -*-
from __future__ import absolute_import

import logging
plainLogger = logging.getLogger(__name__)

import os
import subprocess

from abc import ABCMeta, abstractproperty, abstractmethod
from ruamel.yaml.comments import CommentedMap, CommentedSeq
from six import add_metaclass, iteritems

from container import conductor_only, host_only
from container import exceptions
from container.docker.engine import Engine as DockerEngine, log_runs
from container.utils.visibility import getLogger

logger = getLogger(__name__)

@add_metaclass(ABCMeta)
class K8sBaseEngine(DockerEngine):

    # Capabilities of engine implementations
    CAP_BUILD_CONDUCTOR = False
    CAP_BUILD = False
    CAP_DEPLOY = True
    CAP_IMPORT = False
    CAP_INSTALL = False
    CAP_LOGIN = True
    CAP_PUSH = True
    CAP_RUN = True
    CAP_VERSION = False

    display_name = u'K8s'

    _k8s_client = None
    _deploy = None

    def __init__(self, project_name, services, debug=False, selinux=True, settings=None, **kwargs):
        if not settings:
            settings = {}
        k8s_namespace = settings.get('k8s_namespace', {})
        self.namespace_name = k8s_namespace.get('name', None) or project_name
        self.namespace_display_name = k8s_namespace.get('display_name')
        self.namespace_description = k8s_namespace.get('description')
        super(K8sBaseEngine, self).__init__(project_name, services, debug, selinux=selinux, **kwargs)
        logger.debug("k8s namespace", namspace=self.namespace_name, display_name=self.namespace_display_name,
                     description=self.namespace_description)
        logger.debug("Volume for k8s", volumes=self.volumes)

    @property
    @abstractproperty
    def deploy(self):
        pass

    @property
    @abstractproperty
    def k8s_client(self):
        pass

    @property
    def k8s_config_path(self):
        return os.path.normpath(os.path.expanduser('~/.kube/config'))

    @conductor_only
    def pre_deployment_setup(self, project_name, services, deployment_output_path=None, **kwargs):
        # Prior to running the playbook, install the ansible.kubernetes-modules role

        if not os.path.isdir(os.path.join(deployment_output_path, 'roles')):
            # Create roles subdirectory
            os.mkdir(os.path.join(deployment_output_path, 'roles'), 0o777)

        role_path = os.path.join(deployment_output_path, 'roles', 'ansible.kubernetes-modules')
        if deployment_output_path and not os.path.exists(role_path):
            # Install the role, if not already installed
            ansible_cmd = "ansible-galaxy -vvv install -p ./roles ansible.kubernetes-modules"
            logger.debug('Running ansible-galaxy', command=ansible_cmd, cwd=deployment_output_path)
            process = subprocess.Popen(ansible_cmd,
                                       shell=True,
                                       bufsize=1,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.STDOUT,
                                       cwd=deployment_output_path,
                                       )
            log_iter = iter(process.stdout.readline, '')
            while process.returncode is None:
                try:
                    plainLogger.info(log_iter.next().rstrip())
                except StopIteration:
                    process.wait()
                finally:
                    process.poll()

            if process.returncode:
                raise exceptions.AnsibleContainerDeployException(u"Failed to install ansible.kubernetes-modules role")

    @log_runs
    @host_only
    @abstractmethod
    def run_conductor(self, command, config, base_path, params, engine_name=None, volumes=None):
        volumes = {}
        k8s_auth = config.get('settings', {}).get('k8s_auth', {})

        # Set a value for config_file
        if not k8s_auth.get('config_file'):
            k8s_auth['config_file'] = self.k8s_config_path
        # Verify the config_file exists
        if not os.path.isfile(k8s_auth['config_file']):
            raise exceptions.AnsibleContainerConfigException("Unable to locate {}".format(k8s_auth['config_file']))
        # Mount the config_file to the conductor
        volumes[k8s_auth['config_file']] = {'bind': '/root/.kube/config', 'mode': 'ro'}

        if k8s_auth:
            # check if we need to mount any other paths
            path_params = ['config_file', 'ssl_ca_cert', 'cert_file', 'key_file']
            for param in path_params:
                if k8s_auth.get(param, None) is not None:
                    volumes[k8s_auth[param]] = {'bind': k8s_auth[param], 'mode': 'ro'}

        # Add k8s_auth settings as environment variables in the conductor
        if not params.get('with_variables'):
            params['with_variables'] = []
        for key in k8s_auth:
            if key == 'config_file':
                params['with_variables'].append("K8S_AUTH_KUBECONFIG={}".format(k8s_auth[key]))
            else:
                params['with_variables'].append("K8S_AUTH_{}={}".format(key.upper(), k8s_auth[key]))
        return super(K8sBaseEngine, self).run_conductor(command, config, base_path, params,
                                                        engine_name=engine_name,
                                                        volumes=volumes)

    @conductor_only
    def generate_orchestration_playbook(self, url=None, namespace=None, settings=None, repository_prefix=None,
                                        pull_from_url=None, tag=None, vault_files=None, **kwargs):
        """
        Generate an Ansible playbook to orchestrate services.
        :param url: registry URL where images were pushed.
        :param namespace: registry namespace
        :param repository_prefix: prefix to use for the image name
        :param settings: settings dict from container.yml
        :param pull_from_url: if url to pull from is different than url
        :return: playbook dict
        """

        def _update_service(service_name, service_config):
            if url and namespace:
                # Reference previously pushed image
                image_id = self.get_latest_image_id_for_service(service_name)
                if not image_id:
                    raise exceptions.AnsibleContainerConductorException(
                        u"Unable to get image ID for service {}. Did you forget to run "
                        u"`ansible-container build`?".format(service_name)
                    )
                image_tag = tag or self.get_build_stamp_for_image(image_id)
                if repository_prefix:
                    image_name = "{}-{}".format(repository_prefix, service_name)
                elif repository_prefix is None:
                    image_name = "{}-{}".format(self.project_name, service_name)
                else:
                    image_name = service_name
                repository = "{}/{}".format(namespace, image_name)
                image_name = "{}:{}".format(repository, image_tag)
                pull_url = pull_from_url if pull_from_url else url
                service_config['image'] = "{}/{}".format(pull_url.rstrip('/'), image_name)
            else:
                # We're using a local image, so check that the image was built
                image = self.get_latest_image_for_service(service_name)
                if image is None:
                    raise exceptions.AnsibleContainerConductorException(
                        u"No image found for service {}, make sure you've run `ansible-container "
                        u"build`".format(service_name)
                    )
                service_config['image'] = image.tags[0]

        for service_name, service in iteritems(self.services):
            # set the image property of each container
            if service.get('containers'):
                for container in service['containers']:
                    if container.get('roles'):
                        container_service_name = "{}-{}".format(service_name, container['container_name'])
                        _update_service(container_service_name, container)
                    else:
                        container['image'] = container['from']
            elif service.get('roles'):
                _update_service(service_name, service)
            else:
                service['image'] = service['from']

        play = CommentedMap()
        play['name'] = u'Manage the lifecycle of {} on {}'.format(self.project_name, self.display_name)
        play['hosts'] = 'localhost'
        play['gather_facts'] = 'no'
        play['connection'] = 'local'
        play['roles'] = CommentedSeq()
        play['vars_files'] = CommentedSeq()
        play['tasks'] = CommentedSeq()
        role = CommentedMap([
            ('role', 'ansible.kubernetes-modules')
        ])
        if vault_files:
            play['vars_files'].extend(vault_files)
        play['roles'].append(role)
        play.yaml_set_comment_before_after_key(
            'roles', before='Include Ansible Kubernetes and OpenShift modules', indent=4)
        play.yaml_set_comment_before_after_key('tasks', before='Tasks for setting the application state. '
                                               'Valid tags include: start, stop, restart, destroy', indent=4)
        play['tasks'].append(self.deploy.get_namespace_task(state='present', tags=['start']))
        play['tasks'].append(self.deploy.get_namespace_task(state='absent', tags=['destroy']))
        play['tasks'].extend(self.deploy.get_secret_tasks(tags=['start']))
        play['tasks'].extend(self.deploy.get_service_tasks(tags=['start']))
        play['tasks'].extend(self.deploy.get_deployment_tasks(engine_state='stop', tags=['stop', 'restart']))
        play['tasks'].extend(self.deploy.get_deployment_tasks(tags=['start', 'restart']))
        play['tasks'].extend(self.deploy.get_pvc_tasks(tags=['start']))

        playbook = CommentedSeq()
        playbook.append(play)

        logger.debug(u'Created playbook to run project', playbook=playbook)
        return playbook


data_struct = {"total_spam":0,"total_ham":0,"total_spam_words":0,"total_ham_words":0,"bag_of_words":{}}

def learn(message, messagetype):
	bag_of_words = data_struct['bag_of_words']
	words = message.split(" ")
	for word in words:
		try:
			bag_of_words[word][messagetype]+=1
		except KeyError:
			bag_of_words[word] = [1-messagetype,messagetype]
		data_struct["total_spam_words"]+=messagetype
		data_struct["total_ham_words"]+=(1-messagetype)

	data_struct["total_spam"]+=messagetype
	data_struct["total_ham"]+=(1-messagetype)

#K is laplacian smoother
def predict(message,K):
	bag_of_words = data_struct['bag_of_words']
	k=K
	#total messages
	total_messages=data_struct["total_spam"]+data_struct["total_ham"]
	#prior probability of spam
	p_s=(data_struct["total_spam"]+k)/(total_messages*1.0+k*2)
	#prior probability of ham
	p_h=(data_struct["total_ham"]+k)/(total_messages*1.0+k*2)
	words=message.split(" ")
	#p_m_s of message given its spam && p_m_h probability of message given its ham
	p_m_s=1
	p_m_h=1
	for word in words:
		try:
			value1 = bag_of_words[word][0]
			value2 = bag_of_words[word][1]
		except:
			bag_of_words[word] = [0,1]
		p_m_s*=((bag_of_words[word][1]*1.0+k)/(data_struct["total_spam_words"]+k*(len(bag_of_words))))
		p_m_h*=((bag_of_words[word][0]*1.0+k)/(data_struct["total_ham_words"]+k*(len(bag_of_words))))
	#bayes rule && p_s_m probability of spam given a particluar message
	if p_m_s*p_s+p_m_h*p_h is float(0.00000):
		return 0
	else:
		try:
			p_s_m = p_s*p_m_s/(p_m_s*p_s+p_m_h*p_h)
			return p_s_m
		except:
			return 0


import sys
from collections import deque
input = sys.stdin.readline

n = int(input())
q = deque(enumerate(map(int, input().split())))

sol = []

while q:
    idx, paper = q.popleft()
    sol.append(idx + 1)

    if paper > 0:
        q.rotate(-(paper - 1))
    elif paper < 0:
        q.rotate(-paper)

print(' '.join(map(str, sol)))
# 문제를 푸는 것은 이해가 되는데 이걸 적재적소에 사용하는 방법을 잘 모르겠다. join 과 enumerate, deque, rotate,  이런것들을 잘 알아보자
# 이런식으로 정답만을 가지고 오는 방식은 복습을 하지 않는한 내 것이 안된다. 좀더 공부를 더 해보자 시바 진짜 혼자서는 못풀겠다. ㅈ같네


import numpy as np

from simtk import unit
from simtk.openmm.app import PDBFile, ForceField, PME, Modeller
from openff.toolkit.topology import Molecule
from openmmforcefields.generators import GAFFTemplateGenerator, SMIRNOFFTemplateGenerator
from openmmtools import testsystems

import parmed

import HPC_Drug.MD.openbabel_utils as _obabel

def get_ligand_ff_generator(ligand_forcefield, ligand):
    """Returns the right ligand forcefield generator

    Parameters
    -------------
    ligand_forcefield : str
        the forcefield to use, if None the latest gaff will be used
    ligand : openff.toolkit.topology.Molecule

    Returns
    -----------
    openmmforcefields.generators.GAFFTemplateGenerator or openmmforcefields.generators.SMIRNOFFTemplateGenerator
    """
    if ligand_forcefield is  None:
        ligand_ff_generator = GAFFTemplateGenerator(molecules=ligand)

    elif ligand_forcefield in GAFFTemplateGenerator.INSTALLED_FORCEFIELDS:
        ligand_ff_generator = GAFFTemplateGenerator(molecules=ligand, forcefield=ligand_forcefield)

    elif ligand_forcefield in SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS:
        ligand_ff_generator = SMIRNOFFTemplateGenerator(molecules=ligand, forcefield=ligand_forcefield)

    else:
        raise ValueError(f'{ligand_forcefield} is not a supported ligand forcefield\n'
        'the supported ones are:\n'
        f"{' '.join(GAFFTemplateGenerator.INSTALLED_FORCEFIELDS)}\n"
        f"{' '.join(SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS)}")

    return ligand_ff_generator

def create_water_box(box_edge=3.0*unit.nanometers,
    water_model='tip3p'):
    """Creates a water box with openmmtools.testsystems.WaterBox

    Parameters
    -------------
    box_edge : openmm.Quantity or float, default=3.0*unit.nanometers
        if a float is given it will be considered in nm
    water_model : str, default=tip3p
        a water model supported by openmmtools.testsystems.WaterBox

    Returns
    -----------
    a box of only water with attributes `positions` and `topology` in openmm format
    """
    if not unit.is_quantity(box_edge):
        box_edge = box_edge * unit.nanometers

    water_box = testsystems.WaterBox(box_edge=box_edge, model=water_model,
                                 constrained=False, nonbondedMethod=PME, ionic_strength=0*unit.molar)

    return water_box

def parametrize_complex(protein_water,
    ligand,
    forcefields=None,
    ligand_forcefield=None,
    output_prefix='complex'):
    """"Parametrizes the protein ligand water ions complex
    and creates .top .pdb .gro .prmtop .inpcrd files

    Paramenters
    -------------
    protein_water : str
        the pdb file of the protein, water and ions (not the ligand)
    ligand : str or openff.toolkit.topology.Molecule
        the SDF file of the ligand or a Molecule instance
        if the coordinates of the ligand are not the right ones
        the parametrization will still be succesful but of course the
        obtained structure files will be useless
    forcefields : iterable(str), default=['amber/ff14SB.xml', 'amber/tip3p_standard.xml']
        The forcefield files for the protein, solvent and ions in xml format
        compatible with simtk.openmm.Forcefield they can be the available ones
        in openmm and openmmforcefields or a custom one in the working directory.
        There is no limit to the number of files.
        Do not give the ligand forcefield here
    ligand_forcefield : str, default=the newest gaff FF available
        The forcefield for the ligand, can be any of
        the supported ones by openmmforcefields.generators, at the moment Gaff and the
        openff initiative ones (es SMIRNOFF) are the only ones supported
    output_prefix : str, default='complex'
        the prefix for all the created files, WILL OVERWRITE PRE EXISTSING ONES!
    """

    if not isinstance(ligand, Molecule):
        ligand = Molecule.from_file(ligand)

    if forcefields is None:
        forcefields = ['amber/ff14SB.xml', 'amber/tip3p_standard.xml']

    ligand_ff_generator = get_ligand_ff_generator(ligand_forcefield=ligand_forcefield,
        ligand=ligand)

    system_kwargs = {'constraints': None, 'rigidWater': False, 'nonbondedMethod': PME}

    forcefield = ForceField(*forcefields)

    forcefield.registerTemplateGenerator(ligand_ff_generator.generator)

    protein_water_pdb = PDBFile(protein_water)
    protein_ligand_modeller = Modeller(protein_water_pdb.topology,
        protein_water_pdb.positions)

    protein_ligand_modeller.add(ligand.to_topology().to_openmm(), ligand.conformers[0])

    # Make the protein ligand .top file
    protein_ligand_system = forcefield.createSystem(
        protein_ligand_modeller.topology, **system_kwargs)

    pmd_structure = parmed.openmm.load_topology(protein_ligand_modeller.topology,
    system=protein_ligand_system, xyz=protein_ligand_modeller.positions)

    # Gromacs files
    pmd_structure.save(output_prefix + '.top', overwrite=True)
    pmd_structure.save(output_prefix + '.pdb', overwrite=True)
    pmd_structure.save(output_prefix + '.gro', overwrite=True)

    # Amber files
    pmd_structure.save(output_prefix + '.prmtop', overwrite=True)
    pmd_structure.save(output_prefix + '.inpcrd', overwrite=True)

    # The parmed top files have system1 instead of Protein and when
    # defining a restraint gromacs will complain
    with open(output_prefix + '.top', 'r') as f:
        protein_top_str = f.read()

    with open(output_prefix + '.top', 'w') as f:
        f.write(protein_top_str.replace('system1', 'Protein'))

def parametrize_ligand(ligand,
    water_box,
    water_forcefields=None,
    ligand_forcefield=None,
    ligand_prefix='LIG',
    water_prefix='only_water'):
    """Parametrizes the ligand and a box of water

    Creates gromacs and amber files for the ligand, the water, and water + ligand
    (in this order)
    .top .pdb .gro .prmtop .inpcrd

    Paramenters
    -------------
    ligand : str or openff.toolkit.topology.Molecule
        the SDF file of the ligand or a Molecule instance
    water_box : object with .positions and .topology in openmm format
    water_forcefields : iterable(str), default=['amber/tip3p_standard.xml']
        The forcefield files for the protein, solvent and ions in xml format
        compatible with simtk.openmm.Forcefield they can be the available ones
        in openmm and openmmforcefields or a custom one in the working directory.
        There is no limit to the number of files.
        Do not give the ligand forcefield here
    ligand_forcefield : str, default=the newest gaff FF available
        The forcefield for the ligand, can be any of
        the supported ones by openmmforcefields.generators, at the moment Gaff and the
        openff initiative ones (es SMIRNOFF) are the only ones supported
    ligand_prefix : str, default='LIG'
        the prefix for all the created ligand files, WILL OVERWRITE PRE EXISTSING ONES!
    water_prefix : str, default='LIG'
        the prefix for all the created water files, WILL OVERWRITE PRE EXISTSING ONES!
    """
    if not isinstance(ligand, Molecule):
        ligand = Molecule.from_file(ligand)

    if water_forcefields is None:
        water_forcefields = ['amber/tip3p_standard.xml']

    ligand_ff_generator = get_ligand_ff_generator(ligand_forcefield=ligand_forcefield,
        ligand=ligand)

    system_kwargs = {'constraints': None, 'rigidWater': False, 'nonbondedMethod': PME}

    forcefield = ForceField(*water_forcefields)

    forcefield.registerTemplateGenerator(ligand_ff_generator.generator)

    # Only water
    water_box.system = forcefield.createSystem(
        water_box.topology, **system_kwargs)

    pmd_structure = parmed.openmm.load_topology(water_box.topology,
    system=water_box.system, xyz=water_box.positions)

    pmd_structure.save(water_prefix + '.top', overwrite=True)
    pmd_structure.save(water_prefix + '.pdb', overwrite=True)
    pmd_structure.save(water_prefix + '.gro', overwrite=True)

    pmd_structure.save(water_prefix + '.prmtop', overwrite=True)
    pmd_structure.save(water_prefix + '.inpcrd', overwrite=True)

    # Only ligand
    ligand_positions, ligand_topology =  ligand.conformers[0], ligand.to_topology().to_openmm()

    ##########
    # Bring the minimum coordinates to 0 0 0
    tmp_coordinates = np.array(ligand_positions / unit.nanometers)

    for i in range(3):

        tmp_coordinates[:, i] = tmp_coordinates[:, i] - np.amin(tmp_coordinates[:, i])

    ligand_positions = tmp_coordinates *  unit.nanometers

    del tmp_coordinates
    ############

    ligand_topology.setUnitCellDimensions([15.0, 15.0, 15.0] * unit.nanometers)

    ligand_system = forcefield.createSystem(ligand_topology, **system_kwargs)

    pmd_structure = parmed.openmm.load_topology(ligand_topology,
    system=ligand_system, xyz=ligand_positions)

    pmd_structure.save(ligand_prefix + '.top', overwrite=True)
    pmd_structure.save(ligand_prefix + '.pdb', overwrite=True)
    pmd_structure.save(ligand_prefix + '.gro', overwrite=True)

    pmd_structure.save(ligand_prefix + '.prmtop', overwrite=True)
    pmd_structure.save(ligand_prefix + '.inpcrd', overwrite=True)

    # Water ligand topology (water forst ligand second)
    ligand_modeller = Modeller(water_box.topology, water_box.positions)
    ligand_modeller.add(ligand_topology, ligand_positions)

    ligand_system = forcefield.createSystem(
        ligand_modeller.topology, **system_kwargs)

    pmd_structure = parmed.openmm.load_topology(ligand_modeller.topology,
        system=ligand_system, xyz=ligand_modeller.positions)

    pmd_structure.save(water_prefix + '_and_' + ligand_prefix + '.top', overwrite=True)
    pmd_structure.save(water_prefix + '_and_' + ligand_prefix + '.prmtop', overwrite=True)

def parametrize_protein_and_ligand(protein_water,
        ligand,
        forcefields=None,
        ligand_forcefield=None,
        complex_prefix='complex',
        ligand_prefix='LIG',
        ligand_resname='LIG',
        water_prefix='only_water',
        box_edge=3.0*unit.nanometers,
        water_model='tip3p',
        ligand_ph=None):

    # Had to do it because sometimes
    # Molecule from openff does not accept
    # openbabel produced SDF files
    if ligand_ph is not None:
        _obabel.convert_and_protonate_file_to_sdf(ligand,
            ligand_prefix + '.sdf', ph=ligand_ph, ligand_resname=ligand_resname)

        ligand = Molecule.from_file(ligand_prefix + '.sdf')

    else:
        ligand = Molecule.from_file(ligand)

    water_box = create_water_box(box_edge=box_edge,
        water_model=water_model)

    parametrize_ligand(ligand=ligand,
        water_box=water_box,
        water_forcefields=forcefields,
        ligand_forcefield=ligand_forcefield,
        ligand_prefix=ligand_prefix,
        water_prefix=water_prefix)

    parametrize_complex(protein_water=protein_water,
        ligand=ligand,
        forcefields=forcefields,
        ligand_forcefield=ligand_forcefield,
        output_prefix=complex_prefix)


# Script used to recognize faces and to trigger a specific spotify playlist upon recognizing a specific person

from peoples_anthem import PeoplesAnthem

if __name__ == "__main__":

    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--model-filepath",
        type=str,
        required=True,
        help="Filepath to the model to use for face recognition",
    )

    args = parser.parse_args()

    peoples_anthem = PeoplesAnthem(model_path=args.model_filepath)
    peoples_anthem.recognize_and_play_spotify()


import signal
import time
import logging

class GracefulKiller:
    kill_now = False
    def __init__(self):
      signal.signal(signal.SIGINT, self.exit_gracefully)
      signal.signal(signal.SIGTERM, self.exit_gracefully)

    def exit_gracefully(self, *args):
        logging.info("Exit signal received. Setting the kill bool to True.")
        self.kill_now = True

if __name__ == '__main__':
    import time
    killer = GracefulKiller()
    while not killer.kill_now:
      time.sleep(1)
      print("doing something in a loop ...")

    print("End of the program. I was killed gracefully :)")


import datetime
import time
import os
import mail_handler
from requests import exceptions, Session
from patreon_parser import ParseSession, CaptchaError
from download_handler import DownloadHandler
from post_manager import PostManager
from cloudscraper import CloudScraper, create_scraper
from dotenv import load_dotenv
from pathvalidate import sanitize_filename

load_dotenv()

post_manager_file = os.path.join(
    os.getenv("DIR"), os.getenv("POST_TRACKER_FILE"))

error_manager_file = os.path.join(
    os.getenv("DIR"), os.getenv("ERROR_TRACKER_FILE"))

class ScrapeSession():

    def __init__(self):
        self.manager = PostManager(post_manager_file)
        self.error_manager = PostManager(error_manager_file)
        self.scraper = create_scraper()
        self.parse_session: ParseSession

    def main(self, days_back_max=7, days_back_range=8):
        if os.getenv("PROXY_URL"):
            proxy_str = ('socks5://%s:%s@%s:%s' % (os.getenv("PROXY_USER"),os.getenv("PROXY_PASS"),os.getenv("PROXY_URL"),os.getenv("PROXY_PORT")))
            self.scraper.proxies.update({'http':proxy_str,'https':proxy_str})

        post_urls = self.handle_mail(days_back_max, days_back_range)

        self.parse_session = ParseSession(self.scraper)

        post_data = self.handle_posts(post_urls)

        self.handle_download(post_data)

    def handle_mail(self, days_back_max, days_back_range):
        # By default grab 1 week of emails
        mails = mail_handler.get_emails(days_back_max, days_back_range)

        mail_details = []
        post_urls = set()

        url_list = []

        for mail in mails:
            mail_obj = mail_handler.process_email(mail)
            if not mail_obj:
                continue
            mail_details.append(mail_obj)

            url_list += mail_handler.get_post_urls(mail_obj)

            post_urls.update(set(url_list))

        return post_urls

    def handle_posts(self, post_urls):

        post_data = []

        all_creators = set()

        for url in post_urls:
            count = 0
            done = False
            while count <= 10:
                count += 1
                try:
                    data = self.parse_session.parse_patreon_url(url)

                    if data is None:
                        done = True
                        break

                    all_creators.add(data["author_short"])

                    # It'll only have an ID if we did proper processing
                    if "id" not in data:
                        done = True
                        break

                    data["url"] = url

                    if self.manager.should_update(data["id"], data["date"]):
                        post_data.append(data)
                    else:
                        print("Skipping, already processed post " + data["title"])

                    done = True
                    break
                except exceptions.ConnectionError:
                    print("Failed to connect to Patreon while parsing URL: " + url)
                    print("Sleeping for %s seconds" % str((count + 1) * 10))
                    time.sleep((count + 1) * 10)
                    continue
                print("If you're here something went wrong")

            # Idk a better way to do this...
            if done:
                continue

            raise ConnectionError("Failed to connect to Patreon")

        permitted_creators = os.getenv('CREATORS').replace(" ", "").split(",")

        print("\nAll creator IDs found: " + ", ".join(all_creators))
        print("Not downloading these creators: " +
            ",".join([c for c in all_creators if c not in permitted_creators]))
        print("")

        return post_data

    def handle_download(self, post_data):
        for post in post_data:
            try:
                self.print_data(post)

                downloader = None

                for i in range(len(post["links"])):
                    downloader = DownloadHandler(self.scraper)
                    downloader.author = post["author"]
                    downloader.post = post["title"]
                    downloader.post_type = post["type"]
                    if downloader.download_url(post["links"][i]):
                        post["links"][i] = [post["links"][i], downloader.post_dir]
                    else:
                        post["links"][i] = [post["links"][i], "Failed"]

                if not (downloader is None):
                    self.create_shortcut({
                        'url': post['url'],
                        'folder': downloader.post_dir,
                        'post': sanitize_filename(post['title'])
                        })

                self.manager.register_post(post["id"], post)
                self.manager.save_data()
            except Exception as e:
                self.error_manager.register_post(post["id"], post)
                self.error_manager.save_data()
                print(e)
                print("Post processing failed! Continuing to process posts...")
                print("Post: " + post["title"])
                print("Dump: " + str(post))

    def print_data(self, post):
        print("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
        print(post["title"] + " by " + post["author"])
        print("Tags: " + ", ".join(post["tags"]))
        print("Posted on: " + post["date"])
        print("URL: " + post["url"])
        print("Type: " + post["type"])
        if(post["links"]):
            print("Files: ")
            for url in post["links"]:
                print("\t" + url)

    def create_shortcut(self, data):
        content = ('''
        <html>
        <head>
        <meta http-equiv="refresh" content="0; url=%s" />
        </head>
        <body>
        </body>
        </html>
        ''' % data['url'])

        with open(os.path.join(data['folder'], data["post"] + '.html'), 'w') as f:
            f.write(content)

if __name__ == "__main__":

    captcha_retry_count = 0

    while True:
        try:
            session = ScrapeSession()
            session.main(1,30)
            break

        except CaptchaError as e:
            if captcha_retry_count >= int(os.getenv("CAPTCHA_RETRY_COUNT")):
                raise e
                break
            captcha_retry_count = captcha_retry_count + 1
            sleep_time = captcha_retry_count * 5

            print("Hit a CAPTCHA, sleeping for %s seconds and retrying" % sleep_time)
            time.sleep(sleep_time)

    # For processing backlog
    #for i in range(48)[::-1]:
    #    d = (i + 1) * 30
    #    print(str(i + 1) + " months back, through: " + (datetime.date.today() -
    #                                                    datetime.timedelta(d)).strftime("%d-%b-%Y") + "--------------------------------")
    #    main(d, 30)


from time import sleep

from flask import render_template, request
from flask_admin import BaseView, expose

from .models import CommercialProposal, MainMetrics, Region
from app import db, app
from .form import CreateCommercialProposal
from .async_views import count_words, generate_tasks_by_id

class AnalyticsView(BaseView):
    @expose('/', methods=['GET', 'POST'])
    def index(self):
        if request.method == 'POST':
            form = CreateCommercialProposal(request.form)
            if form.validate_on_submit():
                cp_id = None
                kw_ids = []
                cp = CommercialProposal(domain=request.form.get('domain'),
                                        description=request.form.get('description'),
                                        main_region=Region.query.get(int(request.form.get('main_region'))))
                db.session.add(cp)
                db.session.flush()
                cp_id = cp.id
                keywords = request.form.get('keywords')
                for kw in [k.strip() for k in keywords.split('\n') if k]:
                    mm = MainMetrics(cp=cp, keyword=kw)
                    db.session.add(mm)
                    db.session.flush()
                    kw_ids.append(mm.id)
                db.session.commit()
                generate_tasks_by_id(cp_id, kw_ids)

        form = CreateCommercialProposal()
        count_words.delay()
        return self.render('create_cp.html', form=form)

@app.route('/')
@app.route('/index')
def index():
    user = {'username': '123'}
    return render_template('create_cp.html', title='title', user=user)

def save_commercial_proposal(domain, main_region, description, keywords):
    cp = CommercialProposal(domain=domain, main_region=main_region, description=description)
    db.session.add(cp)
    db.session.flush()
    db.session.refresh(cp)

    for kw in keywords:
        mm = MainMetrics(cp=cp, keyword=kw)
        db.session.add(mm)

    db.session.commit()


import datetime
import enum
import logging
import typing

from flask_babel import gettext
from flask_babel import lazy_gettext
from flask_sqlalchemy import BaseQuery
from sqlalchemy import and_
from sqlalchemy import func
from sqlalchemy.orm import joinedload
from twilio.base.exceptions import TwilioRestException

from airq import config
from airq.config import db
from airq.lib.client_preferences import IntegerChoicesPreference
from airq.lib.client_preferences import IntegerPreference
from airq.lib.client_preferences import StringChoicesPreference
from airq.lib.clock import now
from airq.lib.clock import timestamp
from airq.lib.readings import ConversionFactor
from airq.lib.readings import Pm25
from airq.lib.readings import Readings
from airq.lib.sms import coerce_phone_number
from airq.lib.twilio import send_sms
from airq.lib.twilio import TwilioErrorCode
from airq.models.events import Event
from airq.models.events import EventType
from airq.models.zipcodes import Zipcode

logger = logging.getLogger(__name__)

class ClientIdentifierType(enum.Enum):
    PHONE_NUMBER = 1
    IP = 2

class ClientQuery(BaseQuery):

    #
    # Fetchers
    #

    def get_or_create(
        self, identifier: str, type_code: ClientIdentifierType, locale: str
    ) -> typing.Tuple["Client", bool]:
        client = self.filter_by(identifier=identifier, type_code=type_code).first()
        if not client:
            client = Client(
                identifier=identifier,
                type_code=type_code,
                last_activity_at=timestamp(),
                locale=locale,
            )
            db.session.add(client)
            db.session.commit()
            was_created = True
        else:
            was_created = False
        return client, was_created

    def get_by_phone_number(self, phone_number: str) -> typing.Optional["Client"]:
        phone_number = coerce_phone_number(phone_number)
        return self.filter_phones().filter_by(identifier=phone_number).first()

    #
    # Filters
    #

    def filter_phones(self) -> "ClientQuery":
        return self.filter(Client.type_code == ClientIdentifierType.PHONE_NUMBER)

    def filter_inactive_since(
        self, timestamp: float, include_unsubscribed: bool
    ) -> "ClientQuery":
        query = self.filter(Client.last_activity_at < timestamp).filter(
            Client.last_alert_sent_at < timestamp
        )
        if not config.DEV:
            # Don't bother in dev since it makes testing harder.
            # We want to be able to look at the logs to confirm this sent even when not using ngrok.
            query = query.filter_phones()
        if not include_unsubscribed:
            query = query.filter(Client.alerts_disabled_at == 0).filter(
                Client.zipcode_id.isnot(None)
            )
        return query

    def filter_eligible_for_sending(self) -> "ClientQuery":
        return (
            self.filter_phones()
            .options(joinedload(Client.zipcode))
            .filter(Client.alerts_disabled_at == 0)
            .filter(Client.zipcode_id.isnot(None))
        )

    def filter_eligible_for_share_requests(self) -> "ClientQuery":
        subq = (
            Event.query.filter(Event.type_code == EventType.SHARE_REQUEST)
            .filter(Event.timestamp > Client.get_share_request_cutoff())
            .with_entities(Event.client_id, Event.timestamp)
            .subquery()
        )
        share_window_start, share_window_end = Client.get_share_window()
        return (
            self.filter_phones()
            .outerjoin(subq, and_(subq.c.client_id == Client.id))
            .filter(subq.c.timestamp == None)
            # Client must have signed up more than 7 days ago
            .filter(Client.created_at < now() - datetime.timedelta(days=7))
            .filter(Client.last_alert_sent_at > share_window_start)
            .filter(Client.last_alert_sent_at < share_window_end)
        )

    #
    # Stats
    #

    def get_total_num_sends(self) -> int:
        return (
            self.filter_phones()
            .with_entities(func.sum(Client.num_alerts_sent))
            .scalar()
            or 0
        )

    def get_total_new(self) -> int:
        """Number of new clients in the last day"""
        return (
            self.filter_phones()
            .filter(func.timezone("PST", Client.created_at) > now().date())
            .with_entities(func.count(Client.id))
            .scalar()
            or 0
        )

    def get_total_num_subscriptions(self) -> int:
        return (
            self.filter_phones()
            .filter(Client.alerts_disabled_at == 0)
            .filter(Client.zipcode_id.isnot(None))
            .count()
        )

    def get_activity_counts(self):
        windows = [1, 2, 3, 4, 5, 6, 7, 30]
        curr_time = timestamp()
        counts = {window: 0 for window in windows}
        for client in self.filter_phones().all():
            for window in windows:
                ts = curr_time - (window * 24 * 60 * 60)
                if client.last_activity_at > ts or client.last_alert_sent_at > ts:
                    counts[window] += 1
        return counts

class Client(db.Model):  # type: ignore
    __tablename__ = "clients"

    query_class = ClientQuery

    id = db.Column(db.Integer(), primary_key=True)
    identifier = db.Column(db.String(), nullable=False)
    type_code = db.Column(db.Enum(ClientIdentifierType), nullable=False)
    created_at = db.Column(
        db.TIMESTAMP(timezone=True), default=now, index=True, nullable=False
    )
    last_activity_at = db.Column(
        db.Integer(), nullable=False, index=True, server_default="0"
    )

    zipcode_id = db.Column(
        db.Integer(),
        db.ForeignKey("zipcodes.id", name="clients_zipcode_id_fkey"),
        nullable=True,
    )

    last_pm25 = db.Column(db.Float(), nullable=True)
    last_humidity = db.Column(db.Float(), nullable=True)
    last_pm_cf_1 = db.Column(db.Float(), nullable=True)

    last_alert_sent_at = db.Column(
        db.Integer(), nullable=False, index=True, server_default="0"
    )
    alerts_disabled_at = db.Column(
        db.Integer(), nullable=False, index=True, server_default="0"
    )
    num_alerts_sent = db.Column(db.Integer(), nullable=False, server_default="0")
    locale = db.Column(db.String(), nullable=False, server_default="en")

    preferences = db.Column(db.JSON(), nullable=True)

    zipcode = db.relationship("Zipcode")
    events = db.relationship("Event")

    __table_args__ = (
        db.Index(
            "_client_identifier_type_code",
            "identifier",
            "type_code",
            unique=True,
        ),
    )

    def __repr__(self) -> str:
        return f"<Client {self.identifier}>"

    # Send alerts between 8 AM and 9 PM.
    SEND_WINDOW_HOURS = (8, 21)

    # Time after which the client shouldn't treat an event as "recent"
    # and therefore shouldn't include it in its state
    EVENT_RESPONSE_TIME = datetime.timedelta(hours=1)

    @classmethod
    def get_share_window(self) -> typing.Tuple[int, int]:
        ts = timestamp()
        share_window_start = ts - 60 * 15
        share_window_end = ts - 60 * 5
        return share_window_start, share_window_end

    @classmethod
    def get_share_request_cutoff(self) -> datetime.datetime:
        return now() - datetime.timedelta(days=60)

    #
    # Presence
    #

    def mark_seen(self, locale: str):
        self.last_activity_at = timestamp()
        if self.locale != locale:
            self.locale = locale
        db.session.commit()

    #
    # Prefs
    #

    alert_frequency = IntegerPreference(
        display_name=lazy_gettext("Alert Frequency"),
        description=lazy_gettext(
            "By default, Hazebot sends alerts at most every 2 hours."
        ),
        default=2,
        min_value=0,
        max_value=24,
    )

    alert_threshold: IntegerChoicesPreference[Pm25] = IntegerChoicesPreference(
        display_name=lazy_gettext("Alert Threshold"),
        description=lazy_gettext(
            "AQI category below which Hazebot won't send alerts.\n"
            "For example, if you set this to MODERATE, "
            "Hazebot won't send alerts when AQI transitions from GOOD to MODERATE or from MODERATE to GOOD."
        ),
        default=Pm25.MODERATE,
        choices=Pm25,
    )

    conversion_factor: StringChoicesPreference[
        ConversionFactor
    ] = StringChoicesPreference(
        display_name=lazy_gettext("Conversion Factor"),
        description=lazy_gettext(
            "Conversion factor to use when calculating AQI. "
            "For more details, see https://www2.purpleair.com/community/faq#hc-should-i-use-the-conversion-factors-on-the-purpleair-map-1."
        ),
        default=ConversionFactor.NONE,
        choices=ConversionFactor,
    )

    #
    # AQI
    #

    def get_last_readings(self) -> Readings:
        return Readings(
            pm25=self.last_pm25, pm_cf_1=self.last_pm_cf_1, humidity=self.last_humidity
        )

    def get_current_aqi(self) -> int:
        """Current AQI for this client."""
        return self.zipcode.get_aqi(self.conversion_factor)

    def get_current_pm25(self) -> float:
        """Current Pm25 for this client as determined by its chosen strategy."""
        return self.zipcode.get_pm25(self.conversion_factor)

    def get_current_pm25_level(self) -> Pm25:
        """Current Pm25 level for this client as determined by its chosen strategy."""
        return self.zipcode.get_pm25_level(self.conversion_factor)

    def get_last_aqi(self) -> int:
        """Last AQI at which an alert was sent to this client."""
        return self.get_last_readings().get_aqi(self.conversion_factor)

    def get_last_pm25(self) -> float:
        """Last Pm25 for this client as determined by its chosen strategy."""
        return self.get_last_readings().get_pm25(self.conversion_factor)

    def get_last_pm25_level(self) -> Pm25:
        """Last Pm25 level for this client as determined by its chosen strategy."""
        return self.get_last_readings().get_pm25_level(self.conversion_factor)

    def get_recommendations(self, num_desired: int) -> typing.List[Zipcode]:
        """Recommended zipcodes for this client."""
        return self.zipcode.get_recommendations(num_desired, self.conversion_factor)

    #
    # Alerting
    #

    @property
    def is_enabled_for_alerts(self) -> bool:
        return bool(self.zipcode_id and not self.alerts_disabled_at)

    def update_subscription(self, zipcode: Zipcode) -> bool:
        self.last_pm25 = zipcode.pm25
        self.last_humidity = zipcode.humidity
        self.last_pm_cf_1 = zipcode.pm_cf_1

        curr_zipcode_id = self.zipcode_id
        self.zipcode_id = zipcode.id

        db.session.commit()

        return curr_zipcode_id != self.zipcode_id

    def disable_alerts(self, is_automatic=False):
        if self.alerts_disabled_at == 0:
            self.alerts_disabled_at = timestamp()
            db.session.commit()
            self.log_event(
                EventType.UNSUBSCRIBE_AUTO if is_automatic else EventType.UNSUBSCRIBE,
                zipcode=self.zipcode.zipcode,
            )

    def enable_alerts(self):
        if self.alerts_disabled_at > 0:
            self.last_pm25 = self.zipcode.pm25
            self.last_humidity = self.zipcode.humidity
            self.last_pm_cf_1 = self.zipcode.pm_cf_1
            self.alerts_disabled_at = 0
            db.session.commit()
            self.log_event(EventType.RESUBSCRIBE, zipcode=self.zipcode.zipcode)

    @property
    def is_in_send_window(self) -> bool:
        if self.zipcode_id is None:
            return False
        # Timezone can be null since our data is incomplete.
        timezone = self.zipcode.timezone or "America/Los_Angeles"
        dt = now(timezone=timezone)
        send_start, send_end = self.SEND_WINDOW_HOURS
        return send_start <= dt.hour < send_end

    def send_message(self, message: str, media: typing.Optional[str] = None) -> bool:
        if self.type_code == ClientIdentifierType.PHONE_NUMBER:
            try:
                send_sms(message, self.identifier, self.locale, media=media)
            except TwilioRestException as e:
                code = TwilioErrorCode.from_exc(e)
                if code:
                    logger.warning(
                        "Disabling alerts for recipient %s: %s",
                        self,
                        code.name,
                    )
                    self.disable_alerts(is_automatic=True)
                    return False
                else:
                    raise
        else:
            # Other clients types don't yet support message sending.
            logger.info("Not messaging client %s: %s", self.id, message)

        return True

    def maybe_notify(self) -> bool:
        if not self.is_in_send_window:
            return False

        alert_frequency = self.alert_frequency * 60 * 60
        if self.last_alert_sent_at >= timestamp() - alert_frequency:
            return False

        curr_pm25 = self.get_current_pm25()
        curr_aqi_level = self.get_current_pm25_level()
        curr_aqi = self.get_current_aqi()

        # Only send if the pm25 changed a level since the last time we sent this alert.
        last_aqi_level = self.get_last_pm25_level()
        if curr_aqi_level == last_aqi_level:
            return False

        alert_threshold = self.alert_threshold

        # If the current AQI is below the alert threshold, and the last AQI was
        # at the alerting threshold or below, we won't send the alert.
        # For example, if the user sets their threshold at UNHEALTHY, they won't
        # be notified when the AQI transitions from UNHEALTHY to UNHEALHY FOR SENSITIVE GROUPS
        # or from UNHEALTHY to MODERATE, but will be notified if the AQI transitions from
        # VERY UNHEALTHY to UNHEALTHY.
        if curr_aqi_level < alert_threshold and last_aqi_level <= alert_threshold:
            return False

        # If the current AQI is at the alert threshold but the last AQI was under it,
        # don't send the alert because we haven't crossed the threshold yet.
        if curr_aqi_level == alert_threshold and last_aqi_level < alert_threshold:
            return False

        # Do not alert clients who received an alert recently unless AQI has changed markedly.
        was_alerted_recently = self.last_alert_sent_at > timestamp() - (60 * 60 * 6)
        last_aqi = self.get_last_aqi()
        if was_alerted_recently and abs(curr_aqi - last_aqi) < 50:
            return False

        message = gettext(
            'AQI is now %(curr_aqi)s in zipcode %(zipcode)s (level: %(curr_aqi_level)s).\n\nReply "M" for Menu or "E" to end alerts.',
            zipcode=self.zipcode.zipcode,
            curr_aqi_level=curr_aqi_level.display,
            curr_aqi=curr_aqi,
        )
        if not self.send_message(message):
            return False

        self.last_alert_sent_at = timestamp()
        self.last_pm25 = self.zipcode.pm25
        self.last_pm_cf_1 = self.zipcode.pm_cf_1
        self.last_humidity = self.zipcode.humidity
        self.num_alerts_sent += 1
        db.session.commit()

        self.log_event(EventType.ALERT, zipcode=self.zipcode.zipcode, pm25=curr_pm25)

        return True

    def request_share(self) -> bool:
        if not self.is_in_send_window:
            return False

        if self.created_at >= now() - datetime.timedelta(days=7):
            return False

        # Double check that we're all good to go
        share_window_start, share_window_end = self.get_share_window()
        if (
            not self.last_alert_sent_at
            or self.last_alert_sent_at <= share_window_start
            or self.last_alert_sent_at >= share_window_end
        ):
            return False

        # Check the last share request we sent was a long time ago
        share_request = self.get_last_share_request()
        if share_request and share_request.timestamp >= self.get_share_request_cutoff():
            return False

        message = gettext(
            "Has Hazebot been helpful? We’re looking for ways to grow and improve, and we’d love your help. Save our contact and share Hazebot with a friend, or text “feedback” to send feedback."
        )
        if not self.send_message(message):
            return False

        Event.query.create(self.id, EventType.SHARE_REQUEST)
        return True

    #
    # Events
    #

    def log_event(self, event_type: EventType, **event_data: typing.Any) -> Event:
        return Event.query.create(self.id, event_type, **event_data)

    def _get_last_event_by_type(self, event_type: EventType) -> typing.Optional[Event]:
        return (
            Event.query.filter(Event.client_id == self.id)
            .filter(Event.type_code == event_type)
            .order_by(Event.timestamp.desc())
            .first()
        )

    def get_last_alert(self) -> typing.Optional[Event]:
        return self._get_last_event_by_type(EventType.ALERT)

    def get_last_share_request(self) -> typing.Optional[Event]:
        return self._get_last_event_by_type(EventType.SHARE_REQUEST)

    def get_last_client_event(self) -> typing.Optional[Event]:
        return (
            Event.query.filter(Event.client_id == self.id)
            .filter(
                ~Event.type_code.in_([EventType.ALERT, EventType.SHARE_REQUEST])
            )  # filter for only inbound events
            .order_by(Event.timestamp.desc())
            .first()
        )

    def get_last_client_event_type(self) -> typing.Optional[EventType]:
        last_event = self.get_last_client_event()
        if last_event:
            return EventType(last_event.type_code)
        return None

    def should_accept_feedback(self) -> bool:
        # First check if the most recent event is a feedback begin or unsub event
        if self.has_recent_last_events_of_type(
            {
                EventType.FEEDBACK_BEGIN,
                EventType.UNSUBSCRIBE,
            }
        ):
            return True

        # Then check if we have an outstanding feedback request
        cutoff = now() - datetime.timedelta(days=4)
        feedback_request_event = self.get_event_of_type_after(
            EventType.FEEDBACK_REQUEST, cutoff
        )
        # Check whether feedback was responded to
        if feedback_request_event and not self.get_event_of_type_after(
            EventType.FEEDBACK_RECEIVED, feedback_request_event.timestamp
        ):
            return True

        return False

    def get_event_of_type_after(
        self, event_type: EventType, cutoff: datetime.datetime
    ) -> typing.Optional[Event]:
        return (
            Event.query.filter(Event.client_id == self.id)
            .filter(Event.timestamp > cutoff)
            .filter(Event.type_code == event_type)
            .first()
        )

    def has_recent_last_events_of_type(
        self, event_types: typing.Set[EventType]
    ) -> bool:
        return any(self.has_recent_last_event_of_type(e) for e in event_types)

    def has_recent_last_event_of_type(self, event_type: EventType) -> bool:
        last_event = self.get_last_client_event()
        return bool(
            last_event
            and last_event.type_code == event_type
            and now() - last_event.timestamp < Client.EVENT_RESPONSE_TIME
        )