import os
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

# segmentare binara
#INPUT_PATH = r'E:\an_4_LICENTA\Scripturi_24mar\Scripturi\core\results\UNET_binar_brats_2D_FULLDATASET_lr=10^(-4)CONSTANT_AugumentareFLIP,ZOOM_de_la_inceput_Gibbsringingremoved_Squeeze&Extract_8b_SIGMOIDA_32_64_128_256_lr=10^(-4)_normalizaresimpla_bil0.9369.txt'
# dreapta (jos)
INPUT_PATH = r'E:\an_4_LICENTA\Workspace\Scripturi\core\results\wt1.txt' # cu augumentare
# stanga (jos)
#INPUT_PATH = r'E:\an_4_LICENTA\Workspace\Scripturi\core\results\UNET_binar_brats_2D_Gibbsringingremoved_Squeeze&Extract_biassfieldremoved2D_8b_SIGMOIDA_32_64_128_256_lr=10^(-4)_normalizaresimpla_bilinear_0.9316.txt' #
#INPUT_PATH = r'E:\an_4_LICENTA\Workspace\Scripturi\core\results\UNET_RESNET_HYBRID_1_number_of_blocks_3.txt' # OVERFIT SAU ZGOMOT?

# segmentare multiclasa
#INPUT_PATH = r'E:\an_4_LICENTA\Workspace\Scripturi\core_semantic\results_semantic\ABSOLUTE_FULL_DTS__unet_semantic_brats_2D_v3_COPIE_sigmaoida_fcost_dice_Vlad_weights_batch_gibbsremove_eph61.txt'
#INPUT_PATH = r'E:\an_4_LICENTA\Workspace\Scripturi\core_semantic\results_semantic\delazero.txt'
#INPUT_PATH = r'E:\an_4_LICENTA\Workspace\Scripturi\core_semantic\results_semantic\transferLR_gibbsoptimization_1.txt'

INPUT_PATH1 = r'E:\an_4_LICENTA\Workspace\Scripturi\core_semantic\results_semantic\delazero.txt'
INPUT_PATH2 = r'E:\an_4_LICENTA\Workspace\Scripturi\core_semantic\results_semantic\UNET_binar_brats_2D_FULLDATASET_lr=10^(-4)CONSTANT_Augumentare_FLIP,ZOOM_GAUSS_de_la_inceput_Gibbsringingremoved_Squeeze&Extract_biassfieldremoved2D_8b_SIGMOIDA_32_64_128_256_lr=10^(-4)_normalizaresimpla_bil.txt'

INPUT_PATH1 = r'E:\an_4_LICENTA\Workspace\Scripturi\core\results\wt1.txt'
INPUT_PATH2 = r'E:\an_4_LICENTA\Workspace\Scripturi\core\results\UNET_binar_brats_2D_Gibbsringingremoved_Squeeze&Extract_biassfieldremoved2D_8b_SIGMOIDA_32_64_128_256_lr=10^(-4)_normalizaresimpla_bilinear_0.9316.txt' #

def extract_data(INPUT_PATH):

    A = [] # lista cu atribute, adica cu metricile
    # = [] # de fapt nu prea am nevoie de x
    Y = [] # va fi omlista de liste. fiecare lista contine scorurile, de-a lungul axei timpului

    with open(INPUT_PATH, 'r') as file:
        text = file.read()

    lines = text.splitlines()

    for line in lines:
        line_sep_by_coma = line.split(',')
        for element in line_sep_by_coma:
            if len(element.split(':')) == 2:
                attribute, value = element.split(':')
                attribute, value = attribute.strip(), value.strip()
                # se trateaza cazul in care nu se regaseste inca acest atrinut inlista cu atribute
                if attribute not in A:
                    A.append(attribute)
                if len(Y) <= A.index(attribute):
                    Y.append([])
                Y[A.index(attribute)].append(float(value))
    return A, Y

#print(Y)
#print(len(Y))

def generate_2_overlaped_graphs(INPUT_PATH1, INPUT_PATH2):
    A1, Y1 = extract_data(INPUT_PATH1)
    A2, Y2 = extract_data(INPUT_PATH2)
    for y1, y2 in zip(Y1, Y2):
        plt.figure()
        print(y1)
        plt.plot([i for i in range(1, len(y1)+1)], list(y1), label='cu augmentare')
        plt.plot([i for i in range(1, len(y2)+1)], list(y2), label='fără augmentare')
        plt.xlabel('Număr de epoci')
        #plt.ylabel('Valoarea funcției de cost')
        plt.ylabel('Dice - Validare')
        plt.legend(loc='upper right', fontsize=12)
        #plt.legend(loc='lower right', fontsize=12)
        plt.show()

def generate_one_graph(INPUT_PATH):
    A, Y = extract_data(INPUT_PATH)
    for y in Y:
        plt.figure()
        print(y)
        plt.plot([i for i in range(1, len(y)+1)], list(y))
        plt.show()

def generate_graph_binara_val_vs_train_dice(INPUT_PATH):
    A, Y = extract_data(INPUT_PATH)
    plt.figure()
    train_dice = []
    for i in Y[0]:
        train_dice.append(1-i)
    plt.plot([i for i in range(1, len(train_dice)+1)], list(train_dice), label='F1-antrenare')
    plt.plot([i for i in range(1, len(Y[2])+1)], list(Y[2]), label='F1-validare')
    plt.xlabel('Număr de epoci')
    plt.ylabel('Dice')
    plt.legend(loc='lower left', fontsize=12)
    plt.show()

#################################

# Geenrate one graph
#generate_one_graph(INPUT_PATH)

# Generate 2 graphs
generate_2_overlaped_graphs(INPUT_PATH1, INPUT_PATH2)

# Genereaza grafic pentru train dice vs val dice - ca sa se vada overfitul
#generate_graph_binara_val_vs_train_dice(INPUT_PATH)


"""
"""

from random import sample
from abc import ABC, abstractmethod
from typing import Optional, Tuple, Union, List, Any, Sequence, NoReturn

import numpy as np
import torch
from torch import Tensor

from .base import Augmenter
from .baseline_wander import BaselineWanderAugmenter
from .label_smooth import LabelSmooth
from .mixup import Mixup
from .random_flip import RandomFlip
from .random_masking import RandomMasking
from .random_renormalize import RandomRenormalize
from .stretch_compress import StretchCompress
from ..utils.misc import default_class_repr

__all__ = ["AugmenterManager",]

class AugmenterManager(torch.nn.Module):
    """

    The `Module` to manage the augmenters

    Examples
    --------
    ```python
    import torch
    from easydict import EasyDict as ED
    from torch_ecg.augmenters import AugmenterManager

    config = ED(
        random=False,
        fs=500,
        baseline_wander={},
        label_smooth={},
        mixup={},
        random_flip={},
        random_masking={},
        random_renormalize={},
        stretch_compress={},
    )
    am = AugmenterManager.from_config(config)
    sig, label, mask = torch.rand(2,12,5000), torch.rand(2,26), torch.rand(2,5000,1)
    sig, label, mask = am(sig, label, mask)
    ```
    """
    __name__ = "AugmenterManager"

    def __init__(self, *augs:Optional[Tuple[Augmenter,...]], random:bool=False) -> NoReturn:
        """ finished, checked,

        Parameters
        ----------
        aug: tuple of `Augmenter`, optional,
            the augmenters to be added to the manager
        random: bool, default False,
            whether to apply the augmenters in random order
        """
        super().__init__()
        self.random = random
        self._augmenters = list(augs)

    def _add_baseline_wander(self, **config:dict) -> NoReturn:
        self._augmenters.append(BaselineWanderAugmenter(**config))

    def _add_label_smooth(self, **config:dict) -> NoReturn:
        self._augmenters.append(LabelSmooth(**config))

    def _add_mixup(self, **config:dict) -> NoReturn:
        self._augmenters.append(Mixup(**config))

    def _add_random_flip(self, **config:dict) -> NoReturn:
        self._augmenters.append(RandomFlip(**config))

    def _add_random_masking(self, **config:dict) -> NoReturn:
        self._augmenters.append(RandomMasking(**config))

    def _add_random_renormalize(self, **config:dict) -> NoReturn:
        self._augmenters.append(RandomRenormalize(**config))

    def _add_stretch_compress(self, **config:dict) -> NoReturn:
        self._augmenters.append(StretchCompress(**config))

    def forward(self, sig:Tensor, label:Optional[Tensor], *extra_tensors:Sequence[Tensor], **kwargs:Any) -> Union[Tensor,Tuple[Tensor]]:
        """ finished, checked,

        Parameters
        ----------
        sig: Tensor,
            the ECGs to be augmented, of shape (batch, lead, siglen)
        label: Tensor, optional,
            labels of the ECGs
        extra_tensors: Tensor(s), optional,
            extra tensors to be augmented, e.g. masks for custom loss functions, etc.
        kwargs: keyword arguments

        Returns
        -------
        Tensor(s), the augmented ECGs, labels, and optional extra tensors
        """
        if len(self.augmenters) == 0:
            # raise ValueError("No augmenters added to the manager.")
            return (sig, label, *extra_tensors)
        ordering = list(range(len(self.augmenters)))
        if self.random:
            ordering = sample(ordering, len(ordering))
        for idx in ordering:
            sig, label, *extra_tensors = self.augmenters[idx](sig, label, *extra_tensors, **kwargs)
        return (sig, label, *extra_tensors)

    @property
    def augmenters(self) -> List[Augmenter]:
        return self._augmenters

    def extra_repr(self) -> str:
        indent = 4*" "
        s = f"augmenters = [\n{indent}" + \
            f",\n{2*indent}".join(default_class_repr(aug, depth=2) for aug in self.augmenters) + \
            f"{indent}\n]"
        return s

    @classmethod
    def from_config(cls, config:dict) -> "AugmenterManager":
        """ finished, checked,

        Parameters
        ----------
        config: dict,
            the configuration of the augmenters,
            better to be an `OrderedDict`

        Returns
        -------
        am: AugmenterManager,
            a new instance of `AugmenterManager`
        """
        am = cls(random=config.get("random", False))
        _mapping = {
            "baseline_wander": am._add_baseline_wander,
            "label_smooth": am._add_label_smooth,
            "mixup": am._add_mixup,
            "random_flip": am._add_random_flip,
            "random_masking": am._add_random_masking,
            "random_renormalize": am._add_random_renormalize,
            "stretch_compress": am._add_stretch_compress,
        }
        for aug_name, aug_config in config.items():
            if aug_name in ["fs", "random",]:
                continue
            elif aug_name in _mapping and isinstance(aug_config, dict):
                _mapping[aug_name](fs=config["fs"], **aug_config)
            else:
                # just ignore the other items
                pass
                # raise ValueError(f"Unknown augmenter name: {aug_name}")
        return am

    def rearrange(self, new_ordering:List[str]) -> NoReturn:
        """ finished, checked,

        Parameters
        ----------
        new_ordering: list of str,
            the list of augmenter names in the new order
        """
        _mapping = {
            "".join([w.capitalize() for w in k.split("_")]): k \
                for k in "label_smooth,mixup,random_flip,random_masking,random_renormalize,stretch_compress".split(",")
        }
        _mapping.update({"BaselineWanderAugmenter": "baseline_wander"})
        for k in new_ordering:
            if k not in _mapping:
                _mapping.update({k: k})
        self._augmenters.sort(key=lambda aug: new_ordering.index(_mapping[aug.__name__]))


