from sys import excepthook
from selenium.webdriver import Edge
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.keys import Keys
import time
import os
import re
import openpyxl
from openpyxl import load_workbook

#GLOBAL VAR---------------------------------------------------------------------------
SLEEPTIME = 20
SITE = "https://bianalytics.rci.rogers.com/t/BI/views/DailyPostpaidReportNewSubscriberSource/Postpaid?:embed=yes#1"
dash = "-------------------"
DATE = ""
SEGMENT_NAME = ""
PROVINCE_NAME = ""
BRAND_NAME = ""
BRAND_DICT = dict([
    (0,"Rogers"),
    (1,"Fido"),
    (2,"Cityfone")
])

SEGMENT_DICT = dict([
    (0,"Consumer"),
    (1,"Rogers_for_Business")
])

PROVINCE_DICT = dict([
    (0,"BC"),
    (1,"AB"),
    (2,"SK"),
    (3,"MB"),
    (4,"ON"),
    (5,"PQ"),
    (6,"NB"),
    (7,"NS"),
    (8,"PE"),
    (9,"NL")
])
MONTH_DICT_LEAP = dict([
    (1,"31"),
    (2,"29"),
    (3,"31"),
    (4,"30"),
    (5,"31"),
    (6,"30"),
    (7,"31"),
    (8,"31"),
    (9,"30"),
    (10,"31"),
    (11,"30"),
    (12,"31")
])
MONTH_DICT = dict([
    (1,"31"),
    (2,"28"),
    (3,"31"),
    (4,"30"),
    (5,"31"),
    (6,"30"),
    (7,"31"),
    (8,"31"),
    (9,"30"),
    (10,"31"),
    (11,"30"),
    (12,"31")
])
COUNT = 2
DOWNLOAD_DIRECTORY = ""
#ROOT = "C:\\Users\\Steven.Shan\\mypython\\tableau\\web scrape\\"
ROOT = "C:\\Users\\Steven.Shan\\mypython\\tableau\\postpaid\\"
#ROOT = "C:\\Users\\Wei.Li1\\Documents\\Postpaid\\"

#--------------------------------------------------------------------------------------------------------------------------
#initialize webdriver
options = webdriver.EdgeOptions()
options.use_chromium = True
options.add_argument("--start-maximized")
# options.add_argument("--headless")
# options.add_argument("disable-gpu")

#options.add_argument("--log-level=2")

#helpers:

#check whether the xpath exist in current webpage
def check_exists_by_xpath(xpath,web):
    try:
        web.find_element(By.XPATH, xpath)
    except NoSuchElementException:
        return False
    return True

#download and add downloaded info to the map excel sheet
def download(web):
    global PROVINCE_NAME
    global COUNT
    time.sleep(SLEEPTIME)
    el = WebDriverWait(web,300).until(EC.presence_of_element_located((By.XPATH, '//*[@id="download-ToolbarButton"]')))
    el.click()
    time.sleep(SLEEPTIME)
    el = WebDriverWait(web,300).until(EC.presence_of_element_located((By.XPATH, '//*[@id="DownloadDialog-Dialog-Body-Id"]/div/fieldset/button[3]')))
    el.click()
    time.sleep(SLEEPTIME)
    el = WebDriverWait(web,300).until(EC.presence_of_element_located((By.XPATH, '//*[@id="export-crosstab-options-dialog-Dialog-BodyWrapper-Dialog-Body-Id"]/div/div[1]/div[2]/div/div/div[2]/div')))
    el.click()
    time.sleep(SLEEPTIME)
    download = web.find_element(By.XPATH, '//*[@id="export-crosstab-options-dialog-Dialog-BodyWrapper-Dialog-Body-Id"]/div/div[3]/button')
    # download = WebDriverWait(web,300).until(EC.presence_of_element_located((By.XPATH, '//*[@id="export-crosstab-options-dialog-Dialog-BodyWrapper-Dialog-Body-Id"]/div/div[3]/button')))
    download.click()
    time.sleep(SLEEPTIME)
    month = str(re.match('^[^/]*', DATE).group(0))
    year = DATE[-4:]

    try:

        os.rename(DOWNLOAD_DIRECTORY+"\\"+"Closing SC.xlsx",DOWNLOAD_DIRECTORY+"\\"+month+"_"+year+"_"+SEGMENT_NAME+"_"+PROVINCE_NAME+"_"+BRAND_NAME+".xlsx")

        print(dash+"creating map"+dash)

        wb = load_workbook(filename= DOWNLOAD_DIRECTORY+"\\MAP.xlsx")
        sheet = wb.active
        sheet["A"+str(COUNT)] = COUNT-1
        sheet["B"+str(COUNT)] = month+"_"+year+"_"+SEGMENT_NAME+"_"+PROVINCE_NAME+"_"+BRAND_NAME
        sheet["C"+str(COUNT)] = SEGMENT_NAME
        sheet["D"+str(COUNT)] = PROVINCE_NAME
        sheet["E"+str(COUNT)] = BRAND_NAME
        wb.save(filename = DOWNLOAD_DIRECTORY+"\\MAP.xlsx")

        COUNT = COUNT + 1
    except:
        COUNT=COUNT

#start SCRAPE!----------------------------------------------------------------------------------------------

#expand segment filter
def expand_Segment_filter(web):
    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '//*[@id="tableau_base_widget_LegacyCategoricalQuickFilter_1"]/div/div[3]/span')))
    el.click()
    time.sleep(SLEEPTIME)
#click segment all
def click_Segment_all(web):
    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,Segment_113277616459342307234_834514642350544244_(All)"]/div[2]/input')))
    el.click()
    time.sleep(SLEEPTIME)

#traverse the province section and download
#special cases:
# 1. might not exist any province, download button does not exist
# 2. only one province exist, will influence how clicking convention work
def traverse_Segment(web):
    global DATE
    global SEGMENT_NAME
    global PROVINCE_NAME
    global BRAND_NAME
    global DOWNLOAD_DIRECTORY
    time.sleep(SLEEPTIME)
    expand_Segment_filter(web)
    if check_exists_by_xpath('//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,Segment_113277616459342307234_834514642350544244_0"]/div[2]/input',web):
        if check_exists_by_xpath('//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,Segment_113277616459342307234_834514642350544244_1"]/div[2]/input' , web):
            el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
            el.click()
            time.sleep(SLEEPTIME)
            SEGMENT_NAME = "ALL"
            print(dash+"DOWNLOADING"+DATE+"_"+SEGMENT_NAME+BRAND_NAME+PROVINCE_NAME+dash)
            download(web)
            i=0
            while i<2:
                expand_Segment_filter(web)
                if check_exists_by_xpath('//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,Segment_113277616459342307234_834514642350544244_'+str(i)+'"]/div[2]/input' ,web):
                    #if i==0 no need to click twice
                    if i>0:
                        click_Segment_all(web)
                    #for all click at least once to reset list to empty
                    click_Segment_all(web)
                    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,Segment_113277616459342307234_834514642350544244_'+str(i)+'"]/div[2]/input')))
                    el.click()
                    time.sleep(SLEEPTIME)
                    SEGMENT_NAME = web.find_element(By.XPATH, '//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,Segment_113277616459342307234_834514642350544244_'+str(i)+'"]/div[2]/a').get_attribute("title")
                    time.sleep(SLEEPTIME)
                    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
                    el.click()
                    # web.refresh()
                    time.sleep(SLEEPTIME)
                    print(dash+"DOWNLOADING"+DATE+"_"+SEGMENT_NAME+"_"+BRAND_NAME+"_"+PROVINCE_NAME+dash)
                    download(web)
                    i+=1
                else:
                    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
                    el.click()
                    # web.refresh()
                    time.sleep(SLEEPTIME)
                    break
            #get back to all
            expand_Segment_filter(web)
            #select all
            click_Segment_all(web)
            el = WebDriverWait(web,50).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
            el.click()
            time.sleep(SLEEPTIME)
        else:
            print(dash+"only one sheet is available(special case)"+dash)
            SEGMENT_NAME = web.find_element(By.XPATH, '//*[@id="FI_federated.07uceah05f3pzg12vx7ye10ec8da,none:PROVINCE_NM:nk1863049714700099538_14391167526842317961_0"]/div[2]/a').get_attribute("title")
            print(dash+"DOWNLOADING"+DATE+"_"+SEGMENT_NAME+"_"+BRAND_NAME+"_"+PROVINCE_NAME+dash)
            el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
            el.click()
            time.sleep(SLEEPTIME)
            download(web)

    else:
        print(dash+"nothing to download"+dash)
        el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
        el.click()
        time.sleep(SLEEPTIME)

#--------------------------------------------------------------------------------------------------------------------------
#expand Province filter
def expand_Province_filter(web):
    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '//*[@id="tableau_base_widget_LegacyCategoricalQuickFilter_3"]/div/div[3]/span')))
    el.click()
    time.sleep(SLEEPTIME)
#click segment all
def click_Province_all(web):
    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,none:PROVINCE:nk13277616459342307234_834514642350544244_(All)"]/div[2]/input')))
    el.click()
    time.sleep(SLEEPTIME)

#traverse brand section and call traverse province for each brand
#all the brands will always exist
def traverse_Province(web):
    global DATE
    global SEGMENT_NAME
    global PROVINCE_NAME
    global BRAND_NAME
    global DOWNLOAD_DIRECTORY
    time.sleep(SLEEPTIME)
    PROVINCE_NAME = "ALL"
    print(dash+"Scraping"+DATE+"_"+PROVINCE_NAME+"_"+BRAND_NAME+dash)
    traverse_Segment(web)
    j=0
    while j < 10:
        PROVINCE_NAME = PROVINCE_DICT[j]
        print(dash+"Scraping"+DATE+"_"+PROVINCE_NAME+"_"+BRAND_NAME+dash)
        expand_Province_filter(web)
        if j>0:
            click_Province_all(web)
        click_Province_all(web)
        el = WebDriverWait(web,50).until(EC.presence_of_element_located((By.XPATH, '//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,none:PROVINCE:nk13277616459342307234_834514642350544244_'+str(j)+'"]/div[2]/input')))
        el.click()
        time.sleep(SLEEPTIME)
        el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
        el.click()
        time.sleep(SLEEPTIME)
        traverse_Segment(web)
        j+=1
    #get back to all
    expand_Province_filter(web)
    #select all
    click_Province_all(web)
    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
    el.click()
    time.sleep(SLEEPTIME)

#--------------------------------------------------------------------------------------------------------------
#type date in the box
def input_date(web):
    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '//*[@id="typein_[Parameters].[Report Date Parameter]"]/span[1]/input')))
    el.send_keys(Keys.CONTROL + "a")
    el.send_keys(Keys.DELETE)
    time.sleep(SLEEPTIME)
    el.send_keys(DATE)
    time.sleep(SLEEPTIME)
#expand brand filter
def expand_brand_filter(web):
    el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '//*[@id="tableau_base_widget_LegacyCategoricalQuickFilter_5"]/div/div[3]/span')))
    el.click()
    time.sleep(SLEEPTIME)
#click brand all button
def click_brand_all(web):
    el = WebDriverWait(web,50).until(EC.presence_of_element_located((By.XPATH, '//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,Brand13277616459342307234_834514642350544244_(All)"]/div[2]/input')))
    el.click()
    time.sleep(SLEEPTIME)

#traverse segment section will call traverse brand for each segment
def traverse_brand():
    global DATE
    global SEGMENT_NAME
    global PROVINCE_NAME
    global BRAND_NAME
    global DOWNLOAD_DIRECTORY
    time.sleep(SLEEPTIME)
    #print(dash+"Scraping Segment all")
    web = Edge(options=options)
    web.get(SITE)
    time.sleep(15)
    input_date(web)
    print(dash+"Scraping"+DATE+dash)
    time.sleep(SLEEPTIME)
    BRAND_NAME = "ALL"
    print(dash+"Scraping"+DATE+"_"+BRAND_NAME+dash)
    traverse_Province(web)
    k=0
    while k<3:
        web.quit()
        web = Edge(options=options)
        web.get(SITE)
        time.sleep(15)
        input_date(web)
        print(dash+"Scraping"+DATE+dash)
        expand_brand_filter(web)
        click_brand_all(web)
        el = WebDriverWait(web,20).until(EC.presence_of_element_located((By.XPATH, '//*[@id="FI_federated.0dn4toz0kzraq91gkruzo1k7pvrl,Brand13277616459342307234_834514642350544244_'+str(k)+'"]/div[2]/input')))
        el.click()
        time.sleep(SLEEPTIME)
        BRAND_NAME = BRAND_DICT[k]
        el = WebDriverWait(web,30).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
        el.click()
        time.sleep(SLEEPTIME)
        print(dash+"Scraping"+DATE+"_"+SEGMENT_NAME+dash)
        traverse_Province(web)
        k+=1

    #get back to all
    expand_brand_filter(web)
    #select all
    click_brand_all(web)
    el = WebDriverWait(web,20).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[5]')))
    el.click()
    time.sleep(SLEEPTIME)
    print(dash+DATE+" is successfully scrapped"+dash)

