from typing import TYPE_CHECKING

from src import utilities
from src.LevelObject import LevelObject
from src.utilities import Colors, Position
from src.WaterObject import WaterObject

if TYPE_CHECKING:
    from src.level import Level

class WaterFactory:
    poss_water: list[LevelObject] = []

    left: int = 0  # for Rect
    top: int = 0  #  for Rect

    color = Colors.BLUE_LIGHT.value  # for draw rect

    def __init__(self, level: "Level") -> None:
        print("init Water")

        self.HEIGHT_GS = level.HEIGHT_GS
        self.GRID_SIZE = level.GRID_SIZE

        self.width: int = level.GRID_SIZE  # for Rect and Surface
        self.height: int = level.GRID_SIZE  # for Rect ans Surface
        self.rect = (self.width, self.height)

        positions = level.paths.add_water_above_rock(level.GRID_SIZE)

        for position in positions:
            water_object = WaterObject(self.rect, position)
            self.poss_water.append(water_object)

        self.water_collect_positions = self.get_water_collect_positions(level)

        self.water_waterline_positions = self.get_water_waterline_positions(level)

    @property
    def water_objects(self) -> list[LevelObject]:
        res_set = set(self.water_collect_positions + self.water_waterline_positions)
        return list(res_set)

    @property
    def water_line(self) -> float:
        grid_height = self.HEIGHT_GS - self.GRID_SIZE * 2
        water_level = grid_height * (2 / 3)
        return water_level

    # def add_water_above_rock(self, level: "Level") -> None:
    #     for path in level.paths:
    #         new_position = utilities.get_distance_in_direction(
    #             path, "DOWN", level.GRID_SIZE
    #         )
    #         if new_position not in level.paths:
    #             position = Position(path.x, path.y)
    #             water_object = WaterObject(self.rect, position)
    #             self.poss_water.append(water_object)

    def get_position_either_side(self, position: Position, level: "Level"):
        direction = "LEFT"
        grid_size = level.GRID_SIZE
        position_left = utilities.get_distance_in_direction(
            position, direction, grid_size
        )

        direction = "RIGHT"
        grid_size = level.GRID_SIZE
        position_right = utilities.get_distance_in_direction(
            position, direction, grid_size
        )

        return [position_left, position_right]

    def get_water_collect_positions(self, level: "Level") -> list[LevelObject]:
        positions = level.paths.get_list_position_difference(self.poss_water)
        run = True
        while run:
            start = len(self.poss_water)
            for index, p in enumerate(self.poss_water):
                if any(
                    item in self.get_position_either_side(p.position, level)
                    for item in positions
                ):
                    self.poss_water.pop(index)
            end = len(self.poss_water)

            run = start != end

        return self.poss_water

    def get_water_waterline_positions(self, level: "Level") -> list[LevelObject]:
        positions = level.paths.filter_positions_above_height(self.water_line)

        self.water_waterline_positions: list[LevelObject] = [
            WaterObject(self.rect, p) for p in positions
        ]

        return self.water_waterline_positions


from typing import List
from git import Head
from enum import Enum

class BranchStatus(Enum):
    Sync = 1
    NotSync = 2
    Behind = 4
    MergedAndDeleted = 5

class GitBranch:

    @staticmethod
    def branches_to_clean(base_branch, repo) -> List[Head]:
        return [
            branch
            for branch in repo.branches
            if (
                GitBranch.has_commit_in_base(base_branch, branch) and
                not GitBranch.is_base_remote(base_branch, branch, repo) and
                not GitBranch.is_current_branch(branch, repo)
            )
        ]

    @staticmethod
    def is_base_remote(base_branch, branch, repo):
        for remote in repo.remotes:
            name = f"{remote.name}/{branch.name}"
            if name == base_branch.name:
                return True
        return False

    @staticmethod
    def is_current_branch(branch, repo):
        return GitBranch.branch_name(repo.head) == branch.name

    @staticmethod
    def has_commit_in_base(base_branch, branch):
        commit = branch.commit
        tree = base_branch.commit
        for item in tree.traverse():
            if item.hexsha == commit.hexsha:
                return True

        return False

    @staticmethod
    def branch_name(branch):
        try:
            return branch.reference.name
        except:
            return "Detached Head"


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#%% ALBUM DE FIGURITAS - COMPRANDO DE 1 EN 1.

import random
import numpy as np

def crear_album(figus_total):
    album = np.zeros(figus_total)
    return album

def album_incompleto(A):
    if 0 in A:
        return True
    else:
        return False

def comprar_figu(figus_total):
    fig = random.randint(0,figus_total-1)
    return fig

def cuantas_figus(figus_total):
    alb = crear_album(figus_total)
    figus = 0
    while album_incompleto(alb) == True:
        me_toco_esta = comprar_figu(figus_total)
        alb[me_toco_esta] = 1
        figus += 1
    return figus

# n_repeticiones = 1000
# figus_total = 6
# simulaciones = [cuantas_figus(figus_total) for _ in range(n_repeticiones)]
# print(f'Si comprás de a 1 figurita, para completar el album de 6 deberías comprar {int(np.mean(simulaciones))} en promedio')

n_repeticiones=100
figus_total = 670
simulaciones = [cuantas_figus(figus_total) for _ in range(n_repeticiones)]
print(f'Si comprás de a 1 figurita, para completar el album de 670 deberías comprar {int(np.mean(simulaciones))} en promedio')
#%% AHORA CON PAQUETES DE 5 FIGUS

import random
import numpy as np

def crear_album(figus_total):
    album = np.zeros(figus_total)
    return album

def album_incompleto(A):
    if 0 in A:
        return True
    else:
        return False

def comprar_paquete(figus_total, figus_paquete):
    paquete =[random.randint(0,figus_total - 1) for _ in range(figus_paquete)]
    return paquete

def cuantos_paquetes(figus_total, figus_paquete):
    alb = crear_album(figus_total)
    paq = 0
    while album_incompleto(alb) == True:
        me_tocaron = comprar_paquete(figus_total,figus_paquete)
        for fig in me_tocaron:
            alb[fig] = 1
        paq += 1
    return paq

n_repeticiones = 100
figus_total = 670
figus_paquete = 5
simulaciones = [cuantos_paquetes(figus_total, figus_paquete) for _ in range(n_repeticiones)]
print(f'Para completar el album se necesitaron en promedio {int(np.mean(simulaciones))} paquetes de figus.')

#%% PLOT QUE EXPLICITARON EN EL GIT
import matplotlib.pyplot as plt
def calcular_historia_figus_pegadas(figus_total, figus_paquete):
    album = crear_album(figus_total)
    historia_figus_pegadas = [0]
    while album_incompleto(album):
        paquete = comprar_paquete(figus_total, figus_paquete)
        while paquete:
            album[paquete.pop()] = 1
        figus_pegadas = (album>0).sum()
        historia_figus_pegadas.append(figus_pegadas)
    return historia_figus_pegadas

#COMENTO LAS SIGUIENTES PORQUE LAS TENGO DE ANTES
#figus_total = 670
#figus_paquete = 5

plt.plot(calcular_historia_figus_pegadas(figus_total, figus_paquete))
plt.xlabel("Cantidad de paquetes comprados.")
plt.ylabel("Cantidad de figuritas pegadas.")
plt.title("La curva de llenado se desacelera al final")
plt.show()

#%%


import unittest
from json import JSONDecodeError
from app import PathParameterRenderer

class TestPathParameterRenderer(unittest.TestCase):
    def setUp(self):
        self.template = '{"message": "Hello, {{name}}! Your ID is {{id}}."}'
        self.path_params = {
            "name": "John Doe",
            "id": "12345"
        }
        self.renderer = PathParameterRenderer(self.template)

    def test_render_with_valid_params(self):
        expected_output = {'message': 'Hello, John Doe! Your ID is 12345.'}
        self.assertEqual(self.renderer.render(self.path_params), expected_output)

    def test_render_with_invalid_params(self):
        path_params = {"age": "23"}
        expected_output = {'message': 'Hello, {{name}}! Your ID is {{id}}.'}
        self.assertEqual(self.renderer.render(path_params), expected_output)

    def test_render_with_empty_template(self):
        renderer = PathParameterRenderer("")
        with self.assertRaises(JSONDecodeError):
            renderer.render(self.path_params)

    def test_render_with_missing_parameter(self):
        template = '{"message": "Hello, {{name}}! Your age is {{age}}."}'
        renderer = PathParameterRenderer(template)
        expected_output = {"message": "Hello, John Doe! Your age is {{age}}."}
        self.assertEqual(renderer.render(self.path_params), expected_output)


import csv
import json

for i in range(1,8):
	name=str(i)+"-1-2017"
	infile = open(name+".log", "r")
	outfile = open(name+".csv", "w")

	f = csv.writer(outfile)

	logs=[]

	for line in infile:
		logs.append(json.loads(line))

	f.writerow(["username","event_source","name","accept_language","time","agent","page","host","referer","context_user_id","context_org_id","context_path","ip","event","event_type"])

	for log in logs:
		if log.has_key("name")!=True:
			log["name"]=None
		f.writerow([log["username"],log["event_source"],log["name"],log["accept_language"],log["time"],log["agent"],log["page"],log["host"],log["referer"],log["context"]["user_id"],log["context"]["org_id"],log["context"]["course_id"],log["context"]["path"],log["ip"],log["event"],log["event_type"]])
	#print log["event_type"].replace(";","_")
#print logs[81]


import boto3
import json
import os
import sys

readme = """
	A Python script that can be used to upload a directory contents to a bucket in S3
	Used for managing simple S3-hosted websites.

	Usage:

		python upload.py

	This uploads all valid files (except those in the excluced directories specified in config)

		python upload.py index.html error.html css/main.css

	This only uploads the specified files
"""

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

CFG_FILE = "upload_cfg.json"
# CFG file needs to look like so:
demo_cfg_file = """
	{
		"site_name": "<name of the S3 bucket (also domain name of your site",
		"region_name": "<region in which your S3 bucket exists",
		"aws_secret": "<your aws secret>",
		"aws_access_key_id": "<your aws key id>"
		"excluded_dirs": ["assets"]
	}
"""

# map file extensions to ContentTypes
CONTENT_TYPES = {
	'jpg': 'image/jpeg',
	'jpeg': 'image/jpeg',
	'css': 'text/css',
	'html': 'text/html',
	'png': 'image/png',
	'js': 'text/javascript',
	'ico': 'image/x-icon',
	'gif': 'image/gif',
	'svg': 'image/svg+xml',
	'txt': 'text/plain'
}

def get_uploadable_files(directory, excluded_dirs):
	files = []
	for f in os.listdir(directory):
		if f != sys.argv[0] and not f.startswith('.'):
			dir_file = os.path.join(directory.replace('.', ''), f)
			if os.path.isfile(dir_file):
				files.append(dir_file)
			if os.path.isdir(dir_file):
				if dir_file not in excluded_dirs:
					files = files + get_uploadable_files(dir_file, excluded_dirs)
	return files

def get_content_type(filepath):
	suffix = filepath.split('.')[-1]
	return CONTENT_TYPES.get(suffix, None)