import sys
import importlib
from collections import defaultdict

class Loader():
    def __init__(self, moduleType, subModRef, hostPackage):
        self.moduleType = moduleType.lower()
        self.subModRef = subModRef
        self.hostPackage = hostPackage
        self.moduleKeyMap = {}

    def fetchModule(self, moduleKey):
        mod = self.moduleKeyMap.get(moduleKey, None)
        if mod is None:
            mod = self.loadModule(moduleKey)

        return mod

    def moduleName(self, moduleKey):
        return "{0}.{1}".format(self.subModRef, moduleKey)

    def loadModule(self, moduleKey):
        moduleName = self.moduleName(moduleKey)
        if moduleName in sys.modules:
            return Module(moduleKey, sys.modules[moduleName], self)

        m = importlib.import_module(moduleName, package=self.hostPackage)
        return self._regModule(moduleKey, m)

    def unloadModule(self, moduleKey):
        self.moduleKeyMap.pop(moduleKey)
        moduleName = self.moduleName(moduleKey)
        if moduleName not in sys.modules:
            return

        Module(moduleKey, sys.modules[moduleName], self).callIfExist("unloaded")
        del sys.modules[moduleName]

    def reloadModule(self, moduleKey):
        moduleName = self.moduleName(moduleKey)
        if moduleName in sys.modules:
            m = sys.modules[moduleName]
            Module(moduleKey, m, self).callIfExist("unloaded")
            m = importlib.reload(m)
            mod = self._regModule(moduleKey, m)
        else:
            mod = self.loadModule(moduleKey)

        return mod

    def _regModule(self, moduleKey, m):
        mod = Module(moduleKey, m, self)
        mod.callIfExist("loaded")
        self.moduleKeyMap[moduleKey] = mod
        return mod

    def unloadAllModules(self):
        for k in self.moduleKeyMap.keys():
            self.unloadModule(k)

class Module:
    def __init__(self, moduleKey, mod, loader):
        self.moduleKey = moduleKey
        self.mod = mod
        self.loader = loader

    def objByBaseType(self, baseType):
        return self.objDictByBaseType([baseType]).get(baseType, [])

    def objDictByBaseType(self, baseTypes):
        objDict = defaultdict(list)
        for type_name in dir(self.mod):
            try:
                t = self.mod.__dict__[type_name]
                if not t.__bases__:
                    continue

                for baseType in baseTypes:
                    if type_name == baseType.__name__:
                        continue

                    if issubclass(t, baseType):
                        objDict[baseType].append(t)

            except AttributeError:
                pass

        return objDict

    def callIfExist(self, interface, *args, **kwds):
        interface = self._normName(interface)
        if interface in self.mod.__dict__:
            self.mod.__dict__[interface](*args, **kwds)

    def call(self, interface, *args, **kwds):
        interface = self._normName(interface)
        self.mod.__dict__[interface](*args, **kwds)

    def _normName(self, name):
        return "{0}_{1}".format(self.loader.moduleType, name)


from __future__ import print_function

import collections
import logging
import os
import subprocess
import textwrap

import drmr
import drmr.drm.base
import drmr.util

class Slurm(drmr.drm.base.DistributedResourceManager):
    name = 'Slurm'

    default_job_template = textwrap.dedent(
        """
        #!/bin/bash

        ####  Slurm preamble

        #SBATCH --export=ALL
        #SBATCH --job-name={{job_name}}
        {% if nodes %}
        #SBATCH --nodes={{nodes}}
        {% endif %}
        {% if node_properties %}
        #SBATCH --constraint="{{'&'.join(node_properties.split(','))}}"
        {% endif %}
        #SBATCH --cpus-per-task={{processors|default(1)}}
        {% if memory %}
        #SBATCH --mem={{memory|default('4000')}}
        {% else %}
        #SBATCH --mem-per-cpu={{processor_memory|default('4000')}}
        {% endif %}
        {% if time_limit %}
        #SBATCH --time={{time_limit}}
        {% endif %}
        {% if array_controls %}
        #SBATCH --output "{{control_directory}}/{{job_name}}_%A_%a_%j.out"
        {% else %}
        #SBATCH --output "{{control_directory}}/{{job_name}}_%j.out"
        {% endif %}
        {% if account %}
        #SBATCH --account={{account}}
        {% endif %}
        {% if destination %}
        #SBATCH --partition={{destination}}
        {% endif %}
        {% if email %}
        #SBATCH --mail-user={{email}}
        {% endif %}
        {% if mail_event_string %}
        #SBATCH --mail-type={{mail_event_string}}
        {% endif %}
        {% if dependencies %}
        #SBATCH --dependency={{resource_manager.make_dependency_string(dependencies)}}
        {% endif %}
        {% if working_directory %}
        #SBATCH --workdir={{working_directory}}
        {% endif %}
        {% if array_controls %}
        #SBATCH --array {{array_controls['array_index_min']|default(1)}}-{{array_controls['array_index_max']|default(1)}}{% if array_controls['array_concurrent_jobs'] %}%{{array_controls['array_concurrent_jobs']}}{% endif %}
        {% endif %}
        {% if raw_preamble %}
        {{raw_preamble}}
        {% endif %}

        ####  End Slurm preamble

        {% if notes %}
        ####  Notes
        {{notes}}
        {% endif %}

        {% if environment_setup %}
        ####  Environment setup
        {% for line in environment_setup %}
        {{line}}
        {% endfor %}
        {% endif %}

        ####  Commands

        {{command}}

        """
    ).lstrip()

    default_array_command_template = textwrap.dedent(
        """
        if [ "$SLURM_ARRAY_TASK_ID" = "{{index}}" ]; then
            {{command}}
        fi
        """
    )

    job_dependency_states = [
        'any',
        'notok',
        'ok',
    ]

    job_state_map = {
        'any': 'any',
        'notok': 'notok',
        'ok': 'ok',
        'start': '',
    }

    mail_event_map = {
        'BEGIN': 'BEGIN',
        'END': 'END',
        'FAIL': 'FAIL',
    }

    def delete_jobs(self, job_ids=None, job_name=None, job_owner=None, dry_run=False):
        logger = self.get_method_logger()

        if job_ids is None:
            job_ids = []

        targets = set(job_ids)
        targets.update(self.get_active_job_ids(job_ids, job_name, job_owner))

        if targets:
            if dry_run:
                logger.info(self.explain_job_deletion(targets, job_name, job_owner, dry_run))
            else:
                if logger.isEnabledFor(logging.DEBUG):
                    logger.debug(self.explain_job_deletion(targets, job_name, job_owner, dry_run))
                command = ['scancel'] + list(targets)
                try:
                    subprocess.check_call(command)
                except subprocess.CalledProcessError as e:
                    raise drmr.exceptions.DeletionError(e.returncode, e.cmd, e.output, targets)

    def get_active_job_ids(self, job_ids=None, job_name=None, job_owner=None):
        logger = self.get_method_logger()

        if job_ids is None:
            job_ids = []

        jobs = set([])

        command = [
            'squeue',
            '-r',
            '--format=%A,%j,%u',
            '--states=CONFIGURING,COMPLETING,PENDING,PREEMPTED,RUNNING,SUSPENDED'
        ]

        squeue_lines = self.capture_process_output(command).splitlines()
        if 1 < len(squeue_lines):
            squeue_lines = set(squeue_lines[1:])
            for job in squeue_lines:
                job_id, name, owner = job.split(',')
                if job_owner and owner != job_owner:
                    continue

                if job_name and job_name not in name:
                    continue

                if job_ids and job_id not in job_ids:
                    continue

                jobs.add(job_id)

        if jobs:
            logger.debug('Found {} active jobs'.format(len(jobs)))
        else:
            logger.debug('No active jobs found.')

        return jobs

    def is_installed(self):
        output = ''
        try:
            output = self.capture_process_output(['scontrol', 'version'])
        except:
            pass

        return 'slurm' in output

    def write_cancel_script(self, job_data, job_ids):
        logger = self.get_method_logger()
        logger.debug('Writing canceller script for {}'.format(job_data))

        self.set_control_directory(job_data)
        filename = drmr.util.absjoin(job_data['control_directory'], job_data['job_name'])
        if not os.path.exists(job_data['control_directory']):
            os.makedirs(job_data['control_directory'])
        with open(filename, 'w') as canceller:
            canceller.write('#!/bin/sh\n\n')
            for job in job_ids:
                canceller.write('scancel %s\n' % job)
            os.chmod(filename, 0o755)

    def make_dependency_string(self, dependencies):
        dependency_string = ''
        if dependencies:
            dependency_list = []
            if not isinstance(dependencies, collections.Mapping):
                raise ValueError('Job data does not contain a map under the "dependencies" key.')
            for state, job_ids in dependencies.items():
                if state not in self.job_dependency_states:
                    raise ValueError('Unsupported dependency state: %s' % state)

                dependency_list.append('after%s:%s' % (state, ':'.join(str(job_id) for job_id in job_ids)))
            dependency_string = ','.join(dependency_list)

        return dependency_string

    def set_mail_event_string(self, job_data):
        if job_data.get('mail_events'):
            job_data['mail_event_string'] = ','.join(
                sorted(
                    self.mail_event_map[event] for event in job_data['mail_events']
                )
            )

    def submit(self, job_filename, hold=False):
        if not self.is_installed():
            raise drmr.exceptions.ConfigurationError('{} is not installed or not usable.'.format(self.name))

        try:
            command = ['sbatch', '--parsable', job_filename]
            if hold:
                command.insert(1, '--hold')

            job_id = self.capture_process_output(command)
            return job_id.strip().split(';')[0]
        except subprocess.CalledProcessError as e:
            raise drmr.exceptions.SubmissionError(e.returncode, e.cmd, e.output)

    def validate_destination(self, destination):
        if not self.is_installed():
            raise drmr.exceptions.ConfigurationError('{} is not installed or not usable.'.format(self.name))

        valid = False
        try:
            command = ['scontrol', 'show', 'partition', destination]
            status = self.capture_process_output(command)
            if status.startswith('PartitionName={}'.format(destination)):
                valid = True
        except:
            pass

        return valid