#---------------------------------------------------------------------------------------------
#user interface

print(dash+"Enter the year you want to download: "+dash)
year = str(input())
print(dash+"Enter the month you want to download or -1 to download the entire year:"+dash)
month = int(input())

if month != -1:
    if int(year)%4==0:
        DATE = str(month)+"/"+MONTH_DICT_LEAP[month]+"/"+year
    else:
        DATE = str(month)+"/"+MONTH_DICT[month]+"/"+year
    COUNT = 2
    DOWNLOAD_DIRECTORY = ROOT+year+"_"+str(month)
    os.makedirs(DOWNLOAD_DIRECTORY)
    print(dash+"folder should be created"+dash)
    print(dash+"creating map"+dash)
    wb = openpyxl.Workbook()
    wb.save(DOWNLOAD_DIRECTORY+"\\MAP.xlsx")
    wb = load_workbook(filename= DOWNLOAD_DIRECTORY+"\\MAP.xlsx")
    sheet = wb.active
    sheet["B1"] = "Selection#"
    sheet["C1"] = "Segment"
    sheet["D1"] = "Province"
    sheet["E1"] = "Brand"
    wb.save(filename = DOWNLOAD_DIRECTORY+"\\MAP.xlsx")
    options.add_experimental_option("prefs", {
        "download.default_directory": DOWNLOAD_DIRECTORY
    })
    traverse_brand()
    print(dash+"successfully_scrapped   " +str(DATE)+"   "+dash)
else:
    DOWNLOAD_DIRECTORY = ROOT+year
    os.makedirs(DOWNLOAD_DIRECTORY)
    print(dash+"folder should be created"+dash)
    print(dash+"creating map"+dash)
    wb = openpyxl.Workbook()
    wb.save(DOWNLOAD_DIRECTORY+"\\MAP.xlsx")
    wb = load_workbook(filename= DOWNLOAD_DIRECTORY+"\\MAP.xlsx")
    sheet = wb.active
    sheet["B1"] = "Selection#"
    sheet["C1"] = "Segment"
    sheet["D1"] = "Province"
    sheet["E1"] = "Brand"
    wb.save(filename = DOWNLOAD_DIRECTORY+"\\MAP.xlsx")
    options.add_experimental_option("prefs", {
        "download.default_directory": DOWNLOAD_DIRECTORY
    })
    COUNT = 2
    x = 1
    while x < 13:
        if year%4==0:
            DATE = str(month)+"/"+MONTH_DICT_LEAP[x]+"/"+year
        else:
            DATE = str(month)+"/"+MONTH_DICT[x]+"/"+year
        traverse_brand()
        print(dash+"successfully_scrapped   " +str(DATE)+"   "+dash)
        x=x+1

print(dash+"Have A Great Day!"+dash)


#!/usr/bin/python3
# coding: utf-8

list1 = ['Jeff','John','Mark','Lily']
list2 = [1,2,3,4,5,6,7,8,9]

for i in list1:
    if i == 'John':
        print(i)

for x in list2:
    if x == 8:
        print(x)


import matplotlib.pyplot as plt
import numpy as np
import qtt
import qtt.algorithms.allxy
import qtt.algorithms.functions
import scipy.constants
from qcodes.data.data_set import DataSet
from qtt.algorithms.gatesweep import analyseGateSweep
from qtt.algorithms.random_telegraph_signal import _plot_rts_histogram, fit_double_gaussian, tunnelrates_RTS
from qtt.algorithms.tunneling import fit_pol_all, plot_polarization_fit, polmod_all_2slopes
from qtt.data import default_setpoint_array

def _parse_1d_dataset(dataset: DataSet) -> tuple:
    y_data = np.array(dataset.default_parameter_array())
    x_data = np.array(qtt.data.default_setpoint_array(dataset))
    return x_data, y_data

def analyse_coulomb(dataset: DataSet, fig: int = 1) -> dict:
    """Analyse dataset with Coulomb peaks"""
    parameters = {"typicalhalfwidth": 2}
    qtt.algorithms.coulomb.analyseCoulombPeaks(dataset, fig=fig, verbose=0, parameters=parameters)
    plt.legend()
    return {}

def analyse_pinchoff(dataset: DataSet, fig: int = 1) -> dict:
    result = qtt.algorithms.gatesweep.analyseGateSweep(dataset, fig=fig)
    plt.legend()
    return result

def analyse_allxy(dataset: DataSet, fig: int = 1) -> dict:
    result = qtt.algorithms.allxy.fit_allxy(dataset)
    qtt.algorithms.allxy.plot_allxy(dataset, result, fig=fig)
    plt.subplots_adjust(bottom=0.15)
    return result

def analyse_time_rabi(dataset: DataSet, fig: int = 1) -> dict:
    x_data, y_data = _parse_1d_dataset(dataset)
    fit_parameters, xx = qtt.algorithms.functions.fit_gauss_ramsey(x_data, y_data)
    qtt.algorithms.functions.plot_gauss_ramsey_fit(x_data, y_data, fit_parameters, fig=fig)
    plt.title("Time Rabi")
    return {"fit_parameters": fit_parameters}

def analyse_time_ramsey(dataset: DataSet, fig: int = 1) -> dict:
    x_data, y_data = _parse_1d_dataset(dataset)
    fit_parameters, xx = qtt.algorithms.functions.fit_gauss_ramsey(x_data, y_data)
    qtt.algorithms.functions.plot_gauss_ramsey_fit(x_data, y_data, fit_parameters, fig=fig)
    return {"fit_parameters": fit_parameters}

def analyse_polarization_line(dataset: DataSet, fig: int = 1, verbose=0) -> dict:
    """Analyse dataset with polarization line"""
    if verbose:
        print("analyse_polarization_line: dataset: %s" % dataset.location)
    signal = dataset.default_parameter_array()
    delta = default_setpoint_array(dataset, signal.name)

    lever_arm = 80
    delta_uev = np.array(delta) * lever_arm
    signal = qtt.algorithms.generic.smoothImage(signal)

    kb = scipy.constants.physical_constants["Boltzmann constant in eV/K"][0] * 1e6
    kT = 75e-3 * kb  # effective electron temperature in ueV

    par_fit, initial_parameters, results = fit_pol_all(delta_uev, signal, kT)
    plot_polarization_fit(delta_uev, signal, results, fig)
    return {}

def analyse_RTS(dataset: DataSet, fig: int = 1) -> dict:
    time = default_setpoint_array(dataset)
    rtsdata = np.array(dataset.default_parameter_array())
    num_bins = 40
    counts, bins = np.histogram(rtsdata, bins=num_bins)
    bincentres = np.array([(bins[i] + bins[i + 1]) / 2 for i in range(0, len(bins) - 1)])
    par_fit, result_dict = fit_double_gaussian(bincentres, counts)
    split = result_dict["split"]

    plt.figure(fig)
    plt.clf()
    plt.subplot(1, 2, 1)
    plt.plot(time[:10000], rtsdata[:10000], ".", label="signal")
    plt.xlabel("Time")
    plt.title("Selection of points")
    plt.subplot(1, 2, 2)
    _plot_rts_histogram(rtsdata, num_bins, par_fit, split, "Histogram")
    return {}


from image_processor import *
from PIL import Image

def main():
    image_to_convert = Image.open("<image file name>")
    processor = ImageProcessor()
    text = processor.image_to_ascii(image_to_convert, 2)
    print(text)

if __name__ == "__main__":
    main()


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

"""
Commands for backup and restoration of application configuration and data.

Responsible for creating local backups, remote (offsite) backups and restoring from a local backup.
Pulls configuration from `stack-settings.yml`.
If no `backup` section is present in the configuration, a local backup is still taken with nothing excluded and rolling
backup deletion disabled.
See the README for further details on backup configuration.
_______________________________________________________________________________

Created by brightSPARK Labs
www.brightsparklabs.com
"""

# standard libraries
import traceback

# vendor libraries
import click

# local libraries
from appcli.backup_manager.backup_manager import BackupManager
from appcli.commands.appcli_command import AppcliCommand
from appcli.configuration_manager import ConfigurationManager
from appcli.functions import error_and_exit
from appcli.logger import logger
from appcli.models.cli_context import CliContext
from appcli.models.configuration import Configuration

# ------------------------------------------------------------------------------
# CONSTANTS
# ------------------1------------------------------------------------------------

# The name of the key for the backup block in the stack settings file.
BACKUP = "backup"

# ------------------------------------------------------------------------------
# CLASSES
# ------------------------------------------------------------------------------

class BackupManagerCli:

    # --------------------------------------------------------------------------
    # CONSTRUCTOR
    # --------------------------------------------------------------------------

    def __init__(self, configuration: Configuration):

        self.cli_configuration: Configuration = configuration

        # ------------------------------------------------------------------------------
        # PUBLIC METHODS
        # ------------------------------------------------------------------------------

        @click.command(
            help="Create a backup of application data and configuration. Will also execute any configured remote strategies."
        )
        @click.pass_context
        def backup(ctx):
            cli_context: CliContext = ctx.obj

            cli_context.get_configuration_dir_state().verify_command_allowed(
                AppcliCommand.BACKUP
            )

            backup_manager: BackupManager = self.__create_backup_manager(cli_context)

            # Create a local backup
            backup_filename = backup_manager.backup(ctx)

            # Get any remote backup strategies.
            remote_backups = backup_manager.get_remote_backups()

            # Get the key file for decrypting encrypted values used in a remote backup.
            key_file = cli_context.get_key_file()

            # Execute each of the remote backup strategies with the local backup file.
            for remote_backup in remote_backups:
                try:
                    remote_backup.backup(backup_filename, key_file)
                except Exception as e:
                    logger.error(
                        f"Error while executing remote strategy [{remote_backup.name}] - {e}"
                    )
                    traceback.print_exc()

        @click.command(help="Restore a backup of application data and configuration.")
        @click.argument("backup_file")
        @click.option(
            "--force",
            is_flag=True,
            help="Force restoring a backup even if validation checks fail.",
        )
        @click.pass_context
        def restore(ctx, backup_file, force):
            cli_context: CliContext = ctx.obj

            cli_context.get_configuration_dir_state().verify_command_allowed(
                AppcliCommand.RESTORE, force
            )

            backup_manager: BackupManager = self.__create_backup_manager(cli_context)

            backup_manager.restore(ctx, backup_file)

        @click.command(help="View a list of available backups.")
        @click.option(
            "--force",
            is_flag=True,
            help="Force viewing backups even if validation checks fail.",
        )
        @click.pass_context
        def view_backups(ctx, force):

            cli_context: CliContext = ctx.obj
            cli_context.get_configuration_dir_state().verify_command_allowed(
                AppcliCommand.VIEW_BACKUPS, force
            )

            backup_manager: BackupManager = self.__create_backup_manager(cli_context)

            backup_manager.view_backups(ctx)

        # Expose the commands.
        self.commands = {
            "backup": backup,
            "restore": restore,
            "view_backups": view_backups,
        }

    def __create_backup_manager(self, cli_context: CliContext) -> BackupManager:
        """
        Create a BackupManager object from the `backup` section of the stack settings configuration file.

        Returns:
            A BackupManager object that contains backup configuration.
        """
        # Get the settings from the `stack-settings` file.
        configuration = ConfigurationManager(cli_context, self.cli_configuration)
        try:
            stack_variables = configuration.get_stack_variable(BACKUP)
        except (KeyError, TypeError) as e:
            error_and_exit(f"No backup key found in stack settings. [{e}]")

        if stack_variables is None:
            error_and_exit("Backup key in stack settings was empty.")

        # Create our BackupManager from the settings.
        return BackupManager.from_dict(stack_variables)


from typing import Tuple, Set, FrozenSet, Sequence, Generator

from copy import deepcopy
import itertools

from .position import Point2, Size, Rect
from .pixel_map import PixelMap
from .player import Player
from typing import List, Dict, Set, Tuple, Any, Optional, Union # mypy type checking