def load_config():
	try:
		f = open(os.path.join(BASE_DIR, CFG_FILE), 'r')
	except IOError:
		print("Failed to load config file: %s" % CFG_FILE)
		sys.exit()
	try:
		cfg = json.loads(f.read())
	except ValueError:
		print("Could not parse config file: %s" % CFG_FILE)
		sys.exit()
	return cfg

if __name__ == "__main__":
	cfg = load_config()
	s3 = boto3.client('s3',
		aws_access_key_id=cfg['aws_access_key_id'],
		aws_secret_access_key=cfg['aws_secret'],
		region_name=cfg['region_name']
	)
	files = sys.argv[1:] if len(sys.argv) > 1 else []
	files = files or get_uploadable_files('.', cfg['excluded_dirs'])
	if files:
		print("Uploading files to: %s" % cfg['site_name'])
	for f in files:
		ct = get_content_type(f)
		if ct:
			print("Uploading file: %s" % f)
			try:
				s3.upload_file(os.path.join(BASE_DIR, f), cfg['site_name'], f,  ExtraArgs={'ACL':'public-read', 'ContentType': ct})
			except OSError:
				print("ERROR! Could not find file to upload: %s" % f)
		else:
			print("Not uploading unknown file type: %s" % f)
	print("Done!")


import random

def monteCarlo(preds, cnf, n):
  pos = 0
  for i in range(n):
    sampled = {}
    for name in preds:
      if random.random() < preds[name]:
        sampled[name] = 1
      else:
        sampled[name] = 0
    for dnf in cnf:
      flag = True
      for name in dnf:
        if sampled[name] == 0:
          flag = False
          break
      if flag:
        pos += 1
        break
  return pos*1.0/n

if __name__ == '__main__':
  pa = 0.4
  pb = 0.5
  pc = 0.7
  assumption = 1-((1-pa*pb)*(1-pb*pc)*(1-pc*pa))
  preds = {
    'a': 0.4,
    'b': 0.5,
    'c': 0.7
  }
  cnf = [
    ['a', 'b'],
    ['b', 'c'],
    ['c', 'a']
  ]
  simulated = monteCarlo(preds, cnf, 100000)
  print(assumption)
  print(simulated)


"""
Utils that support fringe DataSet features.
"""
import os
import tempfile
import logging
import json
from pbcore.util.Process import backticks

log = logging.getLogger(__name__)

def consolidateBams(inFiles, outFile, filterDset=None):
    """Take a list of infiles, an outFile to produce, and optionally a dataset
    (filters) to provide the definition and content of filtrations."""
    if filterDset and filterDset.filters:
        finalOutfile = outFile
        outFile = _infixFname(outFile, "_unfiltered")
    _mergeBams(inFiles, outFile)
    if filterDset and filterDset.filters:
        _filterBam(outFile, finalOutfile, filterDset)
        outFile = finalOutfile
    _indexBam(outFile)
    _pbindexBam(outFile)

def _pbindexBam(fname):
    cmd = "pbindex {i}".format(i=fname)
    log.info(cmd)
    o, r, m = backticks(cmd)
    if r != 0:
        raise RuntimeError(m)

def _sortBam(fname):
    tmpname = _infixFname(fname, "_sorted")
    cmd = "bamtools sort -in {i} -out {o}".format(i=fname, o=tmpname)
    log.info(cmd)
    o, r, m = backticks(cmd)
    if r != 0:
        raise RuntimeError(m)
    _mvFile(tmpname, fname)

def _mvFile(inFname, outFname):
    cmd = "mv {i} {o}".format(i=inFname,
                              o=outFname)
    log.info(cmd)
    o, r, m = backticks(cmd)
    if r != 0:
        raise RuntimeError(m)

def _cpFile(inFname, outFname):
    cmd = "cp {i} {o}".format(i=inFname,
                              o=outFname)
    log.info(cmd)
    o, r, m = backticks(cmd)
    if r != 0:
        raise RuntimeError(m)

def _indexBam(fname):
    cmd = "samtools index {i}".format(i=fname)
    log.info(cmd)
    o, r, m = backticks(cmd)
    if r != 0:
        raise RuntimeError(m)

def _mergeBams(inFiles, outFile):
    if len(inFiles) > 1:
        cmd = "bamtools merge -in {i} -out {o}".format(i=' -in '.join(inFiles),
                                                       o=outFile)
        log.info(cmd)
        o, r, m = backticks(cmd)
        if r != 0:
            raise RuntimeError(m)
    else:
        _cpFile(inFiles[0], outFile)

def _maxDigits(numItems):
    return len(str(numItems))

def _filterBam(inFile, outFile, filterDset):
    tmpout = tempfile.mkdtemp(suffix="consolidation-filtration")
    filtScriptName = os.path.join(tmpout, "filtScript.json")
    _emitFilterScript(filterDset, filtScriptName)
    cmd = "bamtools filter -in {i} -out {o} -script {s}".format(
        i=inFile, o=outFile, s=filtScriptName)
    log.info(cmd)
    o, r, m = backticks(cmd)
    if r != 0:
        raise RuntimeError(m)

def _infixFname(fname, infix):
    prefix, extension = os.path.splitext(fname)
    return prefix + infix + extension

def _emitFilterScript(filterDset, filtScriptName):
    bamtoolsFilters = ['reference', 'insertSize', 'mapQuality', 'name',
                       'position', 'queryBases']
    filterMap = {'rname': 'reference',
                 'pos': 'position',
                 'length': 'queryBases',
                 'qname': 'name',
                 'rq': 'mapQuality'}
    cheapFilters = True
    for filt in filterDset.filters:
        for req in filt:
            if not filterMap.get(req.name):
                cheapFilters = False
    if cheapFilters:
        script = {"filters":[]}
        for filt in filterDset.filters:
            filtDict = {}
            for req in filt:
                name = filterMap[req.name]
                if name == 'reference':
                    if req.operator == '=' or req.operator == '==':
                        filtDict[name] = req.value
                    else:
                        raise NotImplementedError()
                else:
                    filtDict[name] = req.operator + req.value
            script['filters'].append(filtDict)
    else:
        names = [rec.qName for rec in filterDset]
        script = {"filters":[{"name": name} for name in names]}
    with open(filtScriptName, 'w') as scriptFile:
        scriptFile.write(json.dumps(script))


# import libraries
import time
import datetime
import pandas as pd
from rich import print as rprint
from rich.pretty import pprint
from rich.progress import Progress
import re
from rich.progress import track
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import ElementClickInterceptedException, TimeoutException
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options
from selenium.webdriver import Firefox
# ------------------------------------------------------------------
# TODO: functions
# ------------------------------------------------------------------

# This function search each element of the document DOM

# sudo apt install firefox
# pip install webdriver-manager
# sudo apt install firefox-geckodriver

def findElementTextBySelector(selector, exception):
    try:
        element = i.find_element(
            By.CSS_SELECTOR, selector).text.strip().upper()
    except:
        element = exception
    return element

def findElementNumberBySelector(selector, exception):
    try:
        element = i.find_element(
            By.CSS_SELECTOR, selector).text.replace('$', '')
        element = "".join([ch for ch in element if ch.isdigit()])
    except:
        element = exception
    return element

def findElementBy(by, selector, t):
    open_modal = driver.find_element(by, selector)
    open_modal.click()
    time.sleep(t)

def findElementByAndSendKey(by, selector, key, t):
    open_modal = driver.find_element(by, selector)
    open_modal.click()
    open_modal.send_keys(key)
    open_modal.send_keys(Keys.TAB)
    time.sleep(t)

def scrollDownPage(driver, t):
    time.sleep(t)
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

def scrollDownFullPage(driver):
    height = driver.execute_script("return document.body.scrollHeight")
    for i in range(height):
        # scroll by 10 on each iteration
        driver.execute_script('window.scrollBy(0,10)')
        # reset height to the new height after scroll-triggered elements have been loaded.
        height = driver.execute_script("return document.body.scrollHeight")
        time.sleep(0.0005)

# Function Beatiful View

def process_data():
    time.sleep(0.02)

# Date

today = datetime.date.today()

# Categories of brands that should be considered for search results
categories = ['vinos', 'licores', 'tequilas']

shops = {'Bogotá, D.c.', 'Medellín', 'Barranquilla'}

# ------------------------------------------------------------------
# TODO: Extract the data for shop EXITO
# ------------------------------------------------------------------