import pygame, os, sys
from pygame.locals import *
pygame.init()
main_clock = pygame.time.Clock()
WIDTH, HEIGHT = 400 * 2, 300 * 2
BG_COLOUR = (124, 124, 124)
screen = pygame.display.set_mode((WIDTH, HEIGHT), 0, 32)
font = pygame.font.SysFont('Arial', 20, True)
block_list = os.listdir('Assets/block')
block_database = {}
for block in block_list:
    img = pygame.image.load(f'Assets/block/{block}')
    block_database[block] = img.copy()

block_map = {}

def Text_to_List(Text, Divider ,is_int=False):
    List = []
    Current = ''
    for char in Text:
        if char != Divider:
            Current += char
        else:
            if is_int == True:
                try:
                    List.append(int(Current))
                except:
                    List.append(Current)
            else:
                List.append(Current)
            Current = ''
    return List


import matplotlib.pyplot as plt
import numpy as np

x = np.arange(-0.1, 1.1, 0.01)

def f(x):
    y = x**2
    return y

def f2(x):
    y = x
    return y

def ReglaSimpson(a, b, n):
    h = (b - a) / n
    x = np.linspace(a, b, n+1)
    y = f(x)

    integral = 0
    for i in range(0, n-1, 2):
        integral += h/3 * (y[i] + 4*y[i+1] + y[i+2])

    return np.pi * integral

a = 0
b = 2
n = 4000

solucion = ReglaSimpson(a, b, n)

fig, (ax3,ax1,ax2) = plt.subplots(1, 3, figsize=(12, 4))

ax1.plot(x, f(x))
ax1.plot(x, f2(x))
ax1.plot(0, f(0), 'ro')
ax1.plot(1, f2(1), 'ro')

ax1.set_title(u"$\ Gráfica\ de\ los\ lugares\ geométricos$")
ax1.set_xlabel('x')
ax1.set_ylabel('y')

ax1.axhline(0, color='black', linewidth=0.5)
ax1.axvline(0, color='black', linewidth=0.5)
footer_text= f"$\ El\ volumen\ del\ sólido\ de\ revolución$\n $es\ aproximadamente: {solucion}$"
ax1.text(-0.75, -0.3, footer_text, transform=ax2.transAxes,fontsize=12, ha='center')

ax2.fill_betweenx(f(x), 0, x, color='blue', alpha=0.5)
ax2.fill_betweenx(f(x), 0, -x, color='blue', alpha=0.5)
ax2.set_xlabel('Eje X')
ax2.set_ylabel('Eje Y')
ax2.set_title(u"$\ Sólido\ de\ Revolución:\ y\ =\ x^2\ (alrededor\ del\ eje\ Y)$")

ax1.axhline(0, color='black', linewidth=0.5)
ax1.axvline(0, color='black', linewidth=0.5)

ax3.axis('off')

theta = np.linspace(0, 2.*np.pi, 100)
r, th = np.meshgrid(x, theta)
X = r * np.cos(th)
Y = r * np.sin(th)
Z = r**2

ax = fig.add_subplot(131, projection='3d')
ax.plot_surface(X, Y, Z, color='b')

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title(u"$\ Sólido\ de\ revolución$\n $generado\ por\ y\ =\ x^2$")

plt.tight_layout()

plt.show()


import csv
from sklearn.preprocessing import LabelEncoder
from flask import Flask,render_template,request,redirect,url_for,flash
import numpy as np
import mysql.connector
import cv2,os
import pandas as pd
from PIL import Image
import datetime
import time
import math
import pickle
app=Flask(__name__)
app.config['SECRET_KEY']='attendance system'
mydb = mysql.connector.connect(host="localhost", user="root", passwd="", database="smart_attendance")
cursor = mydb.cursor()
@app.route('/')
def index():
    return render_template('index.html')
@app.route('/collection')
def collection():
    return render_template('collection.html')
@app.route('/about')
def about():
    return render_template('about.html')
@app.route('/services')
def services():
    return render_template('services.html')
@app.route('/blog')
def blog():
    return render_template('blog.html')
@app.route('/contact')
def contact():
    return render_template('contact.html')
@app.route('/single')
def single():
    return render_template('single.html')

@app.route("/addback", methods=['POST','GET'])
def addback():
    if request.method=='POST':
        Id=request.form['rno']
        name=request.form['name']
        print(type(Id))
        print(type(name))
        if not Id:
           flash("Please enter roll number properly ","warning")
           return render_template('collection.html')

        elif not name:
            flash("Please enter your name properly ", "warning")
            return render_template('collection.html')

        # elif (Id.isalpha() and name.isalpha()):
        cam = cv2.VideoCapture(0)
        harcascadePath = "Haarcascade/haarcascade_frontalface_default.xml"
        detector = cv2.CascadeClassifier(harcascadePath)
        sampleNum = 0
        df = pd.read_csv("Student_Details/StudentDetails.csv")
        val = df.Id.values
        if Id in str(val):
            flash("Roll already exists", "danger")
            return render_template("index.html")

        else:
            while (True):
                ret, img = cam.read()
                gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                faces = detector.detectMultiScale(gray, 1.3, 5)
                for (x, y, w, h) in faces:
                    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
                        # incrementing sample number
                    sampleNum = sampleNum + 1
                        # saving the captured face in the dataset folder TrainingImage
                    cv2.imwrite("TrainingImage/ " + name + "." + Id + '.' + str(
                            sampleNum) + ".jpg", gray[y:y + h, x:x + w])
                        # display the frame

                else:
                    cv2.imshow('frame', img)
                    # wait for 100 miliseconds
                if cv2.waitKey(100) & 0xFF == ord('q'):
                    break
                    # break if the sample number is morethan 100
                elif sampleNum > 100:
                        break
        cam.release()
        cv2.destroyAllWindows()
        # res = "Roll Number : " + Id + " Name : " + name
        row = [Id, name]
        with open('Student_Details/StudentDetails.csv', 'a+') as csvFile:
            writer = csv.writer(csvFile)
            writer.writerow(row)
        csvFile.close()
        flash("Captured images successfully!!","success")
        return render_template("collection.html")

    return render_template("collection.html")
@app.route('/training')
def training():
    le = LabelEncoder()
    faces, Id = getImagesAndLabels("TrainingImage")
    Id = le.fit_transform(Id)
    output = open('label_encoder.pkl', 'wb')
    pickle.dump(le, output)
    output.close()
    recognizer = cv2.face.LBPHFaceRecognizer_create()
    recognizer.train(faces, np.array(Id))
    recognizer.save(r"Trained_Model\Trainner.yml")

    flash("Model Trained Successfully", "success")
    return render_template('index.html')

def getImagesAndLabels(path):
    imagePaths = [os.path.join(path, f) for f in os.listdir(path)]
    faces = []
    Ids = []
    for imagePath in imagePaths:
        pilImage = Image.open(imagePath).convert('L')
        imageNp = np.array(pilImage, 'uint8')
        Id = str(os.path.split(imagePath)[-1].split(".")[1])
        faces.append(imageNp)
        Ids.append(Id)
    return faces, Ids