class Ramp:
    def __init__(self, points: Set[Point2], game_info: "GameInfo"):
        self._points: Set[Point2] = points
        self.__game_info = game_info
        # tested by printing actual building locations vs calculated depot positions
        self.x_offset = 0.5 # might be errors with the pixelmap?
        self.y_offset = -0.5

    @property
    def _height_map(self):
        return self.__game_info.terrain_height

    @property
    def _placement_grid(self):
        return self.__game_info.placement_grid

    @property
    def size(self) -> int:
        return len(self._points)

    def height_at(self, p: Point2) -> int:
        return self._height_map[p]

    @property
    def points(self) -> Set[Point2]:
        return self._points.copy()

    @property
    def upper(self) -> Set[Point2]:
        """ Returns the upper points of a ramp. """
        max_height = max([self.height_at(p) for p in self._points])
        return {
            p
            for p in self._points
            if self.height_at(p) == max_height
        }

    @property
    def upper2_for_ramp_wall(self) -> Set[Point2]:
        """ Returns the 2 upper ramp points of the main base ramp required for the supply depot and barracks placement properties used in this file. """
        upper2 = sorted(list(self.upper), key=lambda x: x.distance_to(self.bottom_center), reverse=True)
        while len(upper2) > 2:
            upper2.pop()
        return set(upper2)

    @property
    def top_center(self) -> Point2:
        pos = Point2((sum([p.x for p in self.upper]) / len(self.upper), \
            sum([p.y for p in self.upper]) / len(self.upper)))
        return pos

    @property
    def lower(self) -> Set[Point2]:
        min_height = min([self.height_at(p) for p in self._points])
        return {
            p
            for p in self._points
            if self.height_at(p) == min_height
        }

    @property
    def bottom_center(self) -> Point2:
        pos = Point2((sum([p.x for p in self.lower]) / len(self.lower), \
            sum([p.y for p in self.lower]) / len(self.lower)))
        return pos

    @property
    def barracks_in_middle(self) -> Point2:
        """ Barracks position in the middle of the 2 depots """
        if len(self.upper2_for_ramp_wall) == 2:
            points = self.upper2_for_ramp_wall
            p1 = points.pop().offset((self.x_offset, self.y_offset))
            p2 = points.pop().offset((self.x_offset, self.y_offset))
            # Offset from top point to barracks center is (2, 1)
            intersects = p1.circle_intersection(p2, (2**2 + 1**2)**0.5)
            anyLowerPoint = next(iter(self.lower))
            return max(intersects, key=lambda p: p.distance_to(anyLowerPoint))
        raise Exception('Not implemented. Trying to access a ramp that has a wrong amount of upper points.')

    @property
    def depot_in_middle(self) -> Point2:
        """ Depot in the middle of the 3 depots """
        if len(self.upper2_for_ramp_wall) == 2:
            points = self.upper2_for_ramp_wall
            p1 = points.pop().offset((self.x_offset, self.y_offset)) # still an error with pixelmap?
            p2 = points.pop().offset((self.x_offset, self.y_offset))
            # Offset from top point to depot center is (1.5, 0.5)
            intersects = p1.circle_intersection(p2, (1.5**2 + 0.5**2)**0.5)
            anyLowerPoint = next(iter(self.lower))
            return max(intersects, key=lambda p: p.distance_to(anyLowerPoint))
        raise Exception('Not implemented. Trying to access a ramp that has a wrong amount of upper points.')

    @property
    def corner_depots(self) -> Set[Point2]:
        """ Finds the 2 depot positions on the outside """
        if len(self.upper2_for_ramp_wall) == 2:
            points = self.upper2_for_ramp_wall
            p1 = points.pop().offset((self.x_offset, self.y_offset)) # still an error with pixelmap?
            p2 = points.pop().offset((self.x_offset, self.y_offset))
            center = p1.towards(p2, p1.distance_to(p2) / 2)
            depotPosition = self.depot_in_middle
            # Offset from middle depot to corner depots is (2, 1)
            intersects = center.circle_intersection(depotPosition, (2**2 + 1**2)**0.5)
            return intersects
        raise Exception('Not implemented. Trying to access a ramp that has a wrong amount of upper points.')

    @property
    def barracks_can_fit_addon(self) -> bool:
        """ Test if a barracks can fit an addon at natural ramp """
        # https://i.imgur.com/4b2cXHZ.png
        if len(self.upper2_for_ramp_wall) == 2:
            return self.barracks_in_middle.x + 1 > max(self.corner_depots, key=lambda depot: depot.x).x
        raise Exception('Not implemented. Trying to access a ramp that has a wrong amount of upper points.')

    @property
    def barracks_correct_placement(self) -> Point2:
        """ Corrected placement so that an addon can fit """
        if len(self.upper2_for_ramp_wall) == 2:
            if self.barracks_can_fit_addon:
                return self.barracks_in_middle
            else:
                return self.barracks_in_middle.offset((-2, 0))
        raise Exception('Not implemented. Trying to access a ramp that has a wrong amount of upper points.')

class GameInfo(object):
    def __init__(self, proto):
        # TODO: this might require an update during the game because placement grid and playable grid are greyed out on minerals, start locations and ramps (debris)
        self._proto = proto
        self.players: List[Player] = [Player.from_proto(p) for p in proto.player_info]
        self.map_size: Size = Size.from_proto(proto.start_raw.map_size)
        self.pathing_grid: PixelMap = PixelMap(proto.start_raw.pathing_grid)
        self.terrain_height: PixelMap = PixelMap(proto.start_raw.terrain_height)
        self.placement_grid: PixelMap = PixelMap(proto.start_raw.placement_grid)
        self.playable_area = Rect.from_proto(proto.start_raw.playable_area)
        self.map_ramps: List[Ramp] = self._find_ramps()
        self.player_races: Dict[int, "Race"] = {p.player_id: p.race_actual or p.race_requested for p in proto.player_info}
        self.start_locations: List[Point2] = [Point2.from_proto(sl) for sl in proto.start_raw.start_locations]
        self.player_start_location: Point2 = None # Filled later by BotAI._prepare_first_step

    @property
    def map_center(self) -> Point2:
        return self.playable_area.center

    def _find_ramps(self) -> List[Ramp]:
        """Calculate (self.pathing_grid - self.placement_grid) (for sets) and then find ramps by comparing heights."""
        rampDict = {
            Point2((x, y)): self.pathing_grid[(x, y)] == 0 and self.placement_grid[(x, y)] == 0
            for x in range(self.pathing_grid.width)
            for y in range(self.pathing_grid.height)
        }

        rampPoints = {p for p in rampDict if rampDict[p]} # filter only points part of ramp
        rampGroups = self._find_groups(rampPoints)
        return [Ramp(group, self) for group in rampGroups]

    def _find_groups(self, points: Set[Point2], minimum_points_per_group: int=8, max_distance_between_points: int=2) -> List[Set[Point2]]:
        """ From a set/list of points, this function will try to group points together """
        foundGroups = []
        currentGroup = set()
        newlyAdded = set()
        pointsPool = set(points)

        while pointsPool or currentGroup:
            if not currentGroup:
                randomPoint = pointsPool.pop()
                currentGroup.add(randomPoint)
                newlyAdded.add(randomPoint)

            newlyAddedOld = newlyAdded
            newlyAdded = set()
            for p1 in newlyAddedOld:
                # create copy as we change set size during iteration
                for p2 in pointsPool.copy():
                    if abs(p1.x - p2.x) + abs(p1.y - p2.y) <= max_distance_between_points:
                        currentGroup.add(p2)
                        newlyAdded.add(p2)
                        pointsPool.discard(p2)

            # Check if all connected points were found
            if not newlyAdded:
                # Add to group if number of points reached threshold - discard group if not enough points
                if len(currentGroup) >= minimum_points_per_group:
                    foundGroups.append(currentGroup)
                currentGroup = set()
        """ Returns groups of points as list
        [{p1, p2, p3}, {p4, p5, p6, p7, p8}]
        """
        return foundGroups


import numpy as np
import gzip
import pickle
import os
import glob
import time
import cv2
import datetime
import pandas as pd
from sklearn.metrics import fbeta_score
from sklearn.model_selection import KFold, train_test_split
from collections import Counter, defaultdict
import random
import shutil
import operator
#import pyvips
#from PIL import Image
import platform
import json
try:
    from PIL import Image as pil_image
except ImportError:
    pil_image = None
from keras import backend as K

############# SET PATHS ################
DATASET_PATH = '/mnt/YTdata/inclusive/train_images/'
ROOT_PATH = '/home/weimin/reproduce/2018_09_Inclusive_Images_submit_final/'
TUNING_IMAGE_PATH = '/home/weimin/reproduce/2018_09_Inclusive_Images_submit_final/input/tuning_label_images/*.jpg'
#######################################

INPUT_PATH = ROOT_PATH + 'input/'
OUTPUT_PATH = ROOT_PATH + 'modified_data/'
VAL_DATAPATH = ROOT_PATH + 'val_images/'

MODELS_PATH = ROOT_PATH + 'models/'
HISTORY_FOLDER_PATH = MODELS_PATH + "history/"
SUBM_PATH = ROOT_PATH + 'subm/'

if not os.path.isdir(OUTPUT_PATH):
    os.mkdir(OUTPUT_PATH)
if not os.path.isdir(VAL_DATAPATH):
    os.mkdir(VAL_DATAPATH)
if not os.path.isdir(SUBM_PATH):
    os.mkdir(SUBM_PATH)

def save_in_file(arr, file_name):
    pickle.dump(arr, gzip.open(file_name, 'wb+', compresslevel=3))

def load_from_file(file_name):
    return pickle.load(gzip.open(file_name, 'rb'))

def save_in_file_fast(arr, file_name):
    pickle.dump(arr, open(file_name, 'wb'))

def load_from_file_fast(file_name):
    return pickle.load(open(file_name, 'rb'))

def show_image(im, name='image'):
    cv2.imshow(name, im.astype(np.uint8))
    cv2.waitKey(0)
    cv2.destroyAllWindows()

def show_resized_image(P, w=1000, h=1000):
    res = cv2.resize(P.astype(np.uint8), (w, h), interpolation=cv2.INTER_CUBIC)
    show_image(res)

def get_date_string():
    return datetime.datetime.now().strftime("%Y-%m-%d-%H-%M")

def sort_dict_by_values(a, reverse=True):
    sorted_x = sorted(a.items(), key=operator.itemgetter(1), reverse=reverse)
    return sorted_x

def value_counts_for_list(lst):
    a = dict(Counter(lst))
    a = sort_dict_by_values(a, True)
    return a

def save_history_figure(history, path, columns=('fbeta', 'val_fbeta')):
    import matplotlib.pyplot as plt
    s = pd.DataFrame(history.history)
    plt.plot(s[list(columns)])
    plt.savefig(path)
    plt.close()

def load_img_npz(path):
    """Loads an image from pickle file.
    # Arguments
        path: Path to image file
    # Returns
        An image instance
    """
    #img = load_from_file_fast(path)

    temp_img = cv2.imread(path)
    temp_img = cv2.cvtColor(temp_img, cv2.COLOR_BGR2RGB)
    if temp_img.shape[:2] != (299,299):
        temp_img = cv2.resize(temp_img, (299,299))

    return temp_img.astype(np.float32)

def read_single_image(path):

    #try:
    #    img = np.array(Image.open(path))
    #except:
    try:
        img = cv2.cvtColor(cv2.imread(path), cv2.COLOR_BGR2RGB)
    except:
        print('Fail to read image path: ', path)
        return None

    if len(img.shape) == 2:
        img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)

    if img.shape[2] == 2:
        img = img[:, :, :1]

    if img.shape[2] == 1:
        img = np.concatenate((img, img, img), axis=2)

    if img.shape[2] > 3:
        img = img[:, :, :3]

    return img

def img_to_array(img, data_format=None):
    """Converts a PIL Image instance to a Numpy array.
    # Arguments
        img: PIL Image instance.
        data_format: Image data format.
    # Returns
        A 3D Numpy array.
    # Raises
        ValueError: if invalid `img` or `data_format` is passed.
    """
    if data_format is None:
        data_format = K.image_data_format()
    if data_format not in {'channels_first', 'channels_last'}:
        raise ValueError('Unknown data_format: ', data_format)
    # Numpy array x has format (height, width, channel)
    # or (channel, height, width)
    # but original PIL image has format (width, height, channel)
    x = np.asarray(img, dtype=K.floatx())
    if len(x.shape) == 3:
        if data_format == 'channels_first':
            x = x.transpose(2, 0, 1)
    elif len(x.shape) == 2:
        if data_format == 'channels_first':
            x = x.reshape((1, x.shape[0], x.shape[1]))
        else:
            x = x.reshape((x.shape[0], x.shape[1], 1))
    else:
        raise ValueError('Unsupported image shape: ', x.shape)
    return x