for city in shops:
    for category in categories:
        # Bar progress -> comment
        for _ in track(range(100), description=f'[green]Iniciando Scraping en Dislicores ciudad {city} categoria: {category}'):
            process_data()
        # Initialized by selenium driver with options and optmizer
        options = Options()
        options.set_preference("network.http.pipelining", True)
        options.set_preference("network.http.proxy.pipelining", True)
        # options.set_preference("network.http.pipelining.maxrequests", 8)
        # options.set_preference("content.switch.threshold", 250000)
        # options.set_preference("browser.cache.memory.capacity", 65536)
        options.set_preference("general.startup.browser", False)
        # Disable reader, we won't need that.
        options.set_preference("reader.parse-on-load.enabled", False)
        options.set_preference("browser.pocket.enabled", False)
        options.set_preference("loop.enabled", False)
        # Text on Toolbar instead of icons
        options.set_preference("browser.chrome.toolbar_style", 1)
        # Don't show thumbnails on not loaded images.
        options.set_preference(
            "browser.display.show_image_placeholders", False)
        # Don't show document colors.
        options.set_preference("browser.display.use_document_colors", False)
        # Don't load document fonts.
        options.set_preference("browser.display.use_document_fonts", 0)
        # Use system colors.
        options.set_preference("browser.display.use_system_colors", True)
        # Autofill on forms disabled.
        options.set_preference("browser.formfill.enable", False)
        # Delete temprorary files.
        options.set_preference("browser.helperApps.deleteTempFileOnExit", True)
        options.set_preference("permissions.default.image", 2)
        # Disable tabs, We won't need that.
        options.set_preference("browser.tabs.forceHide", True)
        # Disable autofill on URL bar.
        options.set_preference("browser.urlbar.autoFill", False)
        # Disable autocomplete on URL bar.
        options.set_preference("browser.urlbar.autocomplete.enabled", False)

        driver = webdriver.Firefox(options=options)
        # driver.maximize_window()
        # driver.set_window_position(2000,0)
        # driver.set_window_size(1180, 1000)
        driver.set_window_position(-50, -50)
        # driver.set_window_size(1500, 1050)
        driver.maximize_window()

        # Open the Page

        driver.get(f"https://www.dislicores.com/{category}")

        time.sleep(10)

        # Click on Modal Window
        try:
            findElementBy(
                By.XPATH, "//span[@class='vtex-minicart-2-x-minicartIconContainer gray relative']//*[@class=' ']", 2)
        except:
            pass
        # ActionChains(driver)

        # Click for city selection
        findElementBy(
            By.XPATH, "//div[@class=' css-1fvtkye']", 2)
        # Select City
        findElementBy(
            By.XPATH, "//div[@id='react-select-2-option-9']", 2)
        # Click Box
        findElementBy(
            By.XPATH, "//span[@class='dislicoresqa-custom-app-2-x-m_custom_age_verification__checkbox mr3 ']", 2)
        # Click button continue
        findElementBy(
            By.XPATH, "//button[normalize-space()='Continuar']", 10)

        scrollDownPage(driver, 5)
        # scrollDownFullPage(driver)

        initial_XPATH = "//div[contains(@class,'vtex-button__label flex items-center justify-center h-100 ph5')]"
        # define the max clicks for page for default 30
        max_click_SHOW_MORE = 45
        # count the number of clicks
        count = 1
        # This loop search the button load more and apply the click until the end of page
        while count <= max_click_SHOW_MORE:
            try:
                WebDriverWait(driver, 30).until(
                    EC.visibility_of_all_elements_located((By.XPATH, initial_XPATH)))
                WebDriverWait(driver, 20).until(
                    EC.element_to_be_clickable((By.XPATH, initial_XPATH))).click()
                count += 1
                time.sleep(10)
                # Bar progress -> comment
                for i in track(range(4), description=f"[red]Explorando Pagina Web iter {count - 1}.........."):
                    time.sleep(1)

            except TimeoutException:
                break

        # Search the elements of the page
        items = driver.find_elements(
            By.CSS_SELECTOR,  ".vtex-product-summary-2-x-element.pointer.pt3.pb4.flex.flex-column.h-100")
        # Create a frame empty for the data
        data = []
        # iterate over each element
        for i in items:
            name = findElementTextBySelector(
                ".vtex-product-summary-2-x-productBrand.vtex-product-summary-2-x-brandName.t-body", "SIN DESCRIPCION")
            unit_measure = findElementNumberBySelector(
                ".vtex-product-summary-2-x-valueWrapper.vtex-product-summary-2-x-valueWrapper--grid.vtex-store-components-3-x-valueWrapper.vtex-product-summary-2-x-skuSelectorItemTextValue.vtex-product-summary-2-x-skuSelectorItemTextValue--grid.vtex-store-components-3-x-skuSelectorItemTextValue.c-on-base.center.pl5.pr5.z-1.t-body", "0")
            brand = findElementTextBySelector(
                ".vtex-product-summary-2-x-productBrandName", "SIN MARCA")
            price_prime = findElementNumberBySelector(
                ".exito-vtex-components-4-x-valuePLPAllied", "0")
            price_regular = findElementNumberBySelector(
                ".vtex-store-components-3-x-listPriceValue.ph2.dib.strike.vtex-store-components-3-x-price_listPrice", "0")
            price_now = findElementNumberBySelector(
                ".vtex-store-components-3-x-sellingPrice.vtex-store-components-3-x-sellingPriceValue.t-heading-2-s.dib.ph2.vtex-store-components-3-x-price_sellingPrice", "0")
            conditional_discount = findElementTextBySelector(
                ".dislicoresqa-custom-app-2-x-a_layout.dislicoresqa-custom-app-2-x-a_layout--productSummaryTagsGrid.flex.flex-column.justify-around.items-stretch.h-100.bg-base", "")
            discount = findElementNumberBySelector(
                ".vtex-store-components-3-x-discountInsideContainer.t-mini.white.absolute.right-0.pv2.ph3.bg-emphasis.z-1", "0")

            data.append({f"shop": "DISLICORES",
                         "city": city,
                         "location": city,
                         "category": category,
                         "name": name,
                         "unit_measure": unit_measure,
                         "brand": brand,
                         "price_prime": price_prime,
                         "price_regular": price_regular,
                         "price_now": price_now,
                         "conditional_discount": conditional_discount,
                         "conditional_discount_2": "",
                         "discount": discount,
                         "date": today
                         })

        df = pd.DataFrame(data)
        df.to_csv(f'/home/pydev/workflow/dt_web_scraping/prod/data/dislicores_{city}_{category}_data_{today}.txt',
                  index=False, encoding='utf-8')

        time.sleep(1)
        driver.quit()

time.sleep(10)
driver.quit()


import pandas as pd
import features_time_series
import numpy as np
import time
import gc

start = time.time()

# About this file in general:
# Here I perform the main data-aggregation
# And produce all the features that will be used to build the models

# load the data (sales and test are from kaggle, items_and_vectors.csv is an output from features_from_text.py)
path_sales = 'Input/sales_train_v2.csv'
path_test = 'Input/test.csv'
path_items = 'Input/items_and_vectors.csv'

df_sales = pd.read_csv(path_sales)
df_test = pd.read_csv(path_test)
df_items = pd.read_csv(path_items)

# Here are some manual data fixes after data exploration
# fixing shop_id's
df_sales['shop_id'] = [57 if x == 0 else x for x in df_sales['shop_id']]
df_sales['shop_id'] = [58 if x == 1 else x for x in df_sales['shop_id']]
df_sales['shop_id'] = [10 if x == 11 else x for x in df_sales['shop_id']]

# removing shops that all the time start and stop working, as I do not make predictions for them
df_sales = df_sales.loc[df_sales['shop_id'] != 9]
df_sales = df_sales.loc[df_sales['shop_id'] != 20]

# shop_id = 27 did not work at 32-nd month - no sales and only one return. I exclude this data from model.
df_sales = df_sales.loc[(df_sales['shop_id'] != 27) | (df_sales['date_block_num'] != 32)]

# here I aggregate the data and add rows for zero sales
df = df_sales.groupby(['date_block_num', 'shop_id', 'item_id']).agg({'item_cnt_day': 'sum', 'item_price': 'median'})
df = df.rename(columns={'item_cnt_day': 'item_cnt_month'}).reset_index()
kaggle_1C = features_time_series.FeaturesTimeSeries('shop_id', 'date_block_num', 'item_cnt_month')
df = kaggle_1C.add_zero_sales_rows(df, 'item_id')
df['item_cnt_month'] = df['item_cnt_month'].fillna(0)
df['item_price'] = df.groupby(['item_id', 'shop_id'])['item_price'].ffill()
df['item_price'] = df['item_price'].fillna(0)

# for this task target is clipped to (0,20), but I only clip after preparing all the features
# here I decrease target values only for the few biggest outliers and do not touch values less than 400
df['item_cnt_month'] = [400 + ((x - 400) * 0.25) if x > 400 else x for x in df['item_cnt_month']]

# add month and number of working days per shop
df_sales['date'] = pd.to_datetime(df_sales['date'], format='%d.%m.%Y')
df_sales['month'] = df_sales['date'].dt.month
df = kaggle_1C.add_aggregate(df_sales, 'date_block_num', 'month', 'mean', 'month', df)
df = kaggle_1C.add_aggregate(df_sales, ['date_block_num', 'shop_id'], 'date', 'nunique', 'days', df)

# preparing test data and combining it with main dataframe
df['ID'] = -1
df_test['month'] = 11
df_test['date_block_num'] = 34
df_test['item_cnt_month'] = -1
df_test['days'] = 30
df_test['item_price'] = 0
df = pd.concat([df, df_test], sort=True)

# merging main df's with item_data
df_items = kaggle_1C.reduce_memory(df_items)
df = pd.merge(df, df_items, on='item_id', how='left')

# now delete the ticket categories as I do not need to predict ticket sales
df = df.loc[df['item_category_id'] != 8]
df = df.loc[df['item_category_id'] != 80]

# on_sale feature
df['on_sale'] = 1
df.loc[(df['shop_id'] != 12) & (df['item_category_id'] == 9), 'on_sale'] = 0
df.loc[(df['shop_id'] != 55) & (df['is_digital'] == 1), 'on_sale'] = 0
df.loc[(df['shop_id'] == 55) & (df['is_digital'] == 0), 'on_sale'] = 0
del df['is_digital']

# 'technical' features for previous months sales and days, shop was working
df = kaggle_1C.add_lag_feature(df, range(1, 7), 'item_id', 'item_cnt_month')
df = kaggle_1C.add_lag_feature(df, range(1, 7), 'item_id', 'days')

# feature for month after first sale of this item_id in any shop (0 for new items)
df['month_after_start'] = df['date_block_num'] - df.groupby('item_id')['date_block_num'].transform('min')

# feature for a day when item was first time sold in a shop
# is used to improve predicting for second month sales, so it is set to 0 for all months but second
df_2 = df_sales.groupby(['item_id', 'shop_id']).agg({'date_block_num': 'min', 'date': 'min'}).reset_index()
df_2['first_month'] = df_2.groupby('item_id')['date_block_num'].transform('min')
df_2['first_day'] = df_2['date'].dt.day
df_2.loc[df_2['first_month'] < df_2['date_block_num'], 'first_day'] = 35
df_2 = df_2[['shop_id', 'item_id', 'first_day']]
df = pd.merge(df, df_2, on=['shop_id', 'item_id'], how='left')
df['first_day'] = df['first_day'].fillna(35)
df.loc[df['month_after_start'] != 1, 'first_day'] = 0

# Features generated from item-description sometimes should be used only for the first month item is sold
# Prediction for later months can be better predicted with data from previous periods
df.loc[df['month_after_start'] > 0, ['month_after_same_text', 'same_text_this_month']] = 0
df.loc[df['on_sale'] == 0, 'lang'] = -1

# lag_0.5 feature (sales for the last 14 days of month)
df_sales['day'] = df_sales['date'].dt.day
df = kaggle_1C.last_n_days_of_previous_month_sales(df_sales, 14, df)

# I will only use aggregated data after this point. Will now release some memory and collect garbage.
del df_2
del df_sales
del df_items
gc.collect()
df = kaggle_1C.reduce_memory(df)

# calculate 3 and 6 month shop trends - average sales per day, per shop and per category (or per type)
df = kaggle_1C.add_n_month_trends(df, 'item_category_id', 3)
df = kaggle_1C.add_n_month_trends(df, 'item_category_id', 6)

# adding new lag features, all calculated per day the shop worked that month
df['lag_1_feature'] = 0
df.loc[df['days_lag_1'] > 0, 'lag_1_feature'] = df['item_cnt_month_lag_1']/df['days_lag_1']
df = kaggle_1C.add_n_month_average(df, 3)
df = kaggle_1C.add_n_month_average(df, 6)

# item_sales_stability - share of items sold last months to total sales last three months (all the sales are per day)
df['3_month_stability_feature'] = 0
df.loc[(df['3_month_average_feature'] > 0) & (df['month_after_start'] > 2), '3_month_stability_feature'] = \
    df['lag_1_feature']/df['3_month_average_feature']

# another second month feature - all shops average sales per day after after first sale
df = kaggle_1C.add_aggregate(df, 'date_block_num', 'days_lag_1', 'max', 'max_days')
df['per_day_after_start'] = 0
df.loc[(df['item_cnt_month_lag_1'] > 0) & (df['first_day'] > 0), 'per_day_after_start'] = df['item_cnt_month_lag_1'] /\
                                                                                (df['max_days'] - df['first_day'] + 1)
df = kaggle_1C.add_aggregate(df.loc[(df['item_cnt_month_lag_1'] > 0) & (df['first_day'] > 0)],
                             ['date_block_num', 'item_id'], 'per_day_after_start', 'mean', 'average_per_day_after_start', df)