@app.route('/prediction')
def prediction():
    recognizer = cv2.face.LBPHFaceRecognizer_create()  # cv2.createLBPHFaceRecognizer()
    recognizer.read(r"Trained_Model\Trainner.yml")
    harcascadePath = r"Haarcascade\haarcascade_frontalface_default.xml"
    faceCascade = cv2.CascadeClassifier(harcascadePath);
    df = pd.read_csv(r"Student_Details\StudentDetails.csv")
    cam = cv2.VideoCapture(0)
    # print(df)
    font = cv2.FONT_HERSHEY_SIMPLEX
    col_names = ['First Name', 'Last Name']
    pkl_file = open('label_encoder.pkl', 'rb')
    le = pickle.load(pkl_file)
    pkl_file.close()
    det=0
    while True:
        ret, im = cam.read()
        gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
        faces = faceCascade.detectMultiScale(gray, 1.2, 5)
        for (x, y, w, h) in faces:
            cv2.rectangle(im, (x, y), (x + w, y + h), (225, 0, 0), 2)
            Id, conf = recognizer.predict(gray[y:y + h, x:x + w])
            # print(conf)
            if (conf < 50):
                det += 1
                tt = le.inverse_transform([Id])
                # print(type(tt))
                tt = tt[0]
                rno=str(tt)
                # print(rno)
                print(det)
                if det==40:
                    ts = time.time()
                    date = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d')
                    timeStamp = datetime.datetime.fromtimestamp(ts).strftime('%H:%M')
                    in_time = int(timeStamp.split(':')[0]) * 60
                    # in_time=400
                    print(in_time)
                    sql = "select count(*) from attendance where rno='%s' and date1='%s'" % (rno, date)
                    z = pd.read_sql_query(sql, mydb)
                    count = z.values[0][0]
                    if 600 >= in_time:# Befor 10
                        print("hhhhhhhhhh")
                        if count == 0:
                            status='Early Coming'
                            sql = "insert into attendance(rno,in_time,in_status,date1) values(%s,%s,%s,%s)"
                            val = (rno, timeStamp, status, date)
                            cursor.execute(sql, val)
                            mydb.commit()
                        else:
                            sql = "select in_time from attendance where rno='%s' and date1='%s'" % (rno, date)
                            result = pd.read_sql_query(sql, mydb)
                            in_time1 = result.values[0][0]
                            in_min = int(in_time1.split(':')[0]) * 60
                            in_min = in_min + int(in_time1.split(':')[1])
                            timeStamp.split(':')
                            out_min = int(timeStamp.split(':')[0]) * 60
                            out_min = out_min + int(timeStamp.split(':')[1])
                            work_time = out_min - in_min
                            work_time1 = math.floor(work_time / 60)
                            work_time2 = work_time % 60
                            w_full = str(work_time1) + ':' + str(work_time2)
                            s = "Early Out"
                            sq = "update attendance set out_time='%s',out_status='%s',overall_time='%s' where rno='%s' and date1='%s'" % (
                            timeStamp, s, w_full, rno, date)
                            cursor.execute(sq)
                            mydb.commit()
                    elif 600 < in_time and 720 >= in_time: #after 10-12
                        print("ddddddddddd")
                        if count == 0:
                            status = 'Late Coming'
                            sql= "insert into attendance(rno,in_time,in_status,date1) values(%s,%s,%s,%s)"
                            val = (rno, timeStamp, status, date)
                            cursor.execute(sql, val)
                            mydb.commit()
                        else:
                            sql = "select in_time from attendance where rno='%s' and date1='%s'" % (rno, date)
                            result = pd.read_sql_query(sql, mydb)
                            in_time1 = result.values[0][0]
                            in_min = int(in_time1.split(':')[0]) * 60
                            in_min = in_min + int(in_time1.split(':')[1])
                            timeStamp.split(':')
                            out_min = int(timeStamp.split(':')[0]) * 60
                            out_min = out_min + int(timeStamp.split(':')[1])
                            work_time = out_min - in_min
                            work_time1 = math.floor(work_time / 60)
                            work_time2 = work_time % 60
                            w_full = str(work_time1) + ':' + str(work_time2)
                            s = "Early Out"
                            sq = "update attendance set out_time='%s',out_status='%s',overall_time='%s' where rno='%s' and date1='%s'" % (
                                timeStamp, s, w_full, rno, date)
                            cursor.execute(sq)
                            mydb.commit()

                    elif 720 <= in_time and 960 >= in_time:#12-4
                        print("lllllllllllllllll")
                        # sql="select count(*) from attendance where rno='%s' and date1='%s'"%(rno,date)
                        # x=pd.read_sql_query(sql,mydb)
                        # count=x.values[0][0]
                        if count==0:
                            print("kkkkkkkkkkkkkkk")
                            status = 'Afternoon Come'
                            sql = "insert into attendance(rno,in_time,in_status,date1) values(%s,%s,%s,%s)"
                            val = (rno, timeStamp, status, date)
                            cursor.execute(sql, val)
                            mydb.commit()
                        else:
                            sql = "select in_time from attendance where rno='%s' and date1='%s'" % (rno, date)
                            result = pd.read_sql_query(sql, mydb)
                            in_time1 = result.values[0][0]
                            in_min = int(in_time1.split(':')[0]) * 60
                            in_min = in_min + int(in_time1.split(':')[1])
                            timeStamp.split(':')
                            out_min = int(timeStamp.split(':')[0]) * 60
                            out_min = out_min + int(timeStamp.split(':')[1])
                            work_time = out_min - in_min
                            work_time1 = math.floor(work_time / 60)
                            work_time2 = work_time % 60
                            w_full = str(work_time1) + ':' + str(work_time2)
                            print("uuuuuuuuuuuuuuuuuu")
                            s="Early Out"
                            sq = "update attendance set out_time='%s',out_status='%s',overall_time='%s' where rno='%s' and date1='%s'" % (
                                timeStamp, s, w_full, rno, date)
                            cursor.execute(sq)
                            mydb.commit()
                    else:#after 4
                        sql = "select in_time from attendance where rno='%s' and date1='%s'" % (rno, date)
                        result = pd.read_sql_query(sql, mydb)
                        in_time1 = result.values[0][0]
                        in_min = int(in_time1.split(':')[0]) * 60
                        in_min = in_min + int(in_time1.split(':')[1])
                        timeStamp.split(':')
                        out_min = int(timeStamp.split(':')[0]) * 60
                        out_min = out_min + int(timeStamp.split(':')[1])
                        work_time = out_min - in_min
                        work_time1 = math.floor(work_time / 60)
                        work_time2 = work_time % 60
                        w_full = str(work_time1) + ':' + str(work_time2)
                        print("aaaaaaaaaaaaaaaa")
                        s = "Perfect Out"
                        sq = "update attendance set out_time='%s',out_status='%s',overall_time='%s' where rno='%s' and date1='%s'" % (
                            timeStamp, s, w_full, rno, date)
                        cursor.execute(sq)
                        mydb.commit()
                    det=0

            else:
                Id = 'Unknown'
                tt = str(Id)
                print(tt)
            if (conf > 55):
                noOfFile = len(os.listdir("ImageUnknown")) + 1
                cv2.imwrite(r"ImageUnknown\Image" + str(noOfFile) + ".jpg", im[y:y + h, x:x + w])
            # print("tt:",str(tt))
            # print("x:", str(x))
            # print("y:", str(y))
            # print("font:", str(font))
            cv2.putText(im, str(tt), (x, y + h), font, 1, (255, 255, 255), 2)
        cv2.imshow('im', im)
        if (cv2.waitKey(1) == ord('q')):
            break
    cam.release()
    cv2.destroyAllWindows()
    flash("Attendance taken","success")
    return render_template('index.html')

@app.route('/viewdata', methods=['POST','GET'])
def viewdata():
    if request.method=='POST':
        rno=request.form['rno']
    sql="select * from attendance where rno='%s' "%(rno)
    x=pd.read_sql_query(sql,mydb)
    x=x.drop(['id','rno'], axis=1)
    return render_template('single.html',row_val=x.values.tolist())

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


import pandas as pd
from sklearn import linear_model
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split

df = pd.read_csv("survey_results_public.csv", header=0,
                 usecols=['YearsCode', 'Age', 'Age1stCode'])
df = df.dropna()
df.loc[df['YearsCode'] == 'Less than 1 year'] = 0
df.loc[df['YearsCode'] == 'More than 50 years'] = 51
df.loc[df['Age1stCode'] == 'Younger than 5 years'] = 0
df.loc[df['Age1stCode'] == 'Older than 85'] = 86
df['YearsCode'] = df['YearsCode'].astype("float64")
df['Age1stCode'] = df['Age1stCode'].astype('float64')

Q1 = df.quantile(0.25)
Q3 = df.quantile(0.75)
IQR = Q3 - Q1
df = df[~((df < (Q1 - 1.5 * IQR)) | (df > (Q3 + 1.5 * IQR))).any(axis=1)]
df.describe()

# Dzielenie danych na zbiór treningowy i testowy
X1_train, X1_test, Y_train, Y_test = train_test_split(df[['Age']], df[['YearsCode']],
                                                      test_size=0.33, random_state=42)
X1_X2_train, X1_X2_test, Y_train, Y_test = train_test_split(df[['Age', 'Age1stCode']], df[['YearsCode']],
                                                            test_size=0.33, random_state=42)

regr1 = linear_model.LinearRegression()
regr1.fit(X1_train, Y_train)

print('Coefficients: \n', regr1.coef_)
print("Residual sum of squares: %.2f"
      % mean_squared_error(Y_test, regr1.predict(X1_test)))

regr2 = linear_model.LinearRegression()
regr2.fit(X1_X2_train, Y_train)

print('Coefficients: \n', regr2.coef_)
print("Residual sum of squares: %.2f"
      % mean_squared_error(Y_test, regr2.predict(X1_X2_test)))


import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
host="0.0.0.0"
port=1234
s.connect((host,port))
#server, addr=s.accept()
key=''

import keyboard

while True:
    if keyboard.is_pressed('a'):
        print("You pressed a arrow")
        key='a'
        s.send(bytes("{}\n".format(key), "utf-8"))
        break
    if keyboard.is_pressed('s'):
        print("You pressed s arrow")
        key = 's'
        s.send(bytes("{}\n".format(key), "utf-8"))
        break
    if keyboard.is_pressed('w'):
        print("You pressed w arrow")
        key = 'w'
        s.send(bytes("{}\n".format(key), "utf-8"))
        break
    if keyboard.is_pressed('d'):
        print("You pressed d arrow")
        key = 'd'
        s.send(bytes("{}\n".format(key), "utf-8"))
        break

# display client address

# send message to the client after
# encoding into binary string

#server.send(key)
#server.close()
#if pyautogui.click()=='left':
   # print("got here")

#if pyautogui.keyDown('left'):
    #print("here")

#print(ord('a'))
s.close()


import logging
import math
from typing import Union, Dict, Set
from enum import Enum
import threading

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.units import Units
from sc2.unit import Unit
from sc2.dicts.unit_trained_from import UNIT_TRAINED_FROM
from sc2.game_data import AbilityData, GameData, UnitTypeData
from sc2.data import race_worker
from sc2.data import race_townhalls
from sc2.data import race_gas
from sc2.data import Race
from sc2.position import Point2, Point3
from sc2.ids.ability_id import AbilityId
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.upgrade_id import UpgradeId
from sc2.constants import (
    IS_STRUCTURE,
    TERRAN_TECH_REQUIREMENT,
    PROTOSS_TECH_REQUIREMENT,
    ZERG_TECH_REQUIREMENT,
    EQUIVALENTS_FOR_TECH_PROGRESS,
    ALL_GAS
)
from BuildListProcessorDicts import (
    BASE_BUILDINGS,
    CONVERT_TO_ID,
    StartLocation
)

# Definitions
# ----------------------------------------

race_supplyUnit: Dict[Race, UnitTypeId] = {
    Race.Protoss: UnitTypeId.PYLON,
    Race.Terran: UnitTypeId.SUPPLYDEPOT,
    Race.Zerg: UnitTypeId.OVERLORD,
}

raceBasicTownhall = {
    Race.Terran: UnitTypeId.COMMANDCENTER,
    Race.Zerg: UnitTypeId.HATCHERY,
    Race.Protoss: UnitTypeId.NEXUS
}

class Player(Enum):
    """A way of differentiating players.
    """
    PLAYER_ONE = 1,
    PLAYER_TWO = 2,

class Winner(Enum):
    """A way of differentiating winners.
    """
    PLAYER_ONE = 1,
    PLAYER_TWO = 2,
    UNKNOWN = 3

# Class
# ----------------------------------------