def read_single_image_keras(path):

    if pil_image is None:
        raise ImportError('Could not import PIL.Image. '
                          'The use of `array_to_img` requires PIL.')
    img = pil_image.open(path)

    if img.mode != 'RGB':
        img = img.convert('RGB')

    img = img_to_array(img)
    return img

def get_description_for_labels():
    out = open(INPUT_PATH + 'class-descriptions-boxable.csv')
    lines = out.readlines()
    ret_1, ret_2 = dict(), dict()
    for l in lines:
        arr = l.strip().split(',')
        ret_1[arr[0]] = arr[1]
        ret_2[arr[1]] = arr[0]
    return ret_1, ret_2

def read_image_bgr_fast(path):
    img2 = read_single_image(path)
    img2 = img2[:, :, ::-1]
    return img2

def get_subcategories(sub_cat, upper_cat, level, l, d1, sub):
    ret = []
    sub_cat[upper_cat] = ([], [])
    for j, k in enumerate(l[sub]):
        nm = d1[k['LabelName']]
        sub_cat[upper_cat][1].append(nm)
        if nm in sub_cat:
            continue
        ret.append(nm)
        if 'Subcategory' in k:
            get_subcategories(sub_cat, nm, level + 1, l, d1, 'Subcategory')
        else:
            sub_cat[nm] = ([upper_cat], [])
    return ret

def get_hierarchy_structures():
    sub_cat = dict()
    part_cat = dict()
    d1, d2 = get_description_for_labels()
    arr = json.load(open(INPUT_PATH + 'bbox_labels_600_hierarchy.json', 'r'))
    lst = dict(arr.items())['Subcategory']
    for i, l in enumerate(lst):
        nm = d1[l['LabelName']]
        if 'Subcategory' in l:
            get_subcategories(sub_cat, nm, 1, l, d1, 'Subcategory')
        else:
            if nm in sub_cat:
                print('Strange!')
                exit()
            sub_cat[nm] = [], []
    return sub_cat

def get_description_for_labels_500():
    out = open(INPUT_PATH + 'challenge-2018-class-descriptions-500.csv')
    lines = out.readlines()
    ret_1, ret_2 = dict(), dict()
    for l in lines:
        arr = l.strip().split(',')
        ret_1[arr[0]] = arr[1]
        ret_2[arr[1]] = arr[0]
    return ret_1, ret_2

def get_classes_dict(type='all'):
    dcode, dreal = dict(), dict()
    s1 = list(pd.read_csv(INPUT_PATH + 'classes-trainable.csv')['label_code'].values)
    s2 = pd.read_csv(INPUT_PATH + 'class-descriptions.csv')
    orig = s2['label_code'].values
    descr = s2['description'].values

    if type == 'all':
        for i, o in enumerate(orig):
            dcode[o] = descr[i]
            dreal[descr[i]] = o
    else:
        for i, o in enumerate(orig):
            if o in s1:
                dcode[o] = descr[i]
                dreal[descr[i]] = o

    return dcode, dreal

def get_classes_dict_top_543(type='all'):
    dcode, dreal = dict(), dict()
    s1 = set(pd.read_csv(INPUT_PATH + 'final_label_543_count_500.csv')['LabelName'].values)
    s2 = pd.read_csv(INPUT_PATH + 'class-descriptions.csv')
    orig = s2['label_code'].values
    descr = s2['description'].values

    if type == 'all':
        for i, o in enumerate(orig):
            dcode[o] = descr[i]
            dreal[descr[i]] = o
    else:
        for i, o in enumerate(orig):
            if o in s1:
                dcode[o] = descr[i]
                dreal[descr[i]] = o

    return dcode, dreal

def get_classes_dict_top_491(type='all'):
    dcode, dreal = dict(), dict()
    s1 = set(pd.read_csv(INPUT_PATH + 'new_labels_from_test.csv')['labels'].values)
    s2 = pd.read_csv(INPUT_PATH + 'class-descriptions.csv')
    orig = s2['label_code'].values
    descr = s2['description'].values

    if type == 'all':
        for i, o in enumerate(orig):
            dcode[o] = descr[i]
            dreal[descr[i]] = o
    else:
        for i, o in enumerate(orig):
            if o in s1:
                dcode[o] = descr[i]
                dreal[descr[i]] = o

    return dcode, dreal

def get_classes_to_index_dicts():
    s1 = list(pd.read_csv(INPUT_PATH + 'classes-trainable.csv')['label_code'].values)
    index_arr_forward = dict()
    index_arr_backward = dict()
    for i in range(len(s1)):
        index_arr_forward[s1[i]] = i
        index_arr_backward[i] = s1[i]

    return index_arr_forward, index_arr_backward

def get_classes_to_index_dicts_top_543():
    s1 = list(pd.read_csv(INPUT_PATH + 'final_label_543_count_500.csv')['LabelName'].values)
    index_arr_forward = dict()
    index_arr_backward = dict()
    for i in range(len(s1)):
        index_arr_forward[s1[i]] = i
        index_arr_backward[i] = s1[i]

    return index_arr_forward, index_arr_backward

def get_classes_to_index_dicts_top_491():
    s1 = list(pd.read_csv(INPUT_PATH + 'new_labels_from_test.csv')['labels'].values)
    index_arr_forward = dict()
    index_arr_backward = dict()
    for i in range(len(s1)):
        index_arr_forward[s1[i]] = i
        index_arr_backward[i] = s1[i]

    return index_arr_forward, index_arr_backward

def get_target_v2(batch_files, image_classes):
    target = np.zeros((len(batch_files), 7178), dtype=np.uint8)
    for i, b in enumerate(batch_files):
        for el in image_classes[b]:
            target[i, el] = 1
    return target

def get_target_top_491(batch_files, image_classes):
    target = np.zeros((len(batch_files), 491), dtype=np.uint8)
    for i, b in enumerate(batch_files):
        for el in image_classes[b]:
            target[i, el] = 1
    return target


from cnn_utils import div2sets, chooseAlgoUsingIOU, checkIfPredictionIsRight
import os
from MAPCalculator import MAPCalculator

from sklearn import tree, preprocessing
from sklearn.neural_network import MLPClassifier

import graphviz

import pickle
from our_paths import *

calc = MAPCalculator()
x_pairs = []
y_pairs = []

x_solo = []
y_solo = []

for i, chosen_pic in enumerate(os.listdir(yolo_results_path)):
    pair_list, pair_boxes, solo_list, solo_boxes = div2sets(os.path.join(yolo_results_path, chosen_pic),
                                                            os.path.join(mrcnn_results_path, chosen_pic))

    real_boxes = calc.getGroundTruth(chosen_pic)
    for i, pair_box in enumerate(pair_boxes):
        alg_index = chooseAlgoUsingIOU(pair_box[0], pair_box[1], real_boxes)
        x_pairs.append(pair_list[i][0] + pair_list[i][1])
        y_pairs.append(alg_index)

    for i, solo_box in enumerate(solo_boxes):
        is_exist = checkIfPredictionIsRight(solo_box, real_boxes)
        x_solo.append(solo_list[i])
        y_solo.append(is_exist)

clf_pair = tree.DecisionTreeClassifier()
clf_pair = clf_pair.fit(x_pairs, y_pairs)

clf_solo = tree.DecisionTreeClassifier()
clf_solo = clf_solo.fit(x_solo, y_solo)

clf_net = MLPClassifier(solver='sgd', alpha=1e-5,
                    hidden_layer_sizes=(100,), random_state=1, max_iter=4000)

X_scaled = preprocessing.scale(x_solo)

clf_net.fit(X_scaled, y_solo)

with open(filename_solo_net, 'wb') as file:
    pickle.dump(clf_net, file)

with open(filename_pairs, 'wb') as file:
    pickle.dump(clf_pair, file)

with open(filename_solo, 'wb') as file:
    pickle.dump(clf_solo, file)

# dot_data_pairs = tree.export_graphviz(clf_pair, out_file=None)
# graph_pairs = graphviz.Source(dot_data_pairs)
# graph_pairs.render(graph_pairs_name, graph_path)
#
# dot_data_solo = tree.export_graphviz(clf_solo, out_file=None)
# graph_solo = graphviz.Source(dot_data_solo)
# graph_solo.render(graph_solo_name, graph_path)

coco = 0


import asyncio
import unittest
from dataclasses import dataclass
from typing import Any, List

from bot.rules import attachments

# Using `MagicMock` sadly doesn't work for this usecase
# since it's __eq__ compares the MagicMock's ID. We just
# want to compare the actual attributes we set.
@dataclass
class FakeMessage:
    author: str
    attachments: List[Any]

def msg(total_attachments: int) -> FakeMessage:
    return FakeMessage(author='lemon', attachments=list(range(total_attachments)))

class AttachmentRuleTests(unittest.TestCase):
    """Tests applying the `attachment` antispam rule."""

    def test_allows_messages_without_too_many_attachments(self):
        """Messages without too many attachments are allowed as-is."""
        cases = (
            (msg(0), msg(0), msg(0)),
            (msg(2), msg(2)),
            (msg(0),),
        )

        for last_message, *recent_messages in cases:
            with self.subTest(last_message=last_message, recent_messages=recent_messages):
                coro = attachments.apply(last_message, recent_messages, {'max': 5})
                self.assertIsNone(asyncio.run(coro))

    def test_disallows_messages_with_too_many_attachments(self):
        """Messages with too many attachments trigger the rule."""
        cases = (
            ((msg(4), msg(0), msg(6)), [msg(4), msg(6)], 10),
            ((msg(6),), [msg(6)], 6),
            ((msg(1),) * 6, [msg(1)] * 6, 6),
        )
        for messages, relevant_messages, total in cases:
            with self.subTest(messages=messages, relevant_messages=relevant_messages, total=total):
                last_message, *recent_messages = messages
                coro = attachments.apply(last_message, recent_messages, {'max': 5})
                self.assertEqual(
                    asyncio.run(coro),
                    (f"sent {total} attachments in 5s", ('lemon',), relevant_messages)
                )


import hashlib
import os

def calculate_file_hash(file):
    if not os.path.exists(file):
        return ""

    read_amount = 512
    hash = hashlib.sha1()
    with open(file, 'rb') as file_handle:

        while True:
            data = file_handle.read(read_amount)
            if(len(data)<=0):
                break
            hash.update(data)
    return hash.hexdigest()


"""
!/usr/bin/python
 mabdelra 25.07.2018
 [1] Input data file or folder with subfiles. For example: AMPL_2012_Referenzdatensatz.dat
 [2] Loop start. For example: first day of the year --> 0 [Default: 0]
 [3] Loop end. For example: last day of the year --> 0. Day 15 of the year --> 15 [Default: 0]
 [4] Correction. Binary digit to define if the optimization result is merged with
    the input data and the real battery operation is calculated. 0 = No, 1 = Yes. [Default = 0]
 [5] Battery Capacity: Used to write the battery capacity in the results file name. [Default = ""]
 """

from __future__ import print_function

import csv
from subprocess import call
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from os import listdir, makedirs, path, unlink
from os.path import isfile, join, exists, isdir
import pytz

# + local/relativ modul import
import sys
import inspect
import os
import re

# cmd_folder = os.path.realpath(
#     os.path.dirname(inspect.getfile(inspect.currentframe())))
# if cmd_folder not in sys.path:
#     sys.path.insert(0, cmd_folder)
# os.getcwd(cmd_folder)
# import paths to different files from paths.py
from paths import input_folder, output_folder, parsed_files_dir, generic_folder, merged_files_dir, bat_spec_dir, bat_dat_filename, period_dat_filename

# genpy imports
from genpy.datacrunching import UNIXDataFrameColumn2DataIndex, DATAinput2TimeSeries, csv_to_dict, n_comments
from genpy.systools import sorted_list_of_files, delete_files_in_folder, build_folder_structure, folder_or_file

# -
myFolder = (
    os.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe()))))

# Functions