df['average_per_day_after_start'] = df['average_per_day_after_start'].fillna(0)
df.drop(columns=['per_day_after_start', 'max_days'], inplace=True)

# feature for average item's sales last month
df = kaggle_1C.add_aggregate(df, ['item_id', 'date_block_num'], 'lag_1_feature', 'mean', 'item_popularity_lag_1')

# price features
df = kaggle_1C.add_lag_feature(df, [1], 'item_id', 'item_price')
df['max_price_per_shop'] = df.groupby(['item_id', 'shop_id'])['item_price_lag_1'].transform(pd.Series.cummax)
df = kaggle_1C.add_aggregate(df, ['item_id', 'date_block_num'], 'max_price_per_shop', 'max', 'max_price')
df.drop(columns=['item_price', 'item_price_lag_1', 'max_price_per_shop'], inplace=True)

df = kaggle_1C.add_aggregate(df, ['item_category_id', 'date_block_num'], 'max_price', 'median', 'median_max_price')
df = kaggle_1C.add_cumsum_average(df, ['item_category_id'], 'median_max_price_agg', agg_value='median_max_price')
df.loc[df['median_max_price'] == 0, 'relative_price_median'] = 1
df.loc[df['median_max_price_agg'] > 0, 'relative_price_median'] = df['max_price'] / df['median_max_price_agg']
df['relative_price_median'] = df['relative_price_median'].fillna(0)
del df['median_max_price']
del df['median_max_price_agg']

# average per category and per type encodings
df = kaggle_1C.add_cumsum_average(df.loc[df['on_sale'] == 1], ['type_code'], 'type_average', df_to=df)
df = kaggle_1C.add_cumsum_average(df.loc[df['on_sale'] == 1], ['item_category_id'], 'category_average', df_to=df)
df = kaggle_1C.add_cumsum_average(df.loc[df['on_sale'] == 1], ['type_code', 'shop_id'], 'type_average_per_shop', df_to=df)
df = kaggle_1C.add_cumsum_average(df.loc[df['on_sale'] == 1], ['item_category_id', 'shop_id'],
                                  'category_average_per_shop', df_to=df)
df.loc[df['on_sale'] == 0, ['type_average', 'category_average', 'type_average_per_shop', 'category_average_per_shop']] = -1

# average per category and per type encodings for new (first month) items
df = kaggle_1C.add_cumsum_average(df.loc[(df['date_block_num'] > 1) & (df['month_after_start'] == 0) & (df['on_sale'] == 1)],
                                           ['type_code'], 'type_average_1m', df_to=df)
df = kaggle_1C.add_cumsum_average(df.loc[(df['date_block_num'] > 1) & (df['month_after_start'] == 0) & (df['on_sale'] == 1)],
                                           ['item_category_id'], 'category_average_1m', df_to=df)
df = kaggle_1C.add_cumsum_average(df.loc[(df['date_block_num'] > 1) & (df['month_after_start'] == 0) & (df['on_sale'] == 1)],
                                           ['type_code', 'shop_id'], 'type_average_per_shop_1m', df_to=df)
df = kaggle_1C.add_cumsum_average(df.loc[(df['date_block_num'] > 1) & (df['month_after_start'] == 0) & (df['on_sale'] == 1)],
                                           ['item_category_id', 'shop_id'], 'category_average_per_shop_1m', df_to=df)
df.loc[df['on_sale'] == 0, ['type_average_1m', 'category_average_1m', 'type_average_per_shop_1m',
                            'category_average_per_shop_1m']] = -1

df.loc[df['month_after_start'] > 0, ['type_average_1m', 'category_average_1m', 'type_average_per_shop_1m']] = 0
df.loc[df['month_after_start'] > 0, 'category_average_per_shop_1m'] = 0

# delete 'technical' features
features_to_delete = [col for col in df.columns if ('item_cnt_month_lag_' in col) | ('days_lag_' in col)]
df.drop(columns=features_to_delete, inplace=True)
del df['days']
del df['on_sale']

# now I remove data for the first 6 months, it will not be used for training models
df = df.loc[df['date_block_num'] > 5]

# check before saving data
columns_with_nan = df.isna().any()
print(columns_with_nan)

# save data into file
export_file_final_features = 'Input/' + 'final_features.csv'
df.to_csv(export_file_final_features, encoding='utf-8', index=False)

end = time.time()
print(end - start)


import sys
sys.stdin = open("계산기3_input.txt")