class BuildListProcessBotBase(sc2.BotAI):
    """ Base Class for different race specific bots.

    Combines functionality for clean race specific implementations. It
    combines buildlist processing capability and precondition checking.
    There is a lot more functionality in here to ease implementation.
    """

    # Constructor
    # ----------------------------------------

    def __init__(self, inputBuildList, player: Player):
        """Initialize the bot.

        Provide a buildlist as a list of build tasks. Strings must be
        written as in CONVERT_TO_ID from BuildListProcessorDicts.py.

        Player param must be either player one or player two.
        """
        # player as string
        self.playerString = "UNKNOWN"
        if player == Player.PLAYER_ONE:
            self.playerString = "PlayerOne"
        else:
            self.playerString = "PlayerTwo"
        # logging
        self.loggerBase = logging.getLogger("BuildListProcessBotBase" + self.playerString)
        # player
        self.player: Player = player
        # start locations of both players
        BuildListProcessBotBase.PLAYER_ONE_START_LOCATION: StartLocation = StartLocation.UNKNOWN
        BuildListProcessBotBase.PLAYER_TWO_START_LOCATION: StartLocation = StartLocation.UNKNOWN
        # my start location
        self.startLocation: StartLocation = StartLocation.UNKNOWN
        # communication between bots
        # will be set when player one has set his start location
        BuildListProcessBotBase.PLAYER_ONE_START_LOCATION_EVENT: threading.Event = threading.Event()
        # will be set when player two has set his start location
        BuildListProcessBotBase.PLAYER_TWO_START_LOCATION_EVENT: threading.Event = threading.Event()
        # expansion locations (different datastructures for computing the expansion locations)
        self.expansionLocationsComputed = False
        self.bottomExpansions = list()
        self.topExpansions = list()
        self.leftExpansions = list()
        self.rightExpansions = list()
        BuildListProcessBotBase.PLAYER_ONE_EXPANSION_LOCATIONS = list()
        BuildListProcessBotBase.PLAYER_TWO_EXPANSION_LOCATIONS = list()
        self.expansionLocations = list()
        # build list
        self.buildList = inputBuildList
        self.currentTask = UnitTypeId.NOTAUNIT
        self.done = False
        self.remainingBuildTasks = dict()

        # gas building locations
        self.occupiedGeysers = set()

        # attacking
        self.attacking = False
        BuildListProcessBotBase.PLAYER_ONE_READY_TO_ATTACK = False
        BuildListProcessBotBase.PLAYER_TWO_READY_TO_ATTACK = False
        self.attackDone = False
        BuildListProcessBotBase.PLAYER_ONE_ARMY_COUNT = -1
        BuildListProcessBotBase.PLAYER_TWO_ARMY_COUNT = -1
        BuildListProcessBotBase.WINNER: Winner = Winner.UNKNOWN

    # Startup Preparation
    # ----------------------------------------

    def setSelfStartLocation(self):
        """Derive start location from coordinates.

        Sets the bots start location (enum StartLocation in BuildListProcesserDicts.py)
        based on the start location coordinates.
        """
        if (self.game_info.player_start_location.x == 24.5):
            # left side of map
            if (self.game_info.player_start_location.y == 22.5):
                self.startLocation = StartLocation.BOTTOM_LEFT
            else:
                self.startLocation = StartLocation.TOP_LEFT
        else:
            # right side of map
            if (self.game_info.player_start_location.y == 22.5):
                self.startLocation = StartLocation.BOTTOM_RIGHT
            else:
                self.startLocation = StartLocation.TOP_RIGHT

        self.loggerBase.info("Start location is " + str(self.startLocation))

        if self.player == Player.PLAYER_ONE:
            BuildListProcessBotBase.PLAYER_ONE_START_LOCATION = self.startLocation
        else:
            BuildListProcessBotBase.PLAYER_TWO_START_LOCATION = self.startLocation

    def getCorrespondingStartLocation(self, point: Point2):
        """For a point get the start location.
        """
        if (point[0] == 24.5):
            # left side of map
            if (point[1] == 22.5):
                return StartLocation.BOTTOM_LEFT
            else:
                return StartLocation.TOP_LEFT
        else:
            # right side of map
            if (point[1] == 22.5):
                return StartLocation.BOTTOM_RIGHT
            else:
                return StartLocation.TOP_RIGHT

    def getLocationFromStartLocation(self, startLocation: StartLocation):
        """Return the coordinates for a start location.

        Counterpart to getCorrespondingStartLocation().
        """
        if startLocation == StartLocation.BOTTOM_LEFT:
            return Point2((24.5, 22.5))
        if startLocation == StartLocation.BOTTOM_RIGHT:
            return Point2((127.5, 22.5))
        if startLocation == StartLocation.TOP_RIGHT:
            return Point2((127.5, 125.5))
        if startLocation == StartLocation.TOP_LEFT:
            return Point2((24.5, 125.5))
        raise Exception("Location is not a start location! " + str(startLocation))

    def findNextExpansion(self, current: Point2, ccwDirection: bool):
        """Get the next expansion in a certain direction.

        Only works on simple map. This is used in determining the expansions for
        each bot on the map.
        """
        if current in self.game_info.start_locations + [self.game_info.player_start_location]:
            # first need to check if current is one of the start locations
            # "corners of map"
            correspondingStartLocation = self.getCorrespondingStartLocation(current)
            if (correspondingStartLocation == StartLocation.UNKNOWN):
                raise Exception("Could not find start location for " + str(current))
            if ccwDirection:
                if correspondingStartLocation == StartLocation.BOTTOM_LEFT:
                    return self.bottomExpansions[1] # they are sorted from low to high x so this is fine
                if correspondingStartLocation == StartLocation.BOTTOM_RIGHT:
                    return self.rightExpansions[1]
                if correspondingStartLocation == StartLocation.TOP_RIGHT:
                    return self.topExpansions[len(self.topExpansions) - 2]
                if correspondingStartLocation == StartLocation.TOP_LEFT:
                    return self.leftExpansions[len(self.leftExpansions) - 2]
            else:
                # cw direction
                if correspondingStartLocation == StartLocation.BOTTOM_LEFT:
                    return self.leftExpansions[1]
                if correspondingStartLocation == StartLocation.BOTTOM_RIGHT:
                    return self.bottomExpansions[len(self.bottomExpansions) - 2]
                if correspondingStartLocation == StartLocation.TOP_RIGHT:
                    return self.rightExpansions[len(self.rightExpansions) - 2]
                if correspondingStartLocation == StartLocation.TOP_LEFT:
                    return self.topExpansions[1]
        else:
            # not in one of the corners
            if current in self.bottomExpansions:
                index = self.bottomExpansions.index(current)
                if ccwDirection:
                    # go right
                    return self.bottomExpansions[index+1]
                else:
                    # go left
                    return self.bottomExpansions[index-1]
            if current in self.rightExpansions:
                index = self.rightExpansions.index(current)
                if ccwDirection:
                    return self.rightExpansions[index+1]
                else:
                    return self.rightExpansions[index-1]
            if current in self.topExpansions:
                index = self.topExpansions.index(current)
                if ccwDirection:
                    return self.topExpansions[index-1]
                else:
                    return self.topExpansions[index+1]
            if current in self.leftExpansions:
                index = self.leftExpansions.index(current)
                if ccwDirection:
                    return self.leftExpansions[index-1]
                else:
                    return self.leftExpansions[index+1]

    def computeExpansionLocations(self):
        """Computes expansion locations.

        Only works on simple map. This is used in determining the expansions for
        each bot on the map."""
        possibleExpansionLocations = set()

        # add all expansions including player start locations to the set
        for expansionLocation in self.expansion_locations_list:
            possibleExpansionLocations.add(expansionLocation)

        for expansionLocation in possibleExpansionLocations:
            # the bottom row of expansions is roughly at 22.5
            # use a threshhold to ensure every single one is in
            if abs(expansionLocation[1] - 22.5) < 2.0:
                self.bottomExpansions.append(expansionLocation)
        self.bottomExpansions.sort(key=lambda x: x[0])

        for expansionLocation in possibleExpansionLocations:
            # the top row of expansions is roughly at 125.5
            # use a threshhold to ensure every single one is in
            if abs(expansionLocation[1] - 125.5) < 2.0:
                self.topExpansions.append(expansionLocation)
        self.topExpansions.sort(key=lambda x: x[0])

        for expansionLocation in possibleExpansionLocations:
            # the left column of expansions is roughly at 24.5
            # use a threshhold to ensure every single one is in
            if abs(expansionLocation[0] - 24.5) < 2.0:
                self.leftExpansions.append(expansionLocation)
        self.leftExpansions.sort(key=lambda x: x[1])

        for expansionLocation in possibleExpansionLocations:
            # the right column of expansions is roughly at 24.5
            # use a threshhold to ensure every single one is in
            if abs(expansionLocation[0] - 127.5) < 2.0:
                self.rightExpansions.append(expansionLocation)
        self.rightExpansions.sort(key=lambda x: x[1])

        # there is always two directions in which we could go to find expansions:
        # in ccw and in cw direction
        # we always prefer ccw direction but alternate between the two to stay close to our main

        # my side:
        # --------------------

        playerOnePreferredExpansionCCWDirection = list()
        playerOnePreferredExpansionCWDirection = list()

        currentCCW: Point2 = self.getLocationFromStartLocation(BuildListProcessBotBase.PLAYER_ONE_START_LOCATION)
        currentCW: Point2 = currentCCW

        currentCCW = self.findNextExpansion(currentCCW, True)
        currentCW = self.findNextExpansion(currentCW, False)

        while currentCW != currentCCW:
            # store
            playerOnePreferredExpansionCCWDirection.append(currentCCW)
            playerOnePreferredExpansionCWDirection.append(currentCW)
            # advance
            currentCCW = self.findNextExpansion(currentCCW, True)
            currentCW = self.findNextExpansion(currentCW, False)

        # append one last time
        playerOnePreferredExpansionCCWDirection.append(currentCCW)
        playerOnePreferredExpansionCWDirection.append(currentCW)

        # opponents side side:
        # --------------------

        playerTwoPreferredExpansionCCWDirection = list()
        playerTwoPreferredExpansionCWDirection = list()

        currentCCW: Point2 = self.getLocationFromStartLocation(BuildListProcessBotBase.PLAYER_TWO_START_LOCATION)
        currentCW: Point2 = currentCCW

        currentCCW = self.findNextExpansion(currentCCW, True)
        currentCW = self.findNextExpansion(currentCW, False)

        while currentCW != currentCCW:
            # store
            playerTwoPreferredExpansionCCWDirection.append(currentCCW)
            playerTwoPreferredExpansionCWDirection.append(currentCW)
            # advance
            currentCCW = self.findNextExpansion(currentCCW, True)
            currentCW = self.findNextExpansion(currentCW, False)

        # append one last time
        playerTwoPreferredExpansionCCWDirection.append(currentCCW)
        playerTwoPreferredExpansionCWDirection.append(currentCW)

        # now of both players the preferences of expansions in both possible directions are known

        # remove both start locations from possibleExpansionLocations
        possibleExpansionLocations.remove(self.getLocationFromStartLocation(BuildListProcessBotBase.PLAYER_ONE_START_LOCATION))
        possibleExpansionLocations.remove(self.getLocationFromStartLocation(BuildListProcessBotBase.PLAYER_TWO_START_LOCATION))

        playerOneExpansionLocations = list()
        playerTwoExpansionLocations = list()

        # ccw direction is always taken first
        ccwRoundPlayerOne = True
        ccwRoundPlayerTwo = True
        while bool(possibleExpansionLocations):
            # player one goes first
            if ccwRoundPlayerOne:
                playerOneSelectedExpansion = playerOnePreferredExpansionCCWDirection.pop(0)
                if playerOneSelectedExpansion in possibleExpansionLocations:
                    playerOneExpansionLocations.append(playerOneSelectedExpansion)
                    possibleExpansionLocations.remove(playerOneSelectedExpansion)
                    ccwRoundPlayerOne = False
                else:
                    playerOneSelectedExpansion = playerOnePreferredExpansionCWDirection.pop(0)
                    if playerOneSelectedExpansion in possibleExpansionLocations:
                        playerOneExpansionLocations.append(playerOneSelectedExpansion)
                        possibleExpansionLocations.remove(playerOneSelectedExpansion)
                        ccwRoundPlayerOne = True
                    else:
                        raise Exception("There are no expansion locations left for player one even though there is still locations available!")
            else:
                playerOneSelectedExpansion = playerOnePreferredExpansionCWDirection.pop(0)
                if playerOneSelectedExpansion in possibleExpansionLocations:
                    playerOneExpansionLocations.append(playerOneSelectedExpansion)
                    possibleExpansionLocations.remove(playerOneSelectedExpansion)
                    ccwRoundPlayerOne = True
                else:
                    playerOneSelectedExpansion = playerOnePreferredExpansionCCWDirection.pop(0)
                    if playerOneSelectedExpansion in possibleExpansionLocations:
                        playerOneExpansionLocations.append(playerOneSelectedExpansion)
                        possibleExpansionLocations.remove(playerOneSelectedExpansion)
                        ccwRoundPlayerOne = False
                    else:
                        raise Exception("There are no expansion locations left for player one even though there is still locations available!")
            # player two goes second
            if ccwRoundPlayerTwo:
                playerTwoSelectedExpansion = playerTwoPreferredExpansionCCWDirection.pop(0)
                if playerTwoSelectedExpansion in possibleExpansionLocations:
                    playerTwoExpansionLocations.append(playerTwoSelectedExpansion)
                    possibleExpansionLocations.remove(playerTwoSelectedExpansion)
                    ccwRoundPlayerTwo = False
                else:
                    playerTwoSelectedExpansion = playerTwoPreferredExpansionCWDirection.pop(0)
                    if playerTwoSelectedExpansion in possibleExpansionLocations:
                        playerTwoExpansionLocations.append(playerTwoSelectedExpansion)
                        possibleExpansionLocations.remove(playerTwoSelectedExpansion)
                        ccwRoundPlayerTwo = True
                    else:
                        raise Exception("There are no expansion locations left for player one even though there is still locations available!")
            else:
                playerTwoSelectedExpansion = playerTwoPreferredExpansionCWDirection.pop(0)
                if playerTwoSelectedExpansion in possibleExpansionLocations:
                    playerTwoExpansionLocations.append(playerTwoSelectedExpansion)
                    possibleExpansionLocations.remove(playerTwoSelectedExpansion)
                    ccwRoundPlayerTwo = False
                else:
                    playerTwoSelectedExpansion = playerTwoPreferredExpansionCCWDirection.pop(0)
                    if playerTwoSelectedExpansion in possibleExpansionLocations:
                        playerTwoExpansionLocations.append(playerTwoSelectedExpansion)
                        possibleExpansionLocations.remove(playerTwoSelectedExpansion)
                        ccwRoundPlayerTwo = True
                    else:
                        raise Exception("There are no expansion locations left for player one even though there is still locations available!")

        BuildListProcessBotBase.PLAYER_ONE_EXPANSION_LOCATIONS = playerOneExpansionLocations
        BuildListProcessBotBase.PLAYER_TWO_EXPANSION_LOCATIONS = playerTwoExpansionLocations

        return

    def fillExpansionLocations(self):
        """Wrapper for calling computeExpansionLocations() and doing some logging.

        Only works on simple map. This is used in determining the expansions for
        each bot on the map.
        """
        assert(BuildListProcessBotBase.PLAYER_TWO_START_LOCATION != StartLocation.UNKNOWN)
        assert(BuildListProcessBotBase.PLAYER_ONE_START_LOCATION != StartLocation.UNKNOWN)
        # compute expansions
        self.loggerBase.info("Computing expansion locations...")
        self.computeExpansionLocations()
        self.loggerBase.info("Player one expansion locations: " + str(BuildListProcessBotBase.PLAYER_ONE_EXPANSION_LOCATIONS))
        self.loggerBase.info("Player two expansion locations: " + str(BuildListProcessBotBase.PLAYER_TWO_EXPANSION_LOCATIONS))

    def onStartBase(self):
        """ Call this from implementing bot in on_start().

        startup routine. Should prepare:
           - enemy location
           - scan the buildlist
        """
        self.setSelfStartLocation()
        self.loggerBase.info("Available start locations: " + str(self.game_info.start_locations))
        self.scanBuildList()
        self.prepareBuildListCompletedCheck()

    # BuildList
    # ----------------------------------------

    def checkAndAdvance(self):
        """ Checks buildlist progress and advances it.
        """

        if not self.done:
            if self.currentTask == UnitTypeId.NOTAUNIT:
                if len(self.buildList) > 0:
                    # extract first build list element and set as current task
                    nextTaskName = self.buildList.pop(0)
                    self.currentTask = self.unitToId(nextTaskName)
                    self.loggerBase.info("Beginning next task: " + nextTaskName + "(" + str(self.currentTask) + ")")
                else:
                    self.done = True

    def finishedCurrentTask(self):
        """ Advance buildlist by one task.
        """
        self.loggerBase.info("Finished task: " + str(self.currentTask))
        self.currentTask = UnitTypeId.NOTAUNIT

    def scanBuildList(self):
        """ Preprocess/check of buildlist.

        Checks:
        - number of expansions must be smaller or equal to seven
        - number of gas buildings must be smaller or equal to 2*(numberOfExpansions+1)
        - everything in buildlist is known to the bot
        """
        buildListCopy = self.buildList.copy()

        expansionCount: int = 0
        gasBuildingCount: int = 0

        for element in buildListCopy:
            unitId: UnitTypeId = self.unitToId(element)

            if unitId == raceBasicTownhall[self.race]:
                expansionCount += 1
            elif unitId == race_gas[self.race]:
                gasBuildingCount += 1

        if expansionCount > 7:
            raise Exception("BuildList is invalid! To many expansions. Max: 7; Have: " + str(expansionCount))

        # add one to expansion count (natural)
        expansionCount += 1
        maxNumberOfGas: int = expansionCount * 2

        if gasBuildingCount > maxNumberOfGas:
            raise Exception("BuildList is invalid! To many gas buildings. Max:" + str(maxNumberOfGas) + "; Have: " + str(gasBuildingCount))

        self.loggerBase.info("BuildList has no errors.")

    def prepareBuildListCompletedCheck(self):
        """Setup check when buildlist is completed that everything was built.
        """
        self.remainingBuildTasks[race_worker[self.race]] = 12
        self.remainingBuildTasks[raceBasicTownhall[self.race]] = 1
        self.raceSpecificUnitAndStructureCreations()
        # add everything from build list
        for element in self.buildList:
            # convert to id
            unitId: UnitTypeId = self.unitToId(element)
            if unitId in self.remainingBuildTasks:
                self.remainingBuildTasks[unitId] += 1
            else:
                self.remainingBuildTasks[unitId] = 1
        self.loggerBase.info("Created remaining build tasks data structure: " + str(self.remainingBuildTasks))

    def raceSpecificUnitAndStructureCreations(self):
        """ Add certain unit creations to build list completed check.

        Thhis has to be implemented by a child class to avoid counting initial units
        (automatically given at the start of the game) as units that were created
        use self.remainingBuildTasks to include them
        an example for workers is given in self.prepareBuildListCompletedCheck()
        """
        raise Exception("Has to be implemented by race specific class!")

    async def on_building_construction_complete(self, unit: Unit):
        """When building is completed store that for later checks.
        """
        if not self.raceSpecificStructureCompletedIgnore(unit.type_id):
            self.loggerBase.info("Structure " + str(unit.type_id) + " completed!")
            self.remainingBuildTasks[unit.type_id] -= 1

    async def on_unit_created(self, unit: Unit):
        """When unit is created store that for later checks.
        """
        if not self.raceSpecificUnitCompletedIgnore(unit.type_id):
            self.loggerBase.info("Unit " + str(unit.type_id) + " completed!")
            self.remainingBuildTasks[unit.type_id] -= 1

    def raceSpecificStructureCompletedIgnore(self, unit: UnitTypeId):
        """Some structures need to be ignored by races."""
        raise Exception("Has to be implemented by race specific bot!")

    def raceSpecificUnitCompletedIgnore(self, unit: UnitTypeId):
        """Some units need to be ignored by races."""
        raise Exception("Has to be implemented by race specific bot!")

    def checkBuildListCompleted(self):
        """Post buildlist completed check.

        This does not check if the list of build tasks is empty but it checks
        if all buildings are ready and idle and if all remaining build tasks
        have been completed
        """

        if self.done:

            allStructuresReady = True
            allStructuresIdle = True

            for structure in self.structures:
                allStructuresReady = allStructuresReady and structure.is_ready
                if not structure.is_ready:
                    self.loggerBase.debug(str(structure) + " is not ready")
                allStructuresIdle = allStructuresIdle and structure.is_idle
                if not structure.is_idle:
                    self.loggerBase.debug(str(structure) + " is not idle")

            allUnitsReady = True
            for unit in self.units:
                allUnitsReady = allUnitsReady and unit.is_ready
                if not unit.is_ready:
                    self.loggerBase.debug(str(unit) + " is not ready!")

            if self.race == Race.Zerg:
                allUnitsReady = self.units.filter(lambda unit: unit.type_id == UnitTypeId.EGG).empty

            if not allStructuresReady:
                self.loggerBase.debug("Buildlist is done but not all structures are ready!")

            if not allStructuresIdle:
                self.loggerBase.debug("Buildlist is done but not all structures are idle!")

            if not allUnitsReady:
                self.loggerBase.debug("Buildlist is done but not all units are ready!")

            if allStructuresReady and allStructuresIdle and allUnitsReady:
                # the final check of remainingBuildTasks if everything was built
                for unitId, count in self.remainingBuildTasks.items():
                    # safety net for terran (and protoss)
                    if self.already_pending(unitId) > 0:
                        return False # the building is pending --> worker walking to build etc.
                    if unitId in BASE_BUILDINGS:
                        self.loggerBase.info("All units: \n" + str(self.all_units))
                    if count != 0:
                        if unitId == race_worker[self.race]:
                            self.loggerBase.warn("The bot did not produce the correct number of workers. Timings will not be correct but the army strength can still be compared!")
                        else:
                            if count > 0:
                                raise Exception("Everything should be done but " + str(unitId) + " was not build as many times as it was supposed to!")
                            else:
                                raise Exception("Everything should be done but " + str(unitId) + " was build more than it was supposed to!")
                return True
            else:
                return False

        else:
            return False

    # Units
    # ----------------------------------------

    def unitToId(self, unitName):
        """Convert unit name to id.
        """
        if unitName not in CONVERT_TO_ID:
            raise Exception(unitName + " is not available in CONVERT_TO_ID!")
        return CONVERT_TO_ID[unitName]

    # returns the ids of producers for the current task
    def getProducerIdsForCurrentTask(self):
        """Get producer ids for current task.
        """
        if self.currentTask in UNIT_TRAINED_FROM:
            return UNIT_TRAINED_FROM[self.currentTask]
        elif self.currentTask in BASE_BUILDINGS:
            return BASE_BUILDINGS[self.currentTask]
        else:
            raise Exception("" + str(self.currentTask) + " is not available in UNIT_TRAINED_FROM and not in BASE_BUILDINGS (only terran)!")

    # returns actual units of producers for the current task
    def getProducerUnitsForCurrentTask(self):
        """Get the units (actual units in the game) that could produce the current task.

        Does not check if these units are busy.
        """
        producerIds: Set[UnitTypeId] = self.getProducerIdsForCurrentTask()
        return (self.units + self.structures).filter(lambda unit: unit.type_id in producerIds)

    # returns true if the given unit id is a worker of the currently played race
    def isWorker(self, unitId: UnitTypeId):
        """Checks if a certain unit is a worker.
        """
        return race_worker[self.race] == unitId

    # Building/Training Conditions
    # ----------------------------------------

    def checkIfProducerExists(self):
        """Check if a producer for the current task exists.

        returns a pair of bools stating if a producer is currently available and
        if we can wait such that it becomes available.
        """
        result = (False, False)
        producersAvailable: Units = self.getProducerUnitsForCurrentTask()
        producersAvailable = producersAvailable.ready
        if not producersAvailable.empty:
            # is any of these available producers idle
            if any(producer.is_idle or producer.is_gathering for producer in producersAvailable):
                result = (True, True)
            else:
                result = (False, True)
        else:
            # is any of the unit ids being produced or queued?
            producerIds = self.getProducerIdsForCurrentTask()
            if any(self.already_pending(producerId) > 0.0 for producerId in producerIds):
                result = (False, True)
            # for zerg producer might be larva. already_pending will not account for that
            # as long as we have a least one Hatchery/Lair/Hive
            if (not result[1]) and self.race == Race.Zerg and UnitTypeId.LARVA in producerIds and len(self.townhalls) > 0:
                result = (False, True)

        return result

    def checkCosts(self):
        """Check if the current task is affordable.

        returns a pair of bools stating if the task is currently affordable and
        if we can wait such that it becomes affordable.
        """
        cost = self.calculate_cost(self.currentTask)

        # TODO: the check here (self.workers.gathering > 0) does not work for terran and protoss

        minerals = (True, True)
        if cost.minerals > self.minerals:
            # not enough right now but maybe later?
            # at least some workers and
            if len(self.workers.gathering) > 0 and len(self.townhalls) > 0:
                minerals = (False, True)
            else:
                minerals = (False, False)
                self.loggerBase.warn("There are not enough minerals to build " + str(self.currentTask) + " and waiting does not help!")

        vespene = (True, True)
        if cost.vespene > self.vespene:
            # not enough right now but maybe later?
            if len(self.workers.gathering) > 0 and len(self.gas_buildings.ready) + self.already_pending(race_gas[self.race]):
                # waiting helps
                vespene = (False, True)
            else:
                vespene = (False, False)
                self.loggerBase.warn("There are not enough vespene to build " + str(self.currentTask) + " and waiting does not help!")

        supply = (True, True)
        supply_cost = self.calculate_supply_cost(self.currentTask)
        # make sure this thing actually has supply cost
        if isinstance(self.currentTask, UnitTypeId):
            if supply_cost and supply_cost > self.supply_left:
                # we dont have enough supply right now but maybe later?
                supply = (False, True)
                # check if supply building is being built
                # already pending checks everything: check its documentation
                if self.already_pending(race_supplyUnit[self.race]) == 0:
                    supply = (False, False)
                    self.loggerBase.warn("There is not enough supply to build " + str(self.currentTask) + " and waiting does not help!")

        return (minerals[0] and vespene[0] and supply[0], minerals[1] and vespene[1] and supply[1])

    def checkIfTechRequirementFulfilled(self):
        """Check if the tech requirement for the current task is fulfilled.

        returns a pair of bools stating if the requirement is currently fulfilled and
        if we can wait such that it becomes fulfilled.
        """

        race_dict = {
            Race.Protoss: PROTOSS_TECH_REQUIREMENT,
            Race.Terran: TERRAN_TECH_REQUIREMENT,
            Race.Zerg: ZERG_TECH_REQUIREMENT,
        }
        # this gets the requirement for the unit we are checking
        requirement = race_dict[self.race][self.currentTask]
        # if there is no requirement we can just skip the remaining stuff
        if UnitTypeId.NOTAUNIT == requirement:
            return (True, True)
        #
        requirementEquivalents = {requirement}
        for equiv_structure in EQUIVALENTS_FOR_TECH_PROGRESS.get(requirement, []):
            requirementEquivalents.add(equiv_structure)

        correspondingStructures = self.structures.filter(lambda unit: unit.type_id in requirementEquivalents)

        # any of them already existing?
        fulfilled = bool(correspondingStructures.ready)

        # is any of these pending?
        # if yes waiting helps
        # waiting is also fine if it already exists
        waitingHelps = fulfilled
        if not fulfilled:
            waitingHelps = self.already_pending(requirement) > 0

        return (fulfilled, waitingHelps)

    def checkPreconditions(self):
        """Combine producer, cost and tech requirement check.

        returns a bool if the current task can be built right now.
        """
        # check if the producer exists or is under construction and if the producer is idle
        producerExists, canWaitProducer = self.checkIfProducerExists()
        if not producerExists and not canWaitProducer:
            raise Exception("There must be a producer for " + str(self.currentTask))
        # check if we can afford the unit/structure
        resourcesExist, canWaitResources = self.checkCosts()
        if not resourcesExist and not canWaitResources:
            raise Exception("There is not enough minerals, vespene or supply and waiting for it will not help!")
        # check if tech requirement is fullfilled only if the resources exist
        requirementFulfilled, canWaitRequirement = self.checkIfTechRequirementFulfilled()
        if not requirementFulfilled and not canWaitRequirement:
            raise Exception("The requirement for " + str(self.currentTask) + " is not fullfilled!")

        # just return if we are able to build immediately --> if not that means we have to wait
        return producerExists and resourcesExist and requirementFulfilled

        # finds an appropriate worker (closest to position or unit)

    # Building
    # ----------------------------------------
    def getWorker(self, position: Union[Unit, Point2, Point3]):
        """Get a worker closest to a certain point on the map.
        """
        workersGathering: Units = self.workers.gathering

        if workersGathering:
            # select worker closest to pos or unit
            return workersGathering.closest_to(position)
        else:
            raise Exception("There are no gathering workers which could be used to build " + self.currentTask)

    def buildGasBuildingAtTownhall(self, townhall: Unit):
        """Build gas building at a certain townhall.

        Needs storage for occupied geysers as library function can_place does not
        work in that case.
        """
        # all vespene geysers closer than distance ten to the current townhall
        vespeneGeysers: Units  = self.vespene_geyser.closer_than(10, townhall)
        self.loggerBase.info("Found " + str(len(vespeneGeysers)) + " vespene geyser locations!")
        # check all locations
        #  apparently can_place does not work in this situation. it will say that a second refinery can be placed on the occupied geyser
        for vespeneGeyser in vespeneGeysers:
            if vespeneGeyser.position not in self.occupiedGeysers:
                if self.can_place(self.currentTask, (vespeneGeyser.position)):
                    worker: Unit = self.getWorker(vespeneGeyser)
                    worker.build_gas(vespeneGeyser)
                    self.occupiedGeysers.add(vespeneGeyser.position)
                    return True
                else:
                    self.loggerBase.warn("Can place stated not possible to place even though according to self.occupiedGeysers it should be free!")

        # if we reach this we have not found a building location
        return False

    def buildGasBuilding(self):
        """Build a gas building by selecting a townhall.
        """
        # cant build more gas buildings than townhalls
        if len(self.gas_buildings.ready) + self.already_pending(self.currentTask) <= len(self.townhalls) * 2:

            # prefer townhalls that are ready
            for townhall in self.townhalls.ready:
                return self.buildGasBuildingAtTownhall(townhall)
            # townhalls that are not ready
            for townhall in self.townhalls.not_ready:
                return self.buildGasBuildingAtTownhall(townhall)

    def buildBase(self):
        """Build base.

        Uses the pre computed expansion locations. Only works for simple map.
        """
        if bool(self.expansionLocations):
            location: Point2 = self.expansionLocations.pop(0)
            worker: Unit = self.workers.gathering.closest_to(location)
            if self.can_place(self.currentTask, location):
                worker.build(self.currentTask, location)
            else:
                raise Exception("could not build the command center where it was supposed to be built!")
        else:
            raise Exception("No more places to build command expansions!")

    # Worker Distribution
    # ----------------------------------------

    def myWorkerDistribution(self):
        """Redistributes workers to geysers and mineral fields.

        Redistributes:
            - idle workers
            - surplus on townhalls/geysers

        Taken from https://github.com/BurnySc2/python-sc2/blob/develop/examples/terran/mass_reaper.py and modified.
        """

        mineralTags = [x.tag for x in self.mineral_field]
        gas_buildingTags = [x.tag for x in self.gas_buildings]

        workerPool = Units([], self)
        workerPoolTags = set()

        # # Find all gas_buildings that have surplus or deficit
        deficit_gas_buildings = {}
        surplusgas_buildings = {}
        for g in self.gas_buildings.filter(lambda x: x.vespene_contents > 0):
            # Only loop over gas_buildings that have still gas in them
            deficit = g.ideal_harvesters - g.assigned_harvesters
            if deficit > 0:
                deficit_gas_buildings[g.tag] = {"unit": g, "deficit": deficit}
            elif deficit < 0:
                surplusWorkers = self.workers.closer_than(10, g).filter(
                    lambda w: w not in workerPoolTags
                    and len(w.orders) == 1
                    and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
                    and w.orders[0].target in gas_buildingTags
                )
                for i in range(-deficit):
                    if surplusWorkers.amount > 0:
                        w = surplusWorkers.pop()
                        workerPool.append(w)
                        workerPoolTags.add(w.tag)
                surplusgas_buildings[g.tag] = {"unit": g, "deficit": deficit}

        # # Find all townhalls that have surplus or deficit
        deficitTownhalls = {}
        surplusTownhalls = {}
        for th in self.townhalls:
            deficit = th.ideal_harvesters - th.assigned_harvesters
            if deficit > 0:
                deficitTownhalls[th.tag] = {"unit": th, "deficit": deficit}
            elif deficit < 0:
                surplusWorkers = self.workers.closer_than(10, th).filter(
                    lambda w: w.tag not in workerPoolTags
                    and len(w.orders) == 1
                    and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
                    and w.orders[0].target in mineralTags
                )
                # workerPool.extend(surplusWorkers)
                for i in range(-deficit):
                    if surplusWorkers.amount > 0:
                        w = surplusWorkers.pop()
                        workerPool.append(w)
                        workerPoolTags.add(w.tag)
                surplusTownhalls[th.tag] = {"unit": th, "deficit": deficit}

        # ---------------------------------------------

        # We now know which building has a deficit and which one has a surplus. If a building has a surplus
        # the workers are added to the worker pool. Whenever we have anything in the worker pool we want to
        # distribute those first.

        if bool(workerPool):

            # iterate deficit townhalls
            for townhallTag, info in deficitTownhalls.items():
                # get the minerals close to the current townhall
                mineralFields: Units = self.mineral_field.closer_than(10, info["unit"])
                # if there are any
                if mineralFields:
                    # get the deficit (missing worker to optimal performance)
                    deficit = info["deficit"]
                    # check if the worker pool does contain anything
                    workersLeft = bool(workerPool)
                    # if there is a deficit move one worker to the townhall from the worker pool
                    if deficit > 0 and workersLeft:
                        worker: Unit = workerPool.pop()
                        mineralField: Unit = mineralFields.closest_to(worker)
                        self.loggerBase.info("Moving one worker to harvest minerals at " + str(info["unit"]))
                        if len(worker.orders) == 1 and worker.orders[0].ability.id in [AbilityId.HARVEST_RETURN]:
                            worker.gather(mineralField, queue=True)
                        else:
                            worker.gather(mineralField)
            # iterate deficit gas buildings
            for gasTag, info in deficit_gas_buildings.items():
                # get the deficit (missing worker to optimal performance)
                deficit = info["deficit"]
                # check if the worker pool does contain anything
                workersLeft = bool(workerPool)
                # if there is a deficit move one worker to the townhall from the worker pool
                if deficit > 0 and workersLeft:
                    worker: Unit = workerPool.pop()
                    self.loggerBase.info("Moving one worker to harvest gas at " + str(info["unit"]))
                    if len(worker.orders) == 1 and worker.orders[0].ability.id in [AbilityId.HARVEST_RETURN]:
                        worker.gather(info["unit"], queue=True)
                    else:
                        worker.gather(info["unit"])
        else:
            # Whenever we do not have worker in the worker pool we want to move some workers to harvest gas but only if a certain ratio between
            # total vespene workers and total mineral workers is not exceeded.

            totalMineralWorkers = 0
            totalVespeneWorkers = 0

            for townhall in self.townhalls.ready:
                totalMineralWorkers += townhall.assigned_harvesters
            for gasBuilding in self.gas_buildings.ready:
                totalVespeneWorkers += gasBuilding.assigned_harvesters

            # only if less than 33% workers are on vespene
            if (totalVespeneWorkers / (totalMineralWorkers + totalVespeneWorkers)) < 0.34:
                for gasTag, info in deficit_gas_buildings.items():
                    worker: Unit = self.workers.gathering.closest_to(info["unit"].position)
                    self.loggerBase.info("Moving one worker to " + str(info["unit"]))
                    if len(worker.orders) == 1 and worker.orders[0].ability.id in [AbilityId.HARVEST_RETURN]:
                        worker.gather(info["unit"], queue=True)
                    else:
                        worker.gather(info["unit"])

        # redistribute idle workers
        if len(self.workers.idle) > 0:
            if self.townhalls:
                for worker in self.workers.idle:
                    townhall: Unit = self.townhalls.closest_to(worker)
                    mineralFields: Units = self.mineral_field.closer_than(10, townhall)
                    if mineralFields:
                        mineralField: Unit = mineralFields.closest_to(worker)
                        self.loggerBase.info("Moving one worker to harvest minerals at " + str(mineralField))
                        worker.gather(mineralField)

    # Attack
    # ----------------------------------------

    def attackMapCenterWithArmy(self):
        """ Has to be implemented by race specific bot.

        Gather bots army and then move to map center with attack move.
        """
        raise Exception("Must be implemented by race specific Bot!")

    # Run
    # ----------------------------------------

    # finishes preprocessing:
    #   - compute expansion locations
    def onStepBase(self, iteration: int):
        """ Neeeds to be called from race specific bot.

        Finishes the preprocessing that could not be done in on_start.
        """
        if not self.expansionLocationsComputed:
            # player one will compute for both
            if self.player == Player.PLAYER_ONE:
                if BuildListProcessBotBase.PLAYER_TWO_START_LOCATION != StartLocation.UNKNOWN:
                    self.fillExpansionLocations()
                    self.expansionLocationsComputed = True
                    self.loggerBase.info("Expansion locations computed at iteration: " + str(iteration))
                    self.expansionLocations = BuildListProcessBotBase.PLAYER_ONE_EXPANSION_LOCATIONS
            else:
                if BuildListProcessBotBase.PLAYER_TWO_EXPANSION_LOCATIONS:
                    self.loggerBase.info("Expansion locations available at iteration: " + str(iteration))
                    self.expansionLocations = BuildListProcessBotBase.PLAYER_TWO_EXPANSION_LOCATIONS
                    self.expansionLocationsComputed = True

        # it is important do to this b4 checking and advancing
        # because orders will be processed after one step is finished
        if (not self.attacking) and self.checkBuildListCompleted():
            self.loggerBase.info("All tasks in buildlist are finished and ready to fight!")
            if self.player == Player.PLAYER_ONE:
                BuildListProcessBotBase.PLAYER_ONE_READY_TO_ATTACK = True
            else:
                BuildListProcessBotBase.PLAYER_TWO_READY_TO_ATTACK = True
            self.attacking = True

        if BuildListProcessBotBase.PLAYER_TWO_READY_TO_ATTACK and BuildListProcessBotBase.PLAYER_ONE_READY_TO_ATTACK and not self.attackDone:
            self.loggerBase.info("Attacking the map center with all available units!")
            self.attackMapCenterWithArmy()
            self.attackDone = True

        if self.attackDone:
            if self.player == Player.PLAYER_ONE:
                BuildListProcessBotBase.PLAYER_ONE_ARMY_COUNT = self.army_count
            else:
                BuildListProcessBotBase.PLAYER_TWO_ARMY_COUNT = self.army_count

        if self.player == Player.PLAYER_ONE and (BuildListProcessBotBase.PLAYER_ONE_ARMY_COUNT == 0 or BuildListProcessBotBase.PLAYER_TWO_ARMY_COUNT == 0):
            if BuildListProcessBotBase.PLAYER_ONE_ARMY_COUNT == 0 and BuildListProcessBotBase.PLAYER_TWO_ARMY_COUNT == 0:
                self.loggerBase.info("Its a tie!")
            elif BuildListProcessBotBase.PLAYER_ONE_ARMY_COUNT == 0:
                self.loggerBase.info("Player two won the match!")
                BuildListProcessBotBase.WINNER = Winner.PLAYER_TWO
            else:
                self.loggerBase.info("Player one won the match!")
                BuildListProcessBotBase.WINNER = Winner.PLAYER_ONE

        if BuildListProcessBotBase.WINNER != Winner.UNKNOWN:
            if self.player == Player.PLAYER_ONE and BuildListProcessBotBase.WINNER == Winner.PLAYER_TWO:
                self.loggerBase.info("Player one should surrender now!")
                #self.client.leave()
                raise Exception("Player one lost the game and surrendered!")

            if self.player == Player.PLAYER_TWO and BuildListProcessBotBase.WINNER == Winner.PLAYER_ONE:
                self.loggerBase.info("Player two should surrender now!")
                #self.client.leave()
                raise Exception("Player two lost the game and surrendered!")

        self.checkAndAdvance()

        # distribute workers
        self.myWorkerDistribution()

        return self.expansionLocationsComputed and not self.done