def readFile2DFts(path, unix_column = 'hoy'):
    """
    Reads CSV with whitespace delimiter and a unix timestamp to pandas daframe as timeseries.
    """
    DF = pd.read_csv(path, delim_whitespace = True)
    DF_ts = UNIXDataFrameColumn2DataIndex(DF, unix_column)
    return DF_ts

def read_batdat(path):
    """
    Read the battery data file in AMPL format and provide the information as dictionary
    """
    bat_filename = '{}/{}'.format(path, bat_dat_filename)
    df = pd.read_csv(bat_filename, sep = ' ''|'';', header = None,
                     skiprows = n_comments(bat_filename, '#'), index_col = 1)
    bat_dict = df.to_dict()[3]
    return bat_dict

def write_spec(name, isfolder, szenname, paramDict = {"Error": "No paramDict available!"}):
    """
    Write specification file by building a dictionary and writing it as columns into a textfile.
    """

    spec_list = name.split('_')
    if isfolder:
        filesspec = {'period':  spec_list[-2], 'overlap':  spec_list[-3], 'length':  spec_list[-4],
                     'name': '_'.join(spec_list[:-4]), 'szenname': szenname, 'fileorfolder': 'folder'}
    else:
        filesspec = {
            'name':  name, 'szenname': szenname, 'fileorfolder': 'file'}
    filesspec.update(paramDict)
    w = csv.writer(open(bat_spec_dir, "w"))
    for key, val in filesspec.items():
        w.writerow([key, val])

    print ('\nSpecifications written in output/spec.txt\n')
    return filesspec

def write_period(lastSOC, inputFile, inputFile2 = None):
    """
    Write the period file with the last SoC and the name of the executed file.
    """

    with open(period_dat_filename, 'w') as period_file:
        if not inputFile2:
            template = '#This file contains all data adjusted for each optimization step.\nparam sto_soc_start := {SOC};\ndata {input};'
            filled_template = template.format(SOC = lastSOC, input = inputFile)
        else:
            template = '#This file contains all data adjusted for each optimization step.\nparam sto_soc_start := {SOC};\ndata {input};\ndata {inputex};'
            filled_template = template.format(
                SOC = lastSOC, input = inputFile, inputex = inputFile2)
        period_file.write(filled_template)

def getNumberofLastFileinFolder(path):
    """
    Get first element of last (sorted by name) file (not folder) of the name seperated by underscore ("_")
    [1] path of folder
    """
    onlyfiles = [f for f in listdir(path) if isfile(join(path, f))]
    onlyfiles = sorted(onlyfiles, key = lambda x: int(x.split("_")[0]))
    filename = onlyfiles.pop()
    return int(filename.split("_")[0])

def parse_generic_output(specDict, step=0):
    # Read AMPL output file and make a table

    column_names = ['Name', 'Eq', 'T', 'Value']

    DF = pd.read_csv(generic_folder, delimiter='\[''|''\,''|'']', header=None, names=column_names)

    DF.iloc[:, 1] = DF.iloc[:, 1].str.replace("'", '')  # replace quote symbol from column 1

    DFindex = DF['Name']

    DFbody = DF[['Eq', 'T', 'Value']]
    DFbodytrans = DFbody.T
    to_shift = pd.isnull(DFbodytrans.iloc[-1])
    DFbodytrans.loc[:, to_shift] = DFbodytrans.loc[:, to_shift].shift(1)
    DFbody2 = DFbodytrans.T

    DFbody2['Name'] = DFindex
    DFbody2.fillna('', inplace=True)

    DFbody2['Name'] = DFbody2['Name'] + DFbody2['Eq']
    del DFbody2['Eq']
    DFbody2 = DFbody2[:-1]

    DFbody2[['T']] = DFbody2[['T']].astype(int)
    DFbody2[['Value']] = DFbody2[['Value']].astype(float)

    table = pd.pivot_table(DFbody2, values='Value', index=['T'], columns=['Name'], aggfunc=np.sum)

    table = table.ix[1:]

    if specDict['fileorfolder'] == 'folder':
        print (specDict)
        orginput_ts = DATAinput2TimeSeries('{0}/{name}/{1}_{name}'.format(input_folder, step, **specDict),
                                           unix_column='UNIX_TIME', escapechar=None, delim_whitespace=True, verbose=1,
                                           cutendlines=2, skipinitialspace=True)
        output_file = '{0}/{1}_res_{name}'.format(parsed_files_dir, step, **specDict)
    else:
        orginput_ts = DATAinput2TimeSeries('{0}/{name}'.format(input_folder, **specDict), unix_column='UNIX_TIME',
                                           escapechar=None, delim_whitespace=True, verbose=1,
                                           cutendlines=2, skipinitialspace=True)
        output_file = '{0}/res_{szenname}_{name}'.format(output_folder, **specDict)

    columnlist = list(orginput_ts.columns.values)

    for item in columnlist:
        if (item != ':=') and (item != 'param:'):
            table[item] = np.array(orginput_ts[item])

    table.to_csv(output_file, sep=" ")
    print ('The parsed file is in: {0}\n'.format(output_file))

def combineResults(folder, period = 'D', length = 1, start = 0, end = 0, unix_column = 'hoy'):
    """
    Combine all the files located in the folder into one single dataframe.
    """
    sorted_file_list = sorted_list_of_files(folder)

    if end != 0:
        sorted_file_list = sorted_file_list[:end]
    if start != 0:
        sorted_file_list = sorted_file_list[start:]

    number_of_files = len(sorted_file_list)
    firstFile = sorted_file_list.pop(0)
    print('\nCombining files\nThe first file in the folder is called: {0}\nThere are {1}files to be combined.'. format(
        firstFile, number_of_files))
    path = '{0}/{1}'.format(folder, firstFile)
    global_df = subPartinDF(path, period, length, unix_column)
    for fileName in sorted_file_list:
        path = '{0}/{1}'.format(folder, fileName)
        next_files_sub_part = subPartinDF(path, period, length, unix_column)
        try:
            global_df = global_df.append(next_files_sub_part, ignore_index = False)
        except AttributeError:
            print("The KeyError caused an AttributeError!")
    return global_df

def loopSubFiles(name, loopStart = 0, loopEnd = None, correct = False, szenname = "", paramDict = None, realDataFile = None,
                 extLoadFile = "",
                 beginSOC = 0.0):
    """
    loops ampl over the files in a folder or just solves a file
    [name] name of the file ormyFolder folder to work with
    [loopStart] first file: 0 is first in folder
    [loopEnd] last file: if None  last in folder is automatically used
    [correct]  1 if predicted is validated with real data. 0 if only predicted data is used
    [szname]  Defined scenario name to keep track of conditions used
    [paramDict] Dictionary. Parameters used in the simulation
    [realDataFile] : File with real PV and Load. Used to calculate real power flows after optimizing with predicted
    values.
    [extLoadFile] File written in perdiod.dat to be read load data file.
    """

    # Starting SOC.

    fileorfolderpath = '{0}/{1}'.format(input_folder, name)
    if paramDict is not None:
        write_spec(name, folder_or_file(fileorfolderpath), szenname, paramDict)
    else:
        write_spec(name, folder_or_file(fileorfolderpath), szenname)

    # System characteristics from the battery
    bat_dict = read_batdat(myFolder)
    PVscale = bat_dict['scale_pv']
    loadscale = bat_dict['scale_load']
    stoCap = bat_dict['sto_cap_max']

    # If correct then read real data from different file
    if correct:
        inputfolder = input_folder  # "input"
        inputpath = '{0}/{1}'.format(inputfolder, realDataFile)
        print('Reading input file: {}\n'.format(inputpath))
        orginput_ts = DATAinput2TimeSeries(inputpath, unix_column = 'hoy', sep = None, escapechar = None,
                                           delim_whitespace = True, verbose = 0, cutendlines = 2)

        cols = ['hoy', 'Load_Pel', 'PV1_Pel']
        orginput_ts = orginput_ts[cols]
        orginput_ts['hoy'] = orginput_ts['hoy'].astype(int)
        orginput_ts = orginput_ts.resample('1min', how = 'mean')

    if folder_or_file(fileorfolderpath):  # if Folder: several files
        delete_files_in_folder(parsed_files_dir)  # 'output/parsed')
        delete_files_in_folder(merged_files_dir)  # 'output/merged')
        specDict = csv_to_dict(bat_spec_dir)
        period = specDict['period']
        length = specDict['length']

        if loopEnd is None:
            print ('{0}/{1}'.format(input_folder, name))

            loopEnd = getNumberofLastFileinFolder(
                '{0}/{1}'.format(input_folder, name))
        for step in range(int(loopStart), int(loopEnd + 1)):
            if step == loopStart:
                lastSOC = beginSOC
                lastRealSOC = beginSOC
                print('the first step ({0}) of the optimisation starts with a SOC of {1}'.format(
                    loopStart, lastSOC))

            else:
                if not correct:
                    lastFILE = '{0}/{1}_res_{2}'.format(parsed_files_dir, step - 1, name)
                    print (lastFILE)
                    cut_sub = subPartinDF(lastFILE, period, length, 'hoy')
                    lastSOC = float(cut_sub.sto_soc[-1:])
                    if lastSOC > 1:
                        lastSOC = 1
                    if lastSOC < 0:
                        lastSOC = 0

                print('the {0} step of the optimisation starts with a SOC of {1}'.format(
                    step, lastSOC))

            if extLoadFile == '':
                FILE = '{0}/{1}/{2}_{3}'.format(input_folder, name, step, name)
                write_period(lastSOC, FILE)
                call(['ampl', 'simple.run'])
                call(['{0}/auto_generic.sh'.format(output_folder), '{0}/{1}_res_{2}'.format(parsed_files_dir, step, name),
                      '{0}/{1}'.format(generic_folder, 'generic_out.dat'), FILE, str(PVscale), str(loadscale)])
            else:
                FILE = '{0}/{1}/{2}_{3}'.format(input_folder, name, step, name)
                FILEex = '{0}/{1}/{2}_{3}'.format(input_folder,
                                                  extLoadFile, step, extLoadFile)
                write_period(lastSOC, FILE, FILEex)
                call(['ampl', 'simple.run'])
                call(['{0}/auto_generic2.sh'.format(output_folder), '{0}/{1}_res_{2}'.format(parsed_files_dir, step, name),
                      '{0}/{1}'.format(generic_folder, 'generic_out.dat'), FILE, str(PVscale), str(loadscale), FILEex])

            if correct:
                print(
                    'Merging results and input file for the step {0}'.format(step))

                # "output/parsed/", 1)
                listOFfiles = sorted_list_of_files(parsed_files_dir, 1)
                outputpath = '{0}/{1}'.format(parsed_files_dir, listOFfiles[-1])
                outputmerged = merged_files_dir  # "output/merged"
                lastRealCap = lastRealSOC * stoCap
                lastCap = lastSOC * stoCap
                relevantPartofoutput_ts = upsampling(
                    subPartinDF(outputpath, period, length), lastCap)
                relevantPartoforginput_ts = cutsubbyts(
                    orginput_ts, relevantPartofoutput_ts)
                merged_ts = inoutmerge(
                    relevantPartoforginput_ts, relevantPartofoutput_ts, PVscale, loadscale)
                merged_ts = surplus4merged(merged_ts)
                print(
                    'Calculating real battery operation and energy flows for the step {0}\n'.format(step))

# TEST TU ADD BATTERY DISCHARGE
               # merged_ts = surplus2bat_basic(merged_ts)
                merged_ts = surplus2bat(merged_ts, lastRealCap, bat_dict)
#################
                merged_ts = batPVLoad2newbilance(merged_ts, bat_dict)
                merged_ts.to_csv(
                    "{}/{}_merged.dat".format(outputmerged, listOFfiles[-1]), sep = " ")
                mergedFILE = '{0}/{1}_res_{2}_merged.dat'.format(
                    merged_files_dir, step, name)
                cut_sub = subPartinDF(mergedFILE, period, length, 'hoy_x')
# ACTUALIZE SOC WHEN IT IS SMALLER THAN REAL SOC
                lastRealSOC = float(cut_sub.sto_soc_x[-1:])
                lastSOC = float(cut_sub.sto_soc[-1:])
                # Only update lastSoC in case it is below the lastRealSOC. This
                # rule helps to add more "charging allowance" when not much sun
                # is expected.
                if (lastRealSOC > lastSOC):
                    lastSOC = lastRealSOC
                # lastSOC = float(cut_sub.sto_soc_x[-1:])  # This would be the standard form to update the value in model predictive control.