tc = 10
isp = {'*' : 2, '/' : 2, '+' : 1, '-' : 1, "(" : 0}
icp = {'*' : 2, '/' : 2, '+' : 1, '-' : 1, "(" : 3}
f_x = ['+', '-', '*', '/', '.']
for T in range(tc):
    N = int(input())
    data = input()
    s = []
    pri = []
    for i in range(len(data)):
        if data[i] in f_x and len(s) == 0:
            s.append(data[i])
            continue
        if data[i] == '(':
            s.append(data[i])
            continue
        if data[i].isdecimal():
            pri.append(data[i])
            continue
        if data[i] == ')':
            while s[-1] != '(':
                pri.append(s.pop())
            s.pop()
            continue
            # if len(s)==0:
            #     break
        if icp[data[i]] > isp[s[-1]]:
            s.append(data[i])
        else:
            pri.append(s.pop())
            s.append(data[i])
    while len(s) > 0:
        if s[-1] != '(' or s[-1] != ')':
            pri.append(s.pop())
    st = []
    for i in range(len(pri)):
        # if data[i] != '+' and data[i] != '-' and data[i] != '*' and data[i] != '/' and data[i] != '.':
        if pri[i] not in f_x:
            st.append(pri[i])
        if pri[i] == '+':
            if len(st) < 2:
                print(f'#{T + 1} error')
                break
            a = int(st.pop())
            b = int(st.pop())
            st.append(b + a)
        if pri[i] == '-':
            if len(st) < 2:
                print(f'#{T + 1} error')
                break
            a = int(st.pop())
            b = int(st.pop())
            st.append(b - a)
        if pri[i] == '*':
            if len(st) < 2:
                print(f'#{T + 1} error')
                break
            a = int(st.pop())
            b = int(st.pop())
            st.append(b * a)
        if pri[i] == '/':
            if len(st) < 2:
                print(f'#{T + 1} error')
                break
            a = int(st.pop())
            b = int(st.pop())
            st.append(b // a)
        if i == len(pri)-1:
            # if len(st) != 1:
            #     print(f'#{T + 1} error')
            #     break
            # else:
            print(f'#{T + 1} {st.pop()}')


#Contains functionality for grids

import random
import pygame
from . import cells
from . import actor_utility
from . import utility

class grid():
    '''
    Grid of cells of the same size with different positions based on the grid's size and the number of cells. Each cell contains various actors, terrain, and resources
    '''
    def __init__(self, from_save, input_dict, global_manager):
        '''
        Description:
            Initializes this object
        Input:
            boolean from_save: True if this object is being recreated from a save file, False if it is being newly created
            dictionary input_dict: Keys corresponding to the values needed to initialize this object
                'origin_coordinates': int tuple value - Two values representing x and y coordinates for the pixel location of the bottom left corner of this grid
                'pixel_width': int value - Pixel width of this grid
                'pixel_height': int value - Pixel height of this grid
                'coordinate_width': int value - Number of columns in this grid
                'coordinate_height': int value - Number of rows in this grid
                'internal_line_color': string value - Color in the color_dict dictionary for lines between cells, like 'bright blue'
                'external_line_color': string value - Color in the color_dict dictionary for lines on the outside of the grid, like 'bright blue'
                'list modes': string list value - Game modes during which this grid can appear
                'strategic_grid': boolean value - True if this grid is the primary strategic map of the game, False if it is a different grid, like the minimap or the Europe grid
                'grid_line_width': int value - Pixel width of lines between cells. Lines on the outside of the grid are one pixel thicker
                'cell_list': dictionary list value - Required if from save, list of dictionaries of saved information necessary to recreate each cell in this grid
            global_manager_template global_manager: Object that accesses shared variables
        Output:
            None
        '''
        self.global_manager = global_manager
        self.global_manager.get('grid_list').append(self)
        self.grid_line_width = input_dict['grid_line_width']
        self.from_save = from_save
        self.is_mini_grid = False
        self.is_abstract_grid = False
        self.attached_grid = 'none'
        self.modes = input_dict['modes']
        self.origin_x, self.origin_y = input_dict['origin_coordinates']
        self.coordinate_width = input_dict['coordinate_width']
        self.coordinate_height = input_dict['coordinate_height']
        self.pixel_width = input_dict['pixel_width']
        self.pixel_height = input_dict['pixel_height']
        self.Rect = pygame.Rect(self.origin_x, self.origin_y - self.pixel_height, self.pixel_width, self.pixel_height)
        self.internal_line_color = input_dict['internal_line_color']
        self.external_line_color = input_dict['external_line_color']
        self.cell_list = []
        self.mini_grid = 'none'
        #self.global_manager.get('terrain_list').append('water') #to generate water normally
        if not from_save:
            self.create_cells()
            if input_dict['strategic_grid']:
                area = self.coordinate_width * self.coordinate_height
                num_worms = area // 5
                if self.global_manager.get('effect_manager').effect_active('enable_oceans'):
                    self.global_manager.get('terrain_list').append('water')
                for i in range(num_worms):
                    self.make_random_terrain_worm(round(area/24), round(area/12), self.global_manager.get('terrain_list'))
                #if self.global_manager.get('effect_manager').effect_active('enable_oceans'):
                #    for i in range(num_worms // 6): #range(num_worms / 3):
                #        self.make_random_terrain_worm(round(area/24), round(area/12), ['water'])
                if not self.global_manager.get('effect_manager').effect_active('enable_oceans'):
                    for cell in self.cell_list:
                        if cell.y == 0:
                            terrain_variant = random.randrange(0, self.global_manager.get('terrain_variant_dict')['ocean_water'])
                            cell.set_terrain('water', terrain_variant)
                    num_rivers = random.randrange(2, 4)
                    valid = False
                    while not valid:
                        valid = True
                        start_x_list = []
                        for i in range(num_rivers):
                            start_x_list.append(random.randrange(0, self.coordinate_width))
                        for index in range(len(start_x_list)):
                            for other_index in range(len(start_x_list)):
                                if not index == other_index:
                                    if abs(start_x_list[index] - start_x_list[other_index]) < 3:
                                        valid = False

                    for start_x in start_x_list:
                        self.make_random_river_worm(round(self.coordinate_height * 0.75), round(self.coordinate_height * 1.25), start_x)

                for cell in self.cell_list:
                    if cell.y == 0 or cell.y == 1:
                        cell.set_visibility(True)
        else:
            self.saved_cell_list = input_dict['cell_list']
            self.load_cells(input_dict['cell_list'])

    def to_save_dict(self):
        '''
        Description:
            Uses this object's values to create a dictionary that can be saved and used as input to recreate it on loading
        Input:
            None
        Output:
            dictionary: Returns dictionary that can be saved and used as input to recreate it on loading
                'grid_type': string value - String matching the global manager key of this grid, used to initialize the correct type of grid on loading
                'cell_list': dictionary list value - list of dictionaries of saved information necessary to recreate each cell in this grid
        '''
        save_dict = {}
        if self.global_manager.get('strategic_map_grid') == self:
            save_dict['grid_type'] = 'strategic_map_grid'
        else:
            save_dict['grid_type'] = 'default'
        save_dict['cell_list'] = []
        for current_cell in self.cell_list:
            save_dict['cell_list'].append(current_cell.to_save_dict())
        return(save_dict)

    def draw(self):
        '''
        Description:
            Draws each cell of this grid
        Input:
            None
        Output:
            None
        '''
        if self.global_manager.get('current_game_mode') in self.modes:
            for cell in self.cell_list:
                cell.draw()

    def draw_grid_lines(self):
        '''
        Description:
            Draws lines between grid cells and on the outside of the grid. Also draws an outline of the area on this grid covered by this grid's minimap grid, if applicable
        Input:
            None
        Output:
            None
        '''
        #if self.global_manager.get('show_grid_lines') and self.global_manager.get('current_game_mode') in self.modes:
        if self.global_manager.get('current_game_mode') in self.modes:
            if not self.global_manager.get('effect_manager').effect_active('hide_grid_lines'):
                for x in range(0, self.coordinate_width+1):
                    pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.internal_line_color], self.convert_coordinates((x, 0)), self.convert_coordinates((x, self.coordinate_height)), self.grid_line_width)
                for y in range(0, self.coordinate_height+1):
                    pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.internal_line_color], self.convert_coordinates((0, y)), self.convert_coordinates((self.coordinate_width, y)), self.grid_line_width)
            pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.external_line_color], self.convert_coordinates((0, 0)), self.convert_coordinates((0, self.coordinate_height)), self.grid_line_width + 1)
            pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.external_line_color], self.convert_coordinates((self.coordinate_width, 0)), self.convert_coordinates((self.coordinate_width, self.coordinate_height)), self.grid_line_width + 1)
            pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.external_line_color], self.convert_coordinates((0, 0)), self.convert_coordinates((self.coordinate_width, 0)), self.grid_line_width + 1)
            pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.external_line_color], self.convert_coordinates((0, self.coordinate_height)), self.convert_coordinates((self.coordinate_width, self.coordinate_height)), self.grid_line_width + 1)
            if (not self.mini_grid == 'none') and self.global_manager.get('show_minimap_outlines'):
                mini_map_outline_color = self.mini_grid.external_line_color
                left_x = self.mini_grid.center_x - ((self.mini_grid.coordinate_width - 1) / 2)
                right_x = self.mini_grid.center_x + ((self.mini_grid.coordinate_width - 1) / 2) + 1
                down_y = self.mini_grid.center_y - ((self.mini_grid.coordinate_height - 1) / 2)
                up_y = self.mini_grid.center_y + ((self.mini_grid.coordinate_height - 1) / 2) + 1
                if right_x > self.coordinate_width:
                    right_x = self.coordinate_width
                if left_x < 0:
                    left_x = 0
                if up_y > self.coordinate_height:
                    up_y = self.coordinate_height
                if down_y < 0:
                    down_y = 0
                pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[mini_map_outline_color], self.convert_coordinates((left_x, down_y)), self.convert_coordinates((left_x, up_y)), self.grid_line_width + 1)
                pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[mini_map_outline_color], self.convert_coordinates((left_x, up_y)), self.convert_coordinates((right_x, up_y)), self.grid_line_width + 1)
                pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[mini_map_outline_color], self.convert_coordinates((right_x, up_y)), self.convert_coordinates((right_x, down_y)), self.grid_line_width + 1)
                pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[mini_map_outline_color], self.convert_coordinates((right_x, down_y)), self.convert_coordinates((left_x, down_y)), self.grid_line_width + 1)

    def find_cell_center(self, coordinates):
        '''
        Description:
            Returns the pixel coordinates of the center of this grid's cell that occupies the inputted grid coordinates
        Input:
            int tuple coordinates: Two values representing x and y grid coordinates of the cell whose center is found
        Output:
            int tuple: Two values representing x and y pixel coordinates of the center of the requested cell
        '''
        x, y = coordinates
        return((int((self.pixel_width/(self.coordinate_width)) * x) + self.origin_x + int(self.get_cell_width()/2)), (self.global_manager.get('display_height') - (int((self.pixel_height/(self.coordinate_height)) * y) + self.origin_y + int(self.get_cell_height()/2))))

    def convert_coordinates(self, coordinates):
        '''
        Description:
            Returns the pixel coordinates of the bottom left corner of this grid's cell that occupies the inputted grid coordinates
        Input:
            int tuple coordinates: Two values representing x and y grid coordinates of the cell whose corner is found
        Output:
            int tuple: Two values representing x and y pixel coordinates of the bottom left corner of the requested cell
        '''
        x, y = coordinates
        return((int((self.pixel_width/(self.coordinate_width)) * x) + self.origin_x), (self.global_manager.get('display_height') - (int((self.pixel_height/(self.coordinate_height)) * y) + self.origin_y )))

    def get_height(self):
        '''
        Description:
            Returns how many rows this grid has
        Input:
            None
        Output:
            int: Number of rows this grid has
        '''
        return(self.coordinate_height)

    def get_width(self):
        '''
        Description:
            Returns how many columns this grid has
        Input:
            None
        Output:
            int: Number of columns this grid has
        '''
        return(self.coordinate_width)

    def get_cell_width(self):
        '''
        Description:
            Returns the pixel width of one of this grid's cells
        Input:
            None
        Output:
            int: Pixel width of one of this grid's cells
        '''
        return(int(self.pixel_width/self.coordinate_width) + 1)

    def get_cell_height(self):
        '''
        Description:
            Returns the pixel height of one of this grid's cells
        Input:
            None
        Output:
            int: Pixel height of one of this grid's cells
        '''
        return(int(self.pixel_height/self.coordinate_height) + 1)

    def find_cell(self, x, y):
        '''
        Description:
            Returns this grid's cell that occupies the inputted coordinates
        Input:
            int x: x coordinate for the grid location of the requested cell
            int y: y coordinate for the grid location of the requested cell
        Output:
            string/cell: Returns this grid's cell that occupies the inputted coordinates, or 'none' if there are no cells at the inputted coordinates
        '''
        for cell in self.cell_list:
            if cell.x == x and cell.y == y:
                return(cell)
        return('none')

    def choose_cell(self, requirements_dict):
        '''
        Description:
            Uses a series of requirements to choose and a return a random cell in this grid that fits those requirements
        Input:
            dictionary choice_info_dict: String keys corresponding to various values such as 'allowed_terrains', 'ocean_allowed', and 'nearby_buildings_allowed' to use as requirements for the chosen cell
        Output:
            cell: Returns a random cell in this grid that fits the inputted requirements
        '''
        allowed_terrains = requirements_dict['allowed_terrains']
        ocean_allowed = requirements_dict['ocean_allowed']
        nearby_buildings_allowed = requirements_dict['nearby_buildings_allowed']
        possible_cells = []
        for current_cell in self.cell_list:
            if not current_cell.terrain in allowed_terrains:
                continue
            if (not ocean_allowed) and current_cell.y == 0:
                continue
            if (not nearby_buildings_allowed) and current_cell.adjacent_to_buildings():
                continue
            possible_cells.append(current_cell)
        if len(possible_cells) == 0:
            possible_cells.append('none')
        return(random.choice(possible_cells))

    def create_cells(self):
        '''
        Description:
            Creates a cell for each of this grid's coordinates
        Input:
            None
        Output:
            None
        '''
        for x in range(0, self.coordinate_width):
            for y in range(0, self.coordinate_height):
                self.create_cell(x, y)
        for current_cell in self.cell_list:
            current_cell.find_adjacent_cells()

    def load_cells(self, cell_list):
        '''
        Description:
            Creates this grid's cells with correct resources and terrain based on the inputted saved information
        Input:
            dictionary list cell_list: list of dictionaries of saved information necessary to recreate each cell in this grid
        Output:
            None
        '''
        for current_cell_dict in cell_list:
            x, y = current_cell_dict['coordinates']
            new_cell = cells.cell(x, y, self.get_cell_width(), self.get_cell_height(), self, self.global_manager.get('color_dict')['bright green'], current_cell_dict, self.global_manager)
        for current_cell in self.cell_list:
            current_cell.find_adjacent_cells()
            current_cell.set_terrain(current_cell.save_dict['terrain'])

    def create_cell(self, x, y):
        '''
        Description:
            Creates a cell at the inputted coordinates
        Input:
            int x: x coordinate at which to create a cell
            int y: y coordinate at which to create a cell
        Output:
            None
        '''
        new_cell = cells.cell(x, y, self.get_cell_width(), self.get_cell_height(), self, self.global_manager.get('color_dict')['bright green'], 'none', self.global_manager)

    def make_resource_list(self, terrain): #should be changed to return dictionary with frequencies of each resource and a list of each resource present, avoiding unnecessary 100+ item lists
        '''
        Description:
            Creates and returns a weighted list of the possible resources, villages, or lack thereof that could spawn in the inputted terrain
        Input:
            string terrain: Type of terrain for which to make a resource list, like 'swamp'
        Output:
            string list: Weighted list of the possible resources, villages, or lack thereof that could spawn in the inputted terrain
        '''
        resource_list = []
        if terrain == 'clear':
            for i in range(135):
                resource_list.append('none')
            for i in range(5):
                resource_list.append('ivory')
            for i in range(20):
                resource_list.append('natives')

        elif terrain == 'mountain':
            for i in range(135):
                resource_list.append('none')
            resource_list.append('diamond')
            for i in range(2):
                resource_list.append('gold')
            for i in range(4):
                resource_list.append('coffee')
            for i in range(4):
                resource_list.append('copper')
            for i in range(4):
                resource_list.append('iron')
            for i in range(10):
                resource_list.append('natives')

        elif terrain == 'hills':
            for i in range(135):
                resource_list.append('none')
            resource_list.append('diamond')
            for i in range(2):
                resource_list.append('gold')
            for i in range(4):
                resource_list.append('coffee')
            for i in range(4):
                resource_list.append('copper')
            for i in range(4):
                resource_list.append('iron')
            for i in range(10):
                resource_list.append('natives')
        elif terrain == 'jungle':
            for i in range(125):
                resource_list.append('none')
            resource_list.append('diamond')
            for i in range(6):
                resource_list.append('rubber')
            resource_list.append('coffee')
            for i in range(6):
                resource_list.append('exotic wood')
            for i in range(6):
                resource_list.append('fruit')
            for i in range(15):
                resource_list.append('natives')
        elif terrain == 'swamp':
            for i in range(130):
                resource_list.append('none')
            for i in range(4):
                resource_list.append('ivory')
            for i in range(4):
                resource_list.append('rubber')
            resource_list.append('coffee')
            for i in range(4):
                resource_list.append('exotic wood')
            for i in range(2):
                resource_list.append('fruit')
            for i in range(15):
                resource_list.append('natives')
        elif terrain == 'desert':
            for i in range(140):
                resource_list.append('none')
            for i in range(2):
                resource_list.append('diamond')
            resource_list.append('gold')
            resource_list.append('ivory')
            for i in range(2):
                resource_list.append('fruit')
            for i in range(2):
                resource_list.append('copper')
            for i in range(2):
                resource_list.append('iron')
            for i in range(10):
                resource_list.append('natives')
        else:
            resource_list.append('none')
        return(resource_list)

    def set_resources(self):
        '''
        Description:
            Spawns a random resource, village, or lack thereof in each of this grid's cells
        Input:
            None
        Output:
            None
        '''
        if self.from_save:
            for cell in self.cell_list:
                cell.set_resource(cell.save_dict['resource'])
        else:
            resource_list_dict = {}
            for terrain in self.global_manager.get('terrain_list'):
                resource_list_dict[terrain] = self.make_resource_list(terrain)
            resource_list_dict['water'] = self.make_resource_list('water')
            for cell in self.cell_list:
                cell.set_resource(random.choice(resource_list_dict[cell.terrain]))

    def make_random_terrain_worm(self, min_len, max_len, possible_terrains):
        '''
        Description:
            Chooses a random terrain from the inputted list and fills a random length chain of adjacent grid cells with the chosen terrain. Can go to the same cell multiple times
        Input:
            int min_len: minimum number of cells whose terrain can be changed
            int max_len: maximum number of cells whose terrain can be changed, inclusive
            string list possible_terrains: list of all terrain types that could randomly spawn, like 'swamp'
        Output:
            None
        '''
        start_x = random.randrange(0, self.coordinate_width)
        start_y = random.randrange(0, self.coordinate_height)
        current_x = start_x
        current_y = start_y
        worm_length = random.randrange(min_len, max_len + 1)
        terrain = random.choice(possible_terrains)
        terrain_variant = random.randrange(0, self.global_manager.get('terrain_variant_dict')[terrain]) #randomly choose from number of terrain variants, if 2 variants then pick 0 or 1
        self.find_cell(current_x, current_y).set_terrain(terrain, terrain_variant)
        counter = 0
        while not counter == worm_length:
            counter = counter + 1
            direction = random.randrange(1, 5) #1 north, 2 east, 3 south, 4 west
            if not (((current_x == self.coordinate_width - 1) and direction == 2) or ((current_x == 0) and direction == 4) or ((current_y == self.coordinate_height - 1) and direction == 3) or ((current_y == 0) and direction == 1)):
                if direction == 3:
                    current_y = current_y + 1
                elif direction == 2:
                    current_x = current_x + 1
                elif direction == 1:
                    current_y = current_y - 1
                elif direction == 4:
                    current_x = current_x - 1
                terrain_variant = random.randrange(0, self.global_manager.get('terrain_variant_dict')[terrain]) #randomly choose from number of terrain variants, if 2 variants then pick 0 or 1
                self.find_cell(current_x, current_y).set_terrain(terrain, terrain_variant)

    def make_random_river_worm(self, min_len, max_len, start_x):
        '''
        Description:
            Fills a random length chain of adjacent grid cells with water, starting at an inputted location. Weighted to generally move toward the top of the grid, away from the coast. Can go to the same cell multiple times
        Input:
            int min_len: minimum number of cells whose terrain can be changed
            int max_len: maximum number of cells whose terrain can be changed, inclusive
            int start_x: x coordinate at which to start the water chain. Always starts with a y location of 1
        Output:
            None
        '''
        start_y = 1
        current_x = start_x
        current_y = start_y
        worm_length = random.randrange(min_len, max_len + 1)
        terrain = 'water'
        water_type = 'water'
        if current_y == 0:
            water_type = 'ocean_water'
        else:
            water_type = 'river_water'
        #self.find_cell(current_x, current_y).set_terrain(terrain)
        terrain_variant = random.randrange(0, self.global_manager.get('terrain_variant_dict')[water_type]) #randomly choose from number of terrain variants, if 2 variants then pick 0 or 1
        self.find_cell(current_x, current_y).set_terrain(terrain, terrain_variant)
        #self.find_cell(current_x, current_y).set_terrain(terrain)
        counter = 0
        while not counter == worm_length:
            counter = counter + 1
            direction = random.randrange(1, 7) #1 3 5 6 north, 2 east, 4 west
            if direction == 1 or direction == 5 or direction == 6:
                direction = 3 #turns extras and south to north
            if not (((current_x == self.coordinate_width - 1) and direction == 2) or ((current_x == 0) and direction == 4) or ((current_y == self.coordinate_height - 1) and direction == 3) or ((current_y == 0) and direction == 1)):
                if direction == 3:
                    current_y = current_y + 1
                elif direction == 2:
                    current_x = current_x + 1
                elif direction == 4:
                    current_x = current_x - 1
                water_type = 'water'
                if current_y == 0:
                    water_type = 'ocean_water'
                else:
                    water_type = 'river_water'
                #self.find_cell(current_x, current_y).set_terrain(terrain)
                terrain_variant = random.randrange(0, self.global_manager.get('terrain_variant_dict')[water_type]) #randomly choose from number of terrain variants, if 2 variants then pick 0 or 1
                self.find_cell(current_x, current_y).set_terrain(terrain, terrain_variant)

    def touching_mouse(self):
        '''
        Description:
            Returns whether this grid is colliding with the mouse
        Input:
            None
        Output:
            boolean: Returns True if this grid is colliding with the mouse, otherwise returns False
        '''
        if self.Rect.collidepoint(pygame.mouse.get_pos()):
            return(True)
        else:
            return(False)

    def can_show(self):
        '''
        Description:
            Returns whether this grid can be shown. By default, it can be shown during game modes in which this grid can appear
        Input:
            None
        Output:
            boolean: Returns True if this grid can appear during the current game mode, otherwise returns False
        '''
        if self.global_manager.get('current_game_mode') in self.modes:
            return(True)
        else:
            return(False)

    def remove(self):
        '''
        Description:
            Removes this object from relevant lists and prevents it from further appearing in or affecting the program
        Input:
            None
        Output:
            None
        '''
        self.global_manager.set('grid_list', utility.remove_from_list(self.global_manager.get('grid_list'), self))