##################
                if lastSOC > 1:
                    lastSOC = 1
                if lastSOC < 0:
                    lastSOC = 0

            if step == int(loopEnd):
                print ('Successfully looped through all files of {0}. Specifications located in output/spec.txt'.format(name))
        global_df = combineResults(parsed_files_dir, period, length)
        final_filename = 'res_{szenname}_{inputfile1}{inputfile2}.dat'.format(
            **specDict)
        global_df.to_csv('{}/{}'.format(output_folder, final_filename), sep =' ')
        print(
            'The combined file is in: {0}/{1}\n'.format("output", final_filename))

        if correct:
            global_df = combineResults(merged_files_dir, period, length, 0, 0, 'hoy_x')
            final_filename = 'res_{szenname}_{inputfile1}{inputfile2}{realdatafile}_merged.dat'.format(
                **specDict)
            global_df.to_csv('{}/{}'.format(output_folder, final_filename), sep =' ')
            print(
                'The combined file is in: {0}/{1}\n'.format("output", final_filename))

    if not folder_or_file(fileorfolderpath):  # if File
        lastSOC = beginSOC
        FILE = '{0}/{1}'.format(input_folder, name)
        write_period(lastSOC, FILE)
        call(['ampl', 'simple.run'])
        newfilename = 'res_{0}_{1}'.format(szenname, name)
        call(['{0}/auto_generic.sh'.format(output_folder), '{0}/{1}'.format(output_folder, newfilename),
              '{0}/{1}'.format(generic_folder, 'generic_out.dat'), FILE, str(PVscale), str(loadscale)])

        if correct:
            print ('Merging results and input file')

            outputpath = '{0}/{1}'.format(output_folder, newfilename)
            lastCap = lastSOC * stoCap
            relevantPartofoutput_ts = upsampling(
                readFile2DFts(outputpath, 'hoy'), lastCap)
            relevantPartoforginput_ts = cutsubbyts(
                orginput_ts, relevantPartofoutput_ts)
            merged_ts = inoutmerge(
                relevantPartoforginput_ts, relevantPartofoutput_ts, PVscale, loadscale)
            merged_ts = surplus4merged(merged_ts)
            print ('Calculating real battery operation and energy flows\n')

# TEST TU ADD BATTERY DISCHARGE
          #  merged_ts = surplus2bat_basic(merged_ts)
            merged_ts = surplus2bat(merged_ts, lastCap, bat_dict)
#################
            merged_ts = batPVLoad2newbilance(merged_ts, stoCap)
            newfilename = newfilename[:-4]
            merged_ts.to_csv(
                "{0}/{1}_merged.dat".format(output_folder, newfilename), sep =" ")

        print(
            'Successfully looped through all files of {0}. Specifications located in output/spec.txt'.format(name))

def bat(i, lastCap, surplus, batMin = 0.0, batMax = 8.0, powerMin = -10.0, powerMax = 10.0, maxpeak = 0.6, period = 1.0 / 60,
        bat_eff = 0.98, inverter_eff = 0.97, bat_self_discharge = 0.07, verbose = 0):
    """
    Calculate sto_in (power +), sto_out (power -) and battery capacity.

    INPUTS
    [1] Starting battery capacity in kWh
    [2] Surplus power in kW. Excess (+) or Deficit (-)
    [3] minimum battery capacity in kWh
    [4] maximum battery capacity in kWh
    [5] maximum power to discharge the battery in kW (negative value)
    [6] maximum power to charge the battery in kW (positive value)
    [7] period in hours
    [bat_eff] : Battery charge/discharge efficiency [Roundtrip eff = 0.96, one-way eff = sqrt(0.96) = 0.97979]
    [inverter_eff] Inverter efficiency
    bas_self_sdicharge : Discharge rate over one month

    RETURN
    [realCap] Final battery capacity in kWh
    [power] Power in kW. Supplied (sto_in) to the battery (positive) or Delivered (sto_out) by the battery (negative)
    """
    bat_dict = read_batdat(myFolder)
    PVscale = bat_dict['scale_pv']
    stoCap = bat_dict['sto_cap_max']

    power = surplus
    if powerMin > power:
        power = powerMin
    if powerMax < power:
        power = powerMax

    bat_charge_discharge_losses = (1 - 0.950789146)  # (1-bat_eff*inverter_eff)
    # Equation to calculate time losses per hour considering 30 days and 24
    # hours
    bat_time_losses = 1 - (1 - bat_self_discharge) ** (period / (30 * 24))

    """
    Traditional battery operation: charge the battery when there is a possitive surplus and discharge when there is a negative surplus
    Battery Capacity = last capacity + energy in/out - charge/discharge
    losses - time losses
    """

# To equal HeiPhoss modell:
    if power >= 0.0:
        theoreticalCap = lastCap / (1 + bat_time_losses) + (
            power * period * (1 - bat_charge_discharge_losses)) / (1 + bat_time_losses)
    else:
        theoreticalCap = lastCap / (1 + bat_time_losses) + (
            power * period / (1 - bat_charge_discharge_losses)) / (1 + bat_time_losses)

    if batMin < theoreticalCap < batMax:
        realCap = theoreticalCap
        power = power

    elif theoreticalCap >= batMax:

        # means it is charging. It is allowed to charge up to batMax
        if lastCap <= batMax:
            realCap = batMax
            power = (realCap * (1 + bat_time_losses) - lastCap) / \
                (period * (1 - bat_charge_discharge_losses))
        # can be charging or discharging but charging is not allowed
        elif lastCap > batMax:
            realCap = theoreticalCap
            power = (realCap * (1 + bat_time_losses) - lastCap) * \
                (1 - bat_charge_discharge_losses) / (period)
            if power < 0:
                power
            else:
                power = 0
        if power > powerMax:
            power = powerMax
        elif power < powerMin:
            power = powerMin

    elif theoreticalCap <= batMin:
        if lastCap >= batMin:
            realCap = batMin
            power = (realCap * (1 + bat_time_losses) - lastCap) * \
                (1 - bat_charge_discharge_losses) / (period)
        elif lastCap < batMin:
            realCap = theoreticalCap
            power = (realCap * (1 + bat_time_losses) - lastCap) / \
                (period * (1 - bat_charge_discharge_losses))
            if power > 0:
                power
            else:
                power = 0
        if power > powerMax:
            power = powerMax
        elif power < powerMin:
            power = powerMin

# to equal HeiPhoSS
    # access this function only if there is a peak level and an excess of
    # power generated
    if (maxpeak < 1.0 and surplus > 0.0):
        # surplus minus power equals feed-in (if possitive). If feed-in is
        # greater than the limit, charge the battery
        if surplus - power > maxpeak * PVscale:
            power = surplus - maxpeak * PVscale
            theoreticalCap = lastCap / (1 + bat_time_losses) + (
                power * period * (1 - bat_charge_discharge_losses)) / (1 + bat_time_losses)
            if theoreticalCap > stoCap:
                realCap = stoCap
                power = (realCap * (1 + bat_time_losses) - lastCap) / \
                    (period * (1 - bat_charge_discharge_losses))
        if power > powerMax:
            power = powerMax
        elif power < powerMin:
            power = powerMin

    # realCap = lastCap + power*period - abs(power)*bat_charge_discharge_losses*period - lastCap*bat_time_losses*period
    if power >= 0.0:
        realCap = lastCap / (1 + bat_time_losses) + (power * period *
                                                     (1 - bat_charge_discharge_losses)) / (1 + bat_time_losses)
    else:
        realCap = lastCap / (1 + bat_time_losses) + (power * period /
                                                     (1 - bat_charge_discharge_losses)) / (1 + bat_time_losses)

    dictofbat = {"Cap": realCap, "P": power,
                 "surplus": surplus, "batMAX": batMax, "i": i}
# < #######################################################################

    if verbose == 1:
        with open("log_bat.dat", "a") as f:
            w = csv.writer(f, delimiter = ' ')
            w.writerow(dictofbat.items())
    return dictofbat

def subPartinDF(path, period = 'D', period_numbers = 1, unix_column = 'hoy'):
    """
    Get first period of file by converting it to pandas time series.
    Knows days hours minutes and seconds. Be careful with the timezone, since duplicated timeseries (e.g. summertime
    change) can rise an error.
    """
    period_numbers = float(period_numbers)
    df_ts = readFile2DFts(path, unix_column)
    first_time_step = df_ts.index[0]
    first_time_step = first_time_step.replace(second = 0)
    periodDict = {'D': 'days', 'H': 'hours', 'M': 'minutes', 'S': 'seconds'}
    last_time_step = first_time_step + \
        timedelta(
            **{periodDict[period]: period_numbers}) - timedelta(seconds = 1)
    try:
        subPart = df_ts.ix[str(first_time_step): str(last_time_step)]
    except KeyError:
        ErrorM = 'There was a KeyError in file {0} in the timeperiod between {1} and {2}! Maybe it is caused by day time shift e.g. summer time to winter time and therefore duplicate indexes.'.format(
            path, first_time_step, last_time_step)
        print (ErrorM)
        subPart = ErrorM
    return subPart

def upsampling(ts, lastCap, original_resolution = '15min', new_resolution = '1min'):
    """
    Increase frequency of a pandas timeseries by filling it up from the start
    """
    ts = ts.resample(original_resolution, how = 'mean')
    old_length = len(ts)
    new_length = old_length * \
        (int(original_resolution[:-3]) / int(new_resolution[:-3]))
    index = pd.date_range(ts.index[0], periods = new_length, freq = new_resolution)
    maxnumberoffill = int(original_resolution[:-3]) - int(new_resolution[:-3])
    ts1 = ts.reindex(index, limit = 14, method = 'ffill')
    # function to make an interpolated change over sto_cap instead of 15 minutes steps
    # lastCap is used as initial value but then is eliminated to avoid
    # duplicity of values (between lastSoC and firstSoC)
    index2 = pd.date_range(
        ts.index[0], periods = (new_length + 1), freq = new_resolution)
    ts2 = ts.reindex(index2)
    # shift 15 periods to include the last SoC as value in position 0
    ts2 = ts2.shift(periods = 15)
    ts2['sto_cap'][0] = lastCap  # last SoC is considered the initial value
    ts2['sto_soc'][0] = ts2['sto_soc'][15]
    ts2 = ts2.apply(pd.Series.interpolate)
    # shifted one period to eliminate lastSoC and start with value in position
    # 1
    ts2 = ts2.shift(periods = -1)
    ts2 = ts2.reindex(index)
    # Replace the interpolated sto_cap in the original ts
    ts1['sto_cap'] = ts2['sto_cap']
    ts1['sto_soc'] = ts2['sto_soc']
    return ts1

def cutsubbyts(tobecutted_ts, period_ts):
    starttime = period_ts.index[0]
    endtime = period_ts.index[-1]
    cutted_ts = tobecutted_ts[starttime: endtime]
    return cutted_ts

def inoutmerge(in_ts, out_ts, PVscale = 1, loadscale = 1):
    """
    Scales the PV values from the input, makes both load columns positive and merges in and out in one ts
    """

    if out_ts.Load_Pel.min() < 0:
        out_ts['Load_Pel'] = out_ts.Load_Pel * -1 * loadscale
        print ("For the OUTPUT file I multiplied 'Load_Pel' by -1 to change it to positive values.\n")

    else:
        out_ts['Load_Pel'] = out_ts.Load_Pel * loadscale
    if in_ts.Load_Pel.min() < 0:
        in_ts['Load_Pel'] = in_ts.Load_Pel * -1
        print ("For the INPUT file I multiplied 'Load_Pel' by -1 to change it to positive values.\n")

    else:
        in_ts['Load_Pel'] = in_ts.Load_Pel * loadscale

    in_ts['PV1_Pel'] = in_ts.PV1_Pel * PVscale

    merged_ts = pd.merge(in_ts, out_ts, left_index = True, right_index = True)
    merged_ts = merged_ts.tz_convert('Europe/Amsterdam')
    return merged_ts

def surplus4merged(merged_ts):
    """
    Calculate the surplus from input and output by subtracting the electric load from PV production:
    result is positive for surplus and negative for deficit.
    """
    merged_ts['surplus_x'] = merged_ts['PV1_Pel_x'] - merged_ts['Load_Pel_x']
    merged_ts['surplus_y'] = merged_ts['PV1_Pel_y'] - merged_ts['Load_Pel_y']
    return merged_ts