class mini_grid(grid):
    '''
    Grid that zooms in on a small area of a larger attached grid, centered on a certain cell of the attached grid. Which cell is being centered on can be changed
    '''
    def __init__(self, from_save, input_dict, global_manager):
        '''
        Description:
            Initializes this object
        Input:
            boolean from_save: True if this object is being recreated from a save file, False if it is being newly created
            dictionary input_dict: Keys corresponding to the values needed to initialize this object
                'origin_coordinates': int tuple value - Two values representing x and y coordinates for the pixel location of the bottom left corner of this grid
                'pixel_width': int value - Pixel width of this grid
                'pixel_height': int value - Pixel height of this grid
                'coordinate_width': int value - Number of columns in this grid
                'coordinate_height': int value - Number of rows in this grid
                'internal_line_color': string value - Color in the color_dict dictionary for lines between cells, like 'bright blue'
                'external_line_color': string value - Color in the color_dict dictionary for lines on the outside of the grid, like 'bright blue'
                'list modes': string list value - Game modes during which this grid can appear
                'attached_grid': grid value - grid to which this grid is attached
                'grid_line_width': int value - Pixel width of lines between cells. Lines on the outside of the grid are one pixel thicker
                'cell_list': dictionary list value - Required if from save, list of dictionaries of saved information necessary to recreate each cell in this grid
            global_manager_template global_manager: Object that accesses shared variables
        Output:
            None
        '''
        input_dict['strategic_grid'] = False
        super().__init__(from_save, input_dict, global_manager)
        self.is_mini_grid = True
        self.attached_grid = input_dict['attached_grid']
        self.attached_grid.mini_grid = self
        self.center_x = 0
        self.center_y = 0

    def calibrate(self, center_x, center_y):
        '''
        Description:
            Centers this mini grid on the cell at the inputted coordinates of the attached grid, moving any displayed actors, terrain, and resources on this grid to their new locations as needed
        Input:
            int center_x: x coordinate on the attached grid to center on
            int center_y: y coordinate on the attached grid to center on
        Output:
            None
        '''
        if self.global_manager.get('current_game_mode') in self.modes:
            for current_exploration_mark in self.global_manager.get('exploration_mark_list'):
                if self in current_exploration_mark.grids:
                    current_exploration_mark.main_x, current_exploration_mark.main_y = self.get_main_grid_coordinates(current_exploration_mark.x, current_exploration_mark.y)
            self.center_x = center_x
            self.center_y = center_y
            actor_utility.calibrate_actor_info_display(self.global_manager, self.global_manager.get('tile_info_display'), self.attached_grid.find_cell(self.center_x, self.center_y).tile) #calibrate tile display information to centered tile
            for current_cell in self.cell_list:
                attached_x, attached_y = self.get_main_grid_coordinates(current_cell.x, current_cell.y)
                if attached_x >= 0 and attached_y >= 0 and attached_x < self.attached_grid.coordinate_width and attached_y < self.attached_grid.coordinate_height:
                    attached_cell = self.attached_grid.find_cell(attached_x, attached_y)
                    current_cell.contained_mobs = attached_cell.contained_mobs
                    current_cell.contained_buildings = attached_cell.contained_buildings
                    current_cell.village = attached_cell.village
                    current_cell.set_visibility(attached_cell.visible, update_image_bundle = False)
                    current_cell.set_terrain(attached_cell.terrain, attached_cell.terrain_variant, update_image_bundle = False)
                    current_cell.set_resource(attached_cell.resource)
                else: #if the current cell is beyond the boundaries of the attached grid, show an empty cell
                    current_cell.set_visibility(True)
                    current_cell.set_terrain('none')
                    current_cell.set_resource('none')
                    current_cell.reset_buildings()
                    current_cell.tile.update_image_bundle()
            self.Rect = pygame.Rect(self.origin_x, self.origin_y - self.pixel_height, self.pixel_width, self.pixel_height)
            for current_mob in self.global_manager.get('mob_list'):
                if not (current_mob.images[0].current_cell == 'none'): #if not ((current_mob in self.global_manager.get('officer_list') or current_mob in self.global_manager.get('worker_list')) and current_mob.in_group):
                    for current_image in current_mob.images:
                        if current_image.grid == self:
                            current_image.add_to_cell()
        if self.global_manager.get('current_game_mode') in self.modes:
            for current_exploration_mark in self.global_manager.get('exploration_mark_list'):
                if self in current_exploration_mark.grids:
                    current_exploration_mark.x, current_exploration_mark.y = self.get_mini_grid_coordinates(current_exploration_mark.main_x, current_exploration_mark.main_y)

    def get_main_grid_coordinates(self, mini_x, mini_y):
        '''
        Description:
            Converts the inputted coordinates on this grid to the corresponding coordinates on the attached grid, returning the converted coordinates
        Input:
            int mini_x: x coordinate on this grid
            int mini_y: y coordinate on this grid
        Output:
            int: x coordinate of the attached grid corresponding to the inputted x coordinate
            int: y coordinate of the attached grid corresponding to the inputted y coordinate
        '''
        attached_x = self.center_x + mini_x - round((self.coordinate_width - 1) / 2) #if width is 5, ((5 - 1) / 2) = (4 / 2) = 2, since 2 is the center of a 5 width grid starting at 0
        attached_y = self.center_y + mini_y - round((self.coordinate_height - 1) / 2)
        return(attached_x, attached_y)

    def get_mini_grid_coordinates(self, original_x, original_y):
        '''
        Description:
            Converts the inputted coordinates on the attached grid to the corresponding coordinates on this grid, returning the converted coordinates
        Input:
            int mini_x: x coordinate on the attached grid
            int mini_y: y coordinate on the attached grid
        Output:
            int: x coordinate of this grid corresponding to the inputted x coordinate
            int: y coordinate of this grid corresponding to the inputted y coordinate
        '''
        return(original_x - self.center_x + (round(self.coordinate_width - 1) / 2), original_y - self.center_y + round((self.coordinate_height - 1) / 2))

    def is_on_mini_grid(self, original_x, original_y):
        '''
        Description:
            Returns whether the inputted attached grid coordinates are within the boundaries of this grid
        Input:
            int original_x: x coordinate on the attached grid
            int original_y: y coordinate on the attached grid
        Output:
            boolean: Returns True if the inputted attache grid coordinates are within the boundaries of this grid, otherwise returns False
        '''
        minimap_x = original_x - self.center_x + (round(self.coordinate_width - 1) / 2)
        minimap_y = original_y - self.center_y + (round(self.coordinate_height - 1) / 2)
        if(minimap_x >= 0 and minimap_x < self.coordinate_width and minimap_y >= 0 and minimap_y < self.coordinate_height):
            return(True)
        else:
            return(False)

    def draw_grid_lines(self):
        '''
        Description:
            Draws lines between grid cells and on the outside of the grid
        Input:
            None
        Output:
            None
        '''
        lower_left_corner = self.get_mini_grid_coordinates(0, 0)
        upper_right_corner = self.get_mini_grid_coordinates(self.attached_grid.coordinate_width - 1, self.attached_grid.coordinate_height)
        if lower_left_corner[0] < 0: #left
            left_x = 0
        else:
            left_x = lower_left_corner[0]
        if lower_left_corner[1] < 0: #down
            down_y = 0
        else:
            down_y = lower_left_corner[1]
        if upper_right_corner[0] >= self.coordinate_width: #right
            right_x = self.coordinate_width
        else:
            right_x = upper_right_corner[0] + 1
        if upper_right_corner[1] > self.coordinate_height: #up
            up_y = self.coordinate_height
        else:
            up_y = upper_right_corner[1]
        if not self.global_manager.get('effect_manager').effect_active('hide_grid_lines'):

            for x in range(0, self.coordinate_width+1):
                pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.internal_line_color], self.convert_coordinates((x, 0)), self.convert_coordinates((x, self.coordinate_height)),
                                self.grid_line_width)

            for y in range(0, self.coordinate_height+1):
                pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.internal_line_color], self.convert_coordinates((0, y)), self.convert_coordinates((self.coordinate_width, y)),
                                self.grid_line_width)

        for y in range(0, self.coordinate_height+1):
            pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.external_line_color], self.convert_coordinates((left_x, down_y)), self.convert_coordinates((left_x, up_y)),
                            self.grid_line_width + 1)

        pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.external_line_color], self.convert_coordinates((left_x, up_y)), self.convert_coordinates((right_x, up_y)),
                        self.grid_line_width + 1)

        pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.external_line_color], self.convert_coordinates((right_x, up_y)), self.convert_coordinates((right_x, down_y)),
                        self.grid_line_width + 1)

        pygame.draw.line(self.global_manager.get('game_display'), self.global_manager.get('color_dict')[self.external_line_color], self.convert_coordinates((right_x, down_y)), self.convert_coordinates((left_x, down_y)),
                        self.grid_line_width + 1)

class abstract_grid(grid):
    '''
    1-cell grid that is not directly connected to the primary strategic grid but can be moved to by mobs from the strategic grid and vice versa
    '''
    def __init__(self, from_save, input_dict, global_manager):
        '''
        Description:
            Initializes this object
        Input:
            boolean from_save: True if this object is being recreated from a save file, False if it is being newly created
            dictionary input_dict: Keys corresponding to the values needed to initialize this object
                'origin_coordinates': int tuple value - Two values representing x and y coordinates for the pixel location of the bottom left corner of this grid
                'pixel_width': int value - Pixel width of this grid
                'pixel_height': int value - Pixel height of this grid
                'internal_line_color': string value - Color in the color_dict dictionary for lines between cells, like 'bright blue'
                'external_line_color': string value - Color in the color_dict dictionary for lines on the outside of the grid, like 'bright blue'
                'list modes': string list value - Game modes during which this grid can appear
                'grid_line_width': int value - Pixel width of lines between cells. Lines on the outside of the grid are one pixel thicker
                'cell_list': dictionary list value - Required if from save, list of dictionaries of saved information necessary to recreate each cell in this grid
                'tile_image_id': File path to the image used by this grid's tile
                'name': Name of this grid
            global_manager_template global_manager: Object that accesses shared variables
        Output:
            None
        '''
        input_dict['coordinate_width'] = 1
        input_dict['coordinate_height'] = 1
        input_dict['strategic_grid'] = False
        super().__init__(from_save, input_dict, global_manager)
        self.is_abstract_grid = True
        self.name = input_dict['name']
        self.global_manager.get('abstract_grid_list').append(self)
        self.tile_image_id = input_dict['tile_image_id']
        self.cell_list[0].set_visibility(True)

    def to_save_dict(self):
        '''
        Description:
            Uses this object's values to create a dictionary that can be saved and used as input to recreate it on loading
        Input:
            None
        Output:
            dictionary: Returns dictionary that can be saved and used as input to recreate it on loading
                'grid_type': string value - String matching the global manager key of this grid, used to initialize the correct type of grid on loading
                'cell_list': dictionary list value - list of dictionaries of saved information necessary to recreate each cell in this grid
        '''
        save_dict = super().to_save_dict()
        if self.global_manager.get('europe_grid') == self:
            save_dict['grid_type'] = 'europe_grid'
        elif self.global_manager.get('slave_traders_grid') == self:
            save_dict['grid_type'] = 'slave_traders_grid'
        else:
            save_dict['grid_type'] = 'default'
        return(save_dict)


import logging
from pathlib import Path

from rich.logging import RichHandler

try:
    from dotenv import find_dotenv, load_dotenv

    load_dotenv(find_dotenv())
except ModuleNotFoundError:
    pass

# Configure logging
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
rich_handler = RichHandler(rich_tracebacks=True)
logging.getLogger().handlers = [rich_handler]

def get_filepath(filename: str, folder_name: str = "examples") -> str:
    """Function created to take the full path of the base files to
    execute in the scripts.
    Args:
        filename (str): The file name
        folder_name (str, optional): Folder where files are stored..
        Defaults to "examples".
    Returns:
        str: Returns the name of the file, assuming that it has been
        placed in the root folder or in the folder where it should be placed.
    """
    result = filename
    source = Path(folder_name).resolve()
    list_files = [file for file in source.iterdir()]
    for file in list_files:
        if file.name.strip() == filename.strip():
            result = file.as_posix()
    return result


from django.core.exceptions import ValidationError
from django.db.models import Sum
from rest_framework import serializers

from features.models import Feature
from features.multivariate.models import (
    MultivariateFeatureOption,
    MultivariateFeatureStateValue,
)

class NestedMultivariateFeatureOptionSerializer(serializers.ModelSerializer):
    class Meta:
        model = MultivariateFeatureOption

        fields = (
            "id",
            "uuid",
            "type",
            "integer_value",
            "string_value",
            "boolean_value",
            "default_percentage_allocation",
        )
        read_only_fields = ("uuid",)

class MultivariateFeatureOptionSerializer(NestedMultivariateFeatureOptionSerializer):
    class Meta(NestedMultivariateFeatureOptionSerializer.Meta):
        fields = NestedMultivariateFeatureOptionSerializer.Meta.fields + ("feature",)

    def validate(self, attrs):
        attrs = super().validate(attrs)
        total_sibling_percentage_allocation = (
            self._get_siblings(attrs["feature"]).aggregate(
                total_percentage_allocation=Sum("default_percentage_allocation")
            )["total_percentage_allocation"]
            or 0
        )
        total_percentage_allocation = (
            total_sibling_percentage_allocation + attrs["default_percentage_allocation"]
        )

        if total_percentage_allocation > 100:
            raise ValidationError(
                {"default_percentage_allocation": "Invalid percentage allocation"}
            )

        return attrs

    def _get_siblings(self, feature: Feature):
        siblings = feature.multivariate_options.all()
        if self.instance:
            siblings = siblings.exclude(id=self.instance.id)

        return siblings

class MultivariateFeatureStateValueSerializer(serializers.ModelSerializer):
    class Meta:
        model = MultivariateFeatureStateValue
        fields = (
            "id",
            "multivariate_feature_option",
            "percentage_allocation",
        )


from create_AWS_cluster import aws_client, aws_resource, parse_configFile
import configparser
import pandas as pd
import boto3
import json
import time

def persist_cluster_configInfo(Redshift, DWH_CLUSTER_IDENTIFIER):
    """
    Writes the cluster endpoint and IAM ARN to the dwh.cfg config file
    @type redshift -- client
    @param redshift -- Redshift resource client

    @type DWH_CLUSTER_IDENTIFIER -- string
    @param DWH_CLUSTER_IDENTIFIER -- value from config file

    @return -- None
    """
    print('Cluster address and IamRoleARN is being written to the config file.')
    cluster_properties = Redshift.describe_clusters(ClusterIdentifier = DWH_CLUSTER_IDENTIFIER)['Clusters'][0]
    config = configparser.ConfigParser()

    config.read_file(open('dwh.cfg'))

    config.set('CLUSTER','HOST',cluster_properties['Endpoint']['Address'])
    config.set('IAM_ROLE','ARN',cluster_properties['IamRoles'][0]['IamRoleArn'])

    with open('dwh.cfg','w+') as configFile:
        config.write(configFile)
    parse_configFile()

def get_cluster_status(redshift,DWH_CLUSTER_IDENTIFIER):
    """
    Checks the Redshift cluster status, retuns True or False
    @type redshift -- redshift
    @param redshift -- AWS Redshift resource client

    @type DWH_CLUSTER_IDENTIFIER -- string
    @param DWH_CLUSTER_IDENTIFIER -- value from config file

    @return -- boolean True or False
    """
    cluster_properties = redshift.describe_clusters(ClusterIdentifier = DWH_CLUSTER_IDENTIFIER)['Clusters'][0]
    clusters_status = cluster_properties['ClusterStatus']
    return clusters_status.lower()

def describe_clust(redshift,DWH_CLUSTER_IDENTIFIER):
    return redshift.describe_clusters(ClusterIdentifier = DWH_CLUSTER_IDENTIFIER)['Clusters'][0]