def surplus2bat_basic(merged_ts):
    """
    Use surplus and bat() to calcualte power into and from battery (outer value) as well as the course of the capacity.
    """

    firstCap = merged_ts.sto_cap[0]
    lastCap = firstCap
    batPowers = []
    batCaps = []
    for i in range(0, len(merged_ts)):
        batMax = merged_ts.sto_cap[i]
        batDict = bat(i, lastCap, merged_ts.surplus_x[i], 0, batMax)
        batPowers.append(batDict['P'])
        batCaps.append(batDict['Cap'])
        lastCap = batDict['Cap']
    merged_ts['sto_in_out_x'] = batPowers
    merged_ts['sto_cap_x'] = batCaps
    return merged_ts

def surplus2bat(merged_ts, realCap, bat_dict):
    """
    Use surplus and bat() to calcualte power into and from battery (outer value) as well as the course of the capacity.
    """

    stoCap = bat_dict['sto_cap_max']
    batMin = bat_dict['sto_min_charge']
    powerMax = bat_dict['sto_max_charge']
    powerMin = -bat_dict['sto_max_discharge']
    maxFeed = bat_dict['feed_in_limit']

    firstCap = merged_ts.sto_cap[0]
    lastCap = firstCap
    lastrealCap = realCap
    batPowers = []
    batCaps = []

    for i in range(0, len(merged_ts)):
        # Use the maximum between the (a) planned capacity and (b) desired
        # capacity
        batMax = max(merged_ts.sto_cap[i], merged_ts.sto_soc[i] * stoCap)
        # (a)Planned capacity is usefull to avoid discharging the battery when a last is expected
        # (b)Desired capacity is usefull to increase the amount of charged power when short periods of sun are expected
        # don't discharge battery if there is surplus or battery is under XX
        # kWh of its capacity or realCap is less than desired plan
        if (merged_ts.surplus_x[i] > 0 or lastrealCap < merged_ts.sto_cap[i] or lastrealCap <= 0.8):
            surplusminusbatgrid = merged_ts.surplus_x[i]
        else:
            surplusminusbatgrid = merged_ts.surplus_x[
                i] - merged_ts.P_sto_grid_feed[i]

        batDict = bat(i, lastrealCap, surplusminusbatgrid,
                      batMin, batMax, powerMin, powerMax, maxFeed)
        batPowers.append(batDict['P'])
        batCaps.append(batDict['Cap'])
        lastrealCap = batDict['Cap']

    merged_ts['sto_in_out_x'] = batPowers
    merged_ts['sto_cap_x'] = batCaps
    return merged_ts

def batPVLoad2newbilance(merged_ts, bat_dict):
    """
    Calculate the parameter values from the AMPL output to compare
    by numpy array calculations and add them with a ts in the end to
    the pandas dataframe.
    """

    stoCap = bat_dict['sto_cap_max']
    maxFeed = bat_dict['feed_in_limit']
    PVscale = bat_dict['scale_pv']
    loadscale = bat_dict['scale_load']

    merged_ts['PV_local_x'] = np.where(merged_ts.Load_Pel_x + merged_ts.sto_in_out_x >= merged_ts.PV1_Pel_x,
                                       merged_ts.PV1_Pel_x,
                                       np.where(merged_ts.Load_Pel_x + merged_ts.sto_in_out_x > 0,
                                                merged_ts.Load_Pel_x +
                                                merged_ts.sto_in_out_x,
                                                0))

    merged_ts['PV_self_x'] = np.where(merged_ts.PV1_Pel_x - merged_ts.sto_in_out_x > merged_ts.Load_Pel_x,
                                      merged_ts.Load_Pel_x,
                                      np.where(merged_ts.PV1_Pel_x - merged_ts.sto_in_out_x > 0,
                                               merged_ts.PV1_Pel_x -
                                               merged_ts.sto_in_out_x,
                                               0))

    merged_ts['PV_direct_x'] = np.where(merged_ts.PV_local_x - merged_ts.PV_self_x >= 0,
                                        merged_ts.PV_self_x,
                                        merged_ts.PV_local_x)

    merged_ts['PV_grid_feed_x'] = np.where(merged_ts.PV1_Pel_x - merged_ts.PV_local_x <= PVscale * maxFeed,
                                           merged_ts.PV1_Pel_x -
                                           merged_ts.PV_local_x,
                                           PVscale * maxFeed)

    merged_ts['Feed_limit_losses_x'] = np.where(merged_ts.PV1_Pel_x - merged_ts.PV_local_x > PVscale * maxFeed,
                                                merged_ts.PV1_Pel_x -
                                                merged_ts.PV_local_x -
                                                PVscale * maxFeed,
                                                0)

    merged_ts['grid_load_supply_x'] = merged_ts.Load_Pel_x - \
        merged_ts.PV_self_x

    merged_ts['sto_soc_x'] = merged_ts.sto_cap_x / stoCap

    merged_ts['sto_grid_discharge_x'] = np.where(merged_ts.sto_in_out_x < 0,
                                                 - merged_ts.sto_in_out_x -
                                                 (merged_ts.Load_Pel_x - merged_ts.PV_direct_x -
                                                  merged_ts.grid_load_supply_x),
                                                 0)

    return merged_ts

# CORE
if __name__ == "__main__":

    from sys import argv
    print(len(sys.argv))
    print(sys.argv[0])
    loopSubFiles(str(argv[1]), int(argv[2]), int(
        argv[3]), int(argv[4]), int(argv[5]))


import pandas as pd
import math
import copy
import numpy as np
import matplotlib.pyplot as plt
import pyecharts.options as opts
from pyecharts.charts import Line

class Toward:
    buy = "buy"
    sell = "sell"