def prettyRedshiftProps(props):
    pd.set_option('display.max_colwidth', -1)
    keysToShow = ["ClusterIdentifier", "NodeType", "ClusterStatus", "MasterUsername", "DBName", "Endpoint", "NumberOfNodes", 'VpcId']
    x = [(k, v) for k,v in props.items() if k in keysToShow]
    return pd.DataFrame(data=x, columns=["Key", "Value"])

def open_redshift_port (ec2,redshift, DWH_CLUSTER_IDENTIFIER, DB_PORT):
    """
    Redshift post on VPC security group is opened
    @type ec2 -- clinet
    @param ec2 -- AWS EC2 resource client

    @type redshift -- client
    @param redshift -- Redshift resource client

    @return -- none
    """
    myClusterProps = redshift.describe_clusters(ClusterIdentifier = DWH_CLUSTER_IDENTIFIER)['Clusters'][0]
    try:
        vpc = ec2.Vpc(id=myClusterProps['VpcId'])
        secur_group = list(vpc.security_groups.all())
        print(secur_group)
        defaultSg = secur_group[1]

        defaultSg.authorize_ingress(
            GroupName= defaultSg.group_name,
            CidrIp='0.0.0.0/0',
            IpProtocol='TCP',
            FromPort=int(DB_PORT),
            ToPort=int(DB_PORT)
        )
    except Exception as e:
        print(e)

def main():
    """
    Checks the Redshift cluster status and opens the Port
    """
    # parse config file
    configs=parse_configFile()

    #get redshift client
    redshift = aws_client('redshift','us-east-2',configs[0],configs[1])

    # check if cluster was created
        # add port thing, and create ec2 resource object to do so
    if get_cluster_status(redshift,configs[10]):
        print('Cluster is available:')
        print(prettyRedshiftProps(describe_clust(redshift,configs[10])))
        ec2 = aws_resource('ec2','us-east-2',configs[0],configs[1])
        persist_cluster_configInfo(redshift,configs[10])
        open_redshift_port(ec2,redshift,configs[10],configs[5])
        print('Cluster is up and running')
    else:
        print('Cluster is not available yet')

if __name__ =='__main__':
    main()


# Magic Ball
from random import *
answers = ['Бесспорно', 'Мне кажется – да', 'Пока неясно, попробуй снова', 'Даже не думай', 'Предрешено', 'Вероятнее всего', 'Спроси позже', 'Мой ответ – нет', 'Никаких сомнений', 'Хорошие перспективы', 'Лучше не рассказывать', 'По моим данным – нет', 'Определённо да', 'Знаки говорят – да',
'Сейчас нельзя предсказать', 'Перспективы не очень хорошие', 'Можешь быть уверен в этом', 'Да', 'Сконцентрируйся и спроси опять', 'Весьма сомнительно']
print('Привет мир, я магический шар, и я знаю ответ на любой твой вопрос.')
name = input('Как тебя зовут?\n')
print(f'Привет, {name}')
while True:
    qus = input('Задавай вопрос: \n')
    print(f'Ответ на твой вопрос"{qus}" - ', choice(answers), end ='\n')
    quest = input('ты хочешь задать вопрос?\n')
    if quest == 'нет' or quest == 'Нет' or quest == 'No' or quest == 'no':
        break
    elif quest == 'да'or quest == 'Да' or quest == 'ДА' or quest == 'Yes' or quest == 'yes' or quest == 'YES':
        continue
    else:
        break

print('Возвращайся если возникнут вопросы!')


from collections import deque
row, col = list(map(int, input().split()))

board = [list(input()) for _ in range(row)]
moves = [[0, 1], [1, 0], [0, -1], [-1, 0]]
answer = 0

def BFS(a, b):
    global answer
    isVisited = [[None] * col for _ in range(row)]
    queue = deque([[a, b, 0]])
    isVisited[a][b] = True
    while queue:
        r, c, cnt = queue.popleft()
        answer = max(answer, cnt)
        for x, y in moves:
            R, C = r+x, c+y
            if 0 <= R < row and 0 <= C < col and not isVisited[R][C] and board[R][C] == 'L':
                isVisited[R][C] = True
                queue.append([R, C, cnt+1])

def solution(row, col, board):
    # 모든점에서 BFS로 가장 먼거리 찾기 그리고 갱신
    for i in range(row):
        for j in range(col):
            if board[i][j] == 'L':
                BFS(i, j)
    return answer

print(solution(row, col, board))


from flask import Flask, request, jsonify
import string
from tensorflow.keras.models import load_model
import pickle
import spacy

# Initialisation des stopwords
nlp = spacy.load('en_core_web_sm')
stopwords = nlp.Defaults.stop_words

def Preprocess_Sentence(Sentence):
    # Enlever la ponctuation
    Sentence = "".join([i.lower() for i in Sentence if i not in string.punctuation])
    # Enlever les chiffres
    Sentence = ''.join(i for i in Sentence if not i.isdigit())
    # Tokenization : Transformer les phrases en liste de tokens (en liste de mots)
    Sentence = nlp(Sentence)
    # Enlever les stopwords
    Sentence = [i.text for i in Sentence if i.text not in stopwords]
    # Enlever les mots qui ne sont pas alphabétiques ou qui ne sont pas dans le dictionnaire
    Sentence = ' '.join(w for w in Sentence if w.isalpha() and w.lower() in nlp.vocab)
    # Lemmatisation
    Sentence = ' '.join([token.lemma_ for token in nlp(Sentence)])

    return Sentence

# Chargement du tokenizer
with open('tokenizer.pickle', 'rb') as handle:
    tokenizer = pickle.load(handle)

# Chargement du modèle
loaded_model = load_model('advance_model.h5')

app = Flask(__name__)

@app.route('/prediction', methods=['POST'])
def prediction():
    phrase = request.form['phrase']
    sequence = Preprocess_Sentence(phrase)
    sequence = tokenizer.texts_to_sequences([sequence])
    while len(sequence[0]) < 35:
        sequence[0].insert(0, 0)
    prediction = loaded_model.predict(sequence)
    stringprediction = str(prediction[0][0])
    return jsonify({'prediction': stringprediction})

if __name__ == '__main__':
    app.run()


import requests
from urllib.request import urlopen
from bs4 import BeautifulSoup

from selenium import webdriver

import time

def get_(link):

    data = open("data.txt","a")

    page = urlopen(link)
    soup = BeautifulSoup(page, 'html.parser')

    title = soup.find(class_ = 'title')
    time = soup.find(class_ = "content-publication-data__updated")

    if title == None:
        title = soup.find(class_ = "playkit-video-info__ep-section")
        time = soup.find(class_ = "playkit-video-info__published-at")

    if title == None:

        title = soup.find(class_ = "catalog-video-title")
        time = soup.find(class_ = "catalog-metadata-published-time")

    data.write("{}\n{}\n".format(title.text, time.text))

    data.close()

def get_all(link):

    driver = webdriver.Chrome()

    driver.get(link)
    def roll():
        #Rola a barra do navegador pra baixo
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        # Eespera 2s par rolar novamente
        time.sleep(2)
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(2)
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(2)
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(2)
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(1)

    roll()
    roll()
    roll()

    # Pega as div's com a classe igual á abaixo
    posts = driver.find_elements_by_xpath("//div[@class='feed-post-body']")

    #salva a página html
    page_source = driver.page_source

    try:
        soup = BeautifulSoup(page_source, 'lxml')
    except Exception as e:
        print(e)

    reviews = []
    reviews_selector = soup.find_all('div', class_='feed-post-body')
    botao = soup.find_all('div', class_="load-more gui-color-primary-bg")
    botao = botao[0].a.get('href')
    print(botao)
    #try para caso de erro o selenium fechar o driver do navegador
    try:
        links = []
        i = 0
        for count, review_selector in enumerate(reviews_selector):

            title = review_selector.find('div', class_='feed-post-body-title gui-color-primary gui-color-hover')
            links.append(title.a.get('href'))
            print(links[i])
            get_(links[i])

            i += 1
            reviews.append(title)

    except Exception as e:
        print(e)
    #fecha o driver do navegador
    driver.close()
    return botao

link = "https://g1.globo.com/tudo-sobre/petrobras/"
link_vale = "https://g1.globo.com/tudo-sobre/vale/"
link_economics = "https://g1.globo.com/economia/"
link_ibm = "https://g1.globo.com/tudo-sobre/ibm/"
link_bb = "https://g1.globo.com/tudo-sobre/banco-do-brasil/"
link_oi = "https://g1.globo.com/tudo-sobre/oi/"

botao = get_all(link)

for i in range(60):

    botao = get_all(botao)


# -*- coding: utf-8 -*-
import re

import socket
import os

class Config:
    config = dict()

    @staticmethod
    def load():
        dir_path = os.path.dirname(os.path.realpath(__file__))

        configFile = open(dir_path + "/resources/" + str(socket.gethostname()) + ".properties")

        with configFile as f:
            for line in f:
                splits = re.split('=|\n',line)
                Config.config[splits[0]] = splits[1]

    @staticmethod
    def get(key):
        if len(Config.config) == 0:
            Config.load()
        return Config.config[key]


class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        return str_str1(haystack, needle)

def str_str1(haystack: str, needle: str) -> int:
    start_index = 0
    i = 0
    j = 0
    if len(needle) == 0:
        return 0
    if len(needle) > len(haystack):
        return -1
    while True:
        i = 0
        while j < len(haystack) and needle[i] != haystack[j]:
            j = j+1
        if j >= len(haystack) or len(needle) > len(haystack) - j:
            # no matching charactor found
            return -1
        start_index = j
        while i < len(needle) and j < len(haystack) and len(needle[i:]) <= len(haystack) - j and needle[i] == haystack[j]:
            i = i+1
            j = j+1
        if i >= len(needle):
            # complete match found
            return start_index
        else:
            # start the loop again with j pointing next char of first match index
            j = start_index + 1
    return -1

if __name__ == '__main__':
    needle = "issipi"
    haystack = "mississippi"
    index = Solution().strStr(haystack, needle)
    print(f'needle {needle}, haystack {haystack}, index {index}')


import logging.handlers

class GetLogger:
    logger = None

    # 获取 logger
    @classmethod
    def get_logger(cls):
        # 如果 logger为空
        if cls.logger is None:
            # 获取日志器
            cls.logger = logging.getLogger()
            # 设置日志器默认级别
            cls.logger.setLevel(logging.INFO)
            # 获取处理器 控制台
            sh = logging.StreamHandler()
            # 获取处理 文件（时间）
            th = logging.handlers.TimedRotatingFileHandler(filename="./log/tpshop.log",
                                                           when="midnight",
                                                           interval=1,
                                                           backupCount=30,
                                                           encoding="utf-8")
            # 获取格式器
            fm = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d] - %(message)s"
            fmt = logging.Formatter(fm)
            # 将格式器 设置 处理器中
            sh.setFormatter(fmt)
            th.setFormatter(fmt)
            # 将处理器 添加到 日志器中
            cls.logger.addHandler(sh)
            cls.logger.addHandler(th)
        # 返回 日志器
        return cls.logger