class StrategyBase:
    """
    输入df必有字段
                close
    trade_date
    """

    def __init__(self, code, data: pd.DataFrame, ensure_profit = True):
        self.code = code
        self.data4cal = copy.deepcopy(data)  # 避免污染原始数据
        self.init_funds = 100000
        self.current_funds = self.init_funds
        self.current_hands = 0
        self.last_buy_date = None
        self.events_list = []

        self.logger = True
        self.ensure_profit = ensure_profit
        self.holding = False  # 持仓控制
        # self.final_stock_price = 0 删掉了，通过property实现
        # self.final_value = 0
        # self.inited = False #初始化控制，保证只初始化一次

    def commission_cal(self, price, volume, toward):
        # 过户费
        transfer_fee = 0
        # 印花税
        commission_tax = 0
        # 佣金
        commission_broker = 0
        if toward == Toward.buy:
            transfer_fee = volume * 100 * 0.0006
            transfer_fee = transfer_fee if transfer_fee > 1 else 1
            commission_broker = price * volume * 100 * 0.0003
            commission_broker = commission_broker if commission_broker > 5 else 5
        elif toward == Toward.sell:
            transfer_fee = volume * 100 * 0.0006
            transfer_fee = transfer_fee if transfer_fee > 1 else 1
            commission_broker = price * volume * 100 * 0.0003
            commission_broker = commission_broker if commission_broker > 5 else 5

            commission_tax = price * volume * 100 * 0.0001
        else:
            raise AttributeError
        return transfer_fee + commission_tax + commission_broker

    def potential_profit(self, data: pd.DataFrame, bought_date, potential_selling_day, current_position):
        bought_price = data.loc[bought_date, "close"]
        potential_selling_price = data.loc[potential_selling_day, "close"]
        commission_buy = self.commission_cal(bought_price, current_position, Toward.buy)
        commission_sell = self.commission_cal(potential_selling_price, current_position, Toward.sell)
        profit = (potential_selling_price - bought_price) * current_position * 100 - commission_buy - commission_sell
        return profit

    def hands_can_bought(self, money, price):
        assert (money >= 0 and price >= 0)
        potential_hands = math.floor(money / price / 100)
        left_money = money - potential_hands * 100 * price - self.commission_cal(price, potential_hands, Toward.buy)
        if left_money >= 0:
            return potential_hands
        elif potential_hands >= 1:
            return potential_hands - 1
        elif potential_hands == 0:
            return 0

    def final(self, df: pd.DataFrame):
        for date, data in df.iterrows():
            if (not self.holding) and data.buy:
                num_hands_to_buy = self.hands_can_bought(self.current_funds, data.close)
                self.current_hands += num_hands_to_buy
                cost = num_hands_to_buy * 100 * data.close + \
                       self.commission_cal(data.close, num_hands_to_buy, Toward.buy)

                self.events_list.append(("b", date, self.current_funds, # 记录买仓之前的历史资金，求单次收益
                                         self.commission_cal(data.close, num_hands_to_buy, Toward.buy), data.close))
                self.current_funds -= cost
                self.last_buy_date = date

                self.holding = True
                if self.logger:
                    print("Bought {} hands of {} on {} at {} price, cost {}".format(num_hands_to_buy, self.code, date,
                                                                              data.close, cost))
            elif self.holding and data.sell:
                if self.ensure_profit:

                    potential_profit = self.potential_profit(df, self.last_buy_date, date, self.current_hands)
                    if potential_profit > 0:
                        # 不能直接加 potential_profit, 否则买入手续费会计算两次
                        self.current_funds += self.current_hands * 100 * data.close - \
                                              self.commission_cal(data.close, self.current_hands, Toward.sell)
                        if self.logger:
                            print("Sold {} hands of {} on {} at {} price, profit {}, current funds {}".format(
                                self.current_hands,self.code, date, data.close, potential_profit,self.current_funds))
                        self.current_hands = 0  # 一次卖空
                        self.holding = False
                        self.events_list.append(("s", date, self.current_funds, # 记录平仓之后的资金，求单次收益
                                                 self.commission_cal(data.close, self.current_hands, Toward.sell), data.close))
                else:
                    self.current_funds += self.current_hands * 100 * data.close - \
                                          self.commission_cal(data.close, self.current_hands, Toward.sell)
                    potential_profit = self.potential_profit(df, self.last_buy_date, date, self.current_hands)
                    if self.logger:
                        print("Sold {} hands of {} on {} at {} price, profit {}, current funds {}".format(
                            self.current_hands, self.code, date, data.close, potential_profit, self.current_funds))
                    self.current_hands = 0  # 一次卖空
                    self.holding = False
                    self.events_list.append(("s", date, self.current_funds,
                                            self.commission_cal(data.close, self.current_hands, Toward.sell), data.close))
    @property
    def final_stock_price(self):
        return self.data4cal.close.iloc[-1]
    @property
    def final_value(self):
        #最终可能会低于bought的钱数，因为我们又赔了
        return self.current_funds + self.current_hands * 100 * self.final_stock_price
    @property
    def final_return(self):
        return self.final_value/self.init_funds - 1
    @property
    def return_list(self):
        result = []
        for i in range(0, len(self.events_list)//2*2, 2): #//2*2 保证b-s成对出现
            res = self.events_list[i+1][2] / self.events_list[i][2] - 1
            result.append(res)
        return result
    @property
    def ave_return(self):
        return np.mean(self.return_list)
    @property
    def ave_return_dayly(self):
        return np.mean(self.return_dayly_list)
    @property
    def ave_return_yearly(self):
        return np.mean(self.return_yearly_list)

    @property
    def commission_total(self):
        result = 0
        for data_tuple in self.events_list:
            result += data_tuple[3]
        return result
    @property
    def return_yearly_list(self):
        result = []
        for i in range(0, len(self.events_list)//2*2, 2): #//2*2 保证b-s成对出现
            pre_return = self.events_list[i+1][2] / self.events_list[i][2]
            interval = self.events_list[i+1][1] - self.events_list[i][1]
            result.append(self.return_days2year(pre_return, interval.days)-1)
        return result
    @property
    def return_dayly_list(self):
        result = []
        for i in range(0, len(self.events_list)//2*2, 2): #//2*2 保证b-s成对出现
            pre_return = self.events_list[i+1][2] / self.events_list[i][2]
            interval = self.events_list[i+1][1] - self.events_list[i][1]
            result.append(self.return_days2day(pre_return, interval.days)-1)
        return result
    @property
    def prob_win(self):
        pos = 0
        neg = 0
        for r in self.return_list:
            if r > 0:
                pos += 1
            else: #将nan也记作失败
                neg += 1
            # elif r < 0:
            #     neg +=1
        try:
            prob = pos/(pos+neg)
        except:
            prob = 0
        return prob, pos, neg

    # 用于实时跟踪当前权益变化
    def value_list(self):
        pass

    def _init_process(self):
        raise NotImplementedError

    #尽量，不要传入参数，以统一格式
    def buy_condition(self):
        raise NotImplementedError

    def sell_condition(self):
        raise NotImplementedError

    def get_events(self, begin_date, end_date): #datetime 类型
        result = []
        for data in self.events_list:
            if data[1] >= begin_date and data[1] <= end_date:
                result.append((data[0], data[1]))
        return result

    def draw(self):
        l = (
            Line()
            .add_xaxis(xaxis_data=list(self.data4cal.index))
            .add_yaxis(series_name=self.code,
                      y_axis=list(self.data4cal.close))
        )
        for i in range(0, len(self.events_list)//2*2, 2):
            x = [self.events_list[i][1], self.events_list[i+1][1]]
            y = [self.events_list[i][4], self.events_list[i+1][4]]
            tmp_line = (
                Line()
                .add_xaxis(xaxis_data = x)
                .add_yaxis(
                    series_name="",
                    y_axis = y,
                    symbol="triangle",
                    symbol_size=20,
                    linestyle_opts=opts.LineStyleOpts(color="green", width=4, type_="dashed"),
                    itemstyle_opts=opts.ItemStyleOpts(
                        border_width=3,border_color="blue",color="blue"
                    )
                )
            )
            l.overlap(tmp_line)

        return l

    def run(self):
        self._init_process() #后续可以增加 从值中提取，不放在构造函数内部
        self.final(self.data4cal)

    def read(self,df):
        self.data4cal = df

    def return_days2year(self,return_days, days):
        return return_days**(365/days) #两种理解，1.365天里有多少个间隔days的间隔，然后取幂运算 2.先开days次方，求日收益，然后取365次方求年
    def return_days2day(self,return_days, days):
        return return_days**(1/days)


a=0
s=input()
for i in range(len(s)):
    m=list(s)
    m.insert(0, m[len(m)-1])
    del m[len(m)-1]
    s=''.join(m)
    j=s
    for p in range(len(s)//2):
        if '()' in s:
            s=s.replace('()','')
        elif '[]' in s:
            s=s.replace('[]','')
        elif '{}' in s:
            s=s.replace('{}','')
    if s=='':
        a=a+1
    s=j
print(a)


from sqlalchemy.orm import Session
from models.room import Room
from schemas.room_schema import RoomCreate, RoomUpdate, RoomUpdateByIsActive

def get_rooms(db:Session):
    return db.query(Room).all()

def get_room(db:Session, room_id : int):
    return db.query(Room).filter(Room.id==room_id).first()

def insert_room(db:Session, room:RoomCreate):
    new_room = Room(name = room.name, cinema_id = room.cinema_id)
    db.add(new_room)
    db.commit()
    db.refresh(new_room)
    return new_room

def update_room(db:Session, room_id:int, room:RoomUpdate):
    edit_room = db.query(Room).get(room_id)
    edit_room.name = room.name
    edit_room.cinema_id = room.cinema_id
    edit_room.update_at = room.update_at
    db.commit()
    return edit_room

def update_room_by_is_active(db:Session, room_id:int, room:RoomUpdateByIsActive):
    edit_room           = db.query(Room).get(room_id)
    edit_room.is_active = room.is_active
    edit_room.update_at = room.update_at
    db.commit()
    return edit_room

def delete_room(db:Session, room_id:int):
    room = db.query(Room).get(room_id)
    db.delete(room)
    db.commit()
    return "delete"


from pathlib import Path
from urllib.request import urlretrieve

import pandas as pd
import seaborn as sns
import sklearn.datasets

# _DATA_DIR = Path(__file__).parent / "toydata"
_DATA_DIR = Path("").resolve() / "data"/"toydata"

def sklearn_data(name: str, bunch):
    df: pd.DataFrame = bunch["frame"]
    desc = bunch["DESCR"]

    with open(_DATA_DIR / f"{name}_desc.txt", "w") as f:
        f.write(desc)

    # print(f"shape:\n{df.shape}")
    # print(f"head:\n{df.head()}")
    # print(f"first row:\n{df.iloc[0,:]}")

    df.to_csv(_DATA_DIR / f"{name}.csv", index=False)

def sns_data(name: str):
    df: pd.DataFrame = sns.load_dataset(name)
    df.to_csv(_DATA_DIR / f"{name}.csv", index=False)

if __name__ == "__main__":
    sklearn_data("diabetes", sklearn.datasets.load_diabetes(as_frame=True))
    sklearn_data("iris", sklearn.datasets.load_iris(as_frame=True))
    sklearn_data("wine", sklearn.datasets.load_wine(as_frame=True))

    # sns.get_dataset_names()
    names = [
        "anagrams",
        "anscombe",
        "attention",
        "brain_networks",
        "car_crashes",
        "diamonds",
        "dots",
        "exercise",
        "flights",
        "fmri",
        "gammas",
        "geyser",
        # "iris",
        "mpg",
        "penguins",
        "planets",
        "taxis",
        "tips",
        "titanic",
    ]

    for name in names:
        sns_data(name)

    url = "https://archive.ics.uci.edu/ml/machine-learning-databases/00350/default%20of%20credit%20card%20clients.xls"
    urlretrieve(url, _DATA_DIR / "uci_credit_card.xls")


import math
import pygame
from pygame.locals import *

import ScummPyUtils
import ScummPyAnimation
import Textify

class Character:

	def __init__(self, ResourcePath="resources/", CharacterName="bozo"):

		self.ResourcePath=ResourcePath

		#Read in Character properties
		charFP = open(self.ResourcePath + "characters/" + CharacterName + "/" + CharacterName + ".scd", "r")
		for line in charFP:
			if len(line)>0:
				words = line.split()
				if words[0] == 'StartX':
					self.x = int(words[1])
				if words[0] == 'StartY':
					self.y = int(words[1])
				if words[0] == 'Speed':
					self.Speed = int(words[1])
				if words[0] == 'Layer':
					self.Layer = int(words[1])
				if words[0] == 'Startroom':
					self.Startroom = words[1]
				if words[0] == 'WalkSteps':
					self.WalkSteps = int(words[1])
				if words[0] == 'WalkLeftRow':
					self.WalkLeftRow = int(words[1])
				if words[0] == 'WalkRightRow':
					self.WalkRightRow = int(words[1])
				if words[0] == 'WalkUpRow':
					self.WalkUpRow = int(words[1])
				if words[0] == 'WalkDownRow':
					self.WalkDownRow = int(words[1])
				if words[0] == 'SizeX':
					self.SizeX = int(words[1])
				if words[0] == 'SizeY':
					self.SizeY = int(words[1])
				if words[0] == 'FontFile':
					self.FontFile = words[1]
				if words[0] == 'TalkX':
					self.TalkX = int(words[1])
				if words[0] == 'TalkY':
					self.TalkY = int(words[1])

		charFP.close()

		self.scale=1
		self.xDest=self.x
		self.yDest=self.y
		self.pos=(int(self.x),int(self.y))

		self.xVel=0
		self.yVel=0

		self.SheetX=0
		self.SheetY=0
		self.WalkStep=0

		self.blitRect = pygame.Rect(0,0,self.SizeX,self.SizeY)

		#Masks and Sprite Sheet
		self.imPlayer =  pygame.image.load(self.ResourcePath + "characters/" + CharacterName + "/" + CharacterName + "Scale.png")
		self.imLayer = pygame.image.load(ResourcePath + "rooms/" + self.Startroom + "/room" + self.Startroom + "_layers.png")
		self.imWM = pygame.image.load(ResourcePath + "rooms/" + self.Startroom + "/room" + self.Startroom + "_walkmask.png")
		self.imSheet = pygame.image.load(self.ResourcePath + "characters/" + CharacterName + "/" + CharacterName + "Sheet.png")

		#This is a "hook" for allowing the main.py to attach animations to a character.
		#Display code will need to be updated to add it...
		self.Animations = dict()

		#Speaking setup
		self.TalkFont = Textify.BlitFont(ResourcePath, self.FontFile)
		self.SayString = ""
		self.TicsToTalk = 0

	def GoTo(self, destRoom, coords):
		self.imLayer = pygame.image.load(destRoom.ResourcePath + "rooms/" + destRoom.RoomName + "/room" + destRoom.RoomName + "_layers.png")
		self.imWM = pygame.image.load(destRoom.ResourcePath + "rooms/" + destRoom.RoomName + "/room" + destRoom.RoomName + "_walkmask.png")
		self.pos = coords
		self.x = coords[0]
		self.y = coords[1]
		self.xDest = coords[0]
		self.yDest = coords[1]

	def WalkTo(self, WalkX, WalkY):
		#print "X " + str(WalkX) + " Y " + str(WalkY) + " "

		#Set Destination
		self.xDest=WalkX
		self.yDest=WalkY

		#Set Velocity
		Mag=math.sqrt((self.xDest-self.x)*(self.xDest-self.x)+(self.yDest-self.y)*(self.yDest-self.y))
		self.xVel=-self.Speed*(self.x-self.xDest)/Mag
		self.yVel=-self.Speed*(self.y-self.yDest)/Mag
		#print ("X: "+str(self.x)+"Y:"+str(self.y))

	def Update(self):
		#print(self.x)

		Distance = ((self.xDest-self.x)*(self.xDest-self.x)+(self.yDest-self.y)*(self.yDest-self.y))
		MaskPix=self.imWM.get_at((int(self.x),int(self.y)))
		self.scale=float(MaskPix[0])/255
		if (Distance<25):
			#print("Stopping")
			self.xVel=0
			self.yVel=0
		else:
			self.x=self.xVel+self.x
			self.y=self.yVel+self.y
			MaskPix=self.imWM.get_at((int(self.x),int(self.y)))
			if(MaskPix[0]==0):
				self.x=-self.xVel+self.x
				self.y=-self.yVel+self.y
				self.xVel=0
				self.yVel=0

			#Animation
			self.WalkStep=(self.WalkStep+1)%self.WalkSteps

			if(self.yVel>3):
				self.SheetX = self.WalkStep*self.SizeX
				self.SheetY = self.WalkDownRow*self.SizeY
			if(self.yVel<-3):
				self.SheetX = self.WalkStep*self.SizeX
				self.SheetY = self.WalkUpRow*self.SizeY
			if(self.xVel>3):
				self.SheetX = self.WalkStep*self.SizeX
				self.SheetY = self.WalkRightRow*self.SizeY
			if(self.xVel<-3):
				self.SheetX = self.WalkStep*self.SizeX
				self.SheetY = self.WalkLeftRow*self.SizeY

		self.Layer=ScummPyUtils.GetIndex(self.imLayer.get_at((int(self.x),int(self.y))))
		self.pos=(int(self.x),int(self.y))

	def Display(self, imOutPut):
		#Spit to display
		self.imPlayer.fill((255,0,255))
		self.imPlayer.set_colorkey((255,0,255)) #probably inefficient, move to constructor if laggy?

		Middle = self.SizeX/2

		#Animation Handling
		AnyAnimationOn=False
		for k,Anim in self.Animations.iteritems():
			if Anim.Running:
				AnyAnimationOn=True
				Anim.Display(imOutPut,(0,0))

		if not AnyAnimationOn:
			self.imPlayer.blit(self.imSheet,(-self.SheetX,-self.SheetY))
			imOutPut.blit(pygame.transform.scale(self.imPlayer,(int(self.SizeX*self.scale),int(self.SizeY*self.scale))), (self.x-int(Middle*self.scale),self.y-int(self.SizeY*self.scale)))
			self.blitRect.left = self.SheetX
			self.blitRect.top = self.SheetY
			#imOutPut.blit(pygame.transform.scale(self.imSheet,(int(self.SizeX*self.scale),int(self.SizeY*self.scale))), (self.x-int(Middle*self.scale),self.y-int(self.SizeY*self.scale)),self.blitRect)

		if self.TicsToTalk > 0:
			self.TalkFont.BlitTextCenter(imOutPut,(self.pos[0]+self.TalkX,self.pos[1]+self.TalkY),self.SayString)
			self.TicsToTalk -= 1

	def SetWalkMask(self, newWalkMask):
		self.imWM.fill((0,0,0))
		self.imWM.blit(newWalkMask,(0,0))

	def Say(self, inputString = "!!!", duration = "15"):
		self.TicsToTalk = duration
		self.SayString = inputString


from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from .models import Peca, Servico

@receiver(post_delete, sender=Peca)
@receiver(post_save, sender=Peca)
def update_custo_peca_on_revisao(sender, instance, **kwargs):
    instance.revisao.save()

@receiver(post_delete, sender=Servico)
@receiver(post_save, sender=Servico)
def update_custo_servico_on_revisao(sender, instance, **kwargs):
    instance.revisao.save()