import uuid
import faulthandler
from functools import cache
from html import escape as html_escape
import numpy as np
import scipy.stats

from psycopg.rows import dict_row as psycopg_rows_dict_row

from pydantic import BaseModel

faulthandler.enable()  # will catch segfaults and write to STDERR

from lib.db import DB

def rescale_energy_value(value, unit):
    # We only expect values to be mJ for energy!
    if unit != 'mJ' and not unit.startswith('ugCO2e/'):
        raise RuntimeError('Unexpected unit occured for energy rescaling: ', unit)

    unit_type = unit[1:]

    if unit.startswith('ugCO2e'): # bring also to mg
        value = value / (10**3)
        unit = f"m{unit_type}"

    if value > 1_000_000_000: return [value/(10**12), f"G{unit_type}"]
    if value > 1_000_000_000: return [value/(10**9), f"M{unit_type}"]
    if value > 1_000_000: return [value/(10**6), f"k{unit_type}"]
    if value > 1_000: return [value/(10**3), f"{unit_type}"]
    if value < 0.001: return [value*(10**3), f"n{unit_type}"]

    return [value, unit] # default, no change

def is_valid_uuid(val):
    try:
        uuid.UUID(str(val))
        return True
    except ValueError:
        return False

def html_escape_multi(item):
    """Replace special characters "'", "\"", "&", "<" and ">" to HTML-safe sequences."""
    if item is None:
        return None

    if isinstance(item, str):
        return html_escape(item)

    if isinstance(item, list):
        return [html_escape_multi(element) for element in item]

    if isinstance(item, dict):
        for key, value in item.items():
            if isinstance(value, str):
                item[key] = html_escape(value)
            elif isinstance(value, dict):
                item[key] = html_escape_multi(value)
            elif isinstance(value, list):
                item[key] = [
                    html_escape_multi(item)
                    if isinstance(item, dict)
                    else html_escape(item)
                    if isinstance(item, str)
                    else item
                    for item in value
                ]
        return item

    if isinstance(item, BaseModel):
        item_copy = item.model_copy(deep=True)
        # we ignore keys that begin with model_ because pydantic v2 renamed a lot of their fields from __fields to model_fields:
        # https://docs.pydantic.dev/dev-v2/migration/#changes-to-pydanticbasemodel
        # This could cause an error if we ever make a BaseModel that has keys that begin with model_
        keys = [key for key in dir(item_copy) if not key.startswith('_') and not key.startswith('model_') and not callable(getattr(item_copy, key))]
        for key in keys:
            setattr(item_copy, key, html_escape_multi(getattr(item_copy, key)))
        return item_copy

    return item

def get_machine_list():
    query = """
            SELECT id, description, available
            FROM machines
            ORDER BY description ASC
            """
    return DB().fetch_all(query)

def get_run_info(run_id):
    query = """
            SELECT
                id, name, uri, branch, commit_hash,
                (SELECT STRING_AGG(t.name, ', ' ) FROM unnest(runs.categories) as elements
                    LEFT JOIN categories as t on t.id = elements) as categories,
                filename, start_measurement, end_measurement,
                measurement_config, machine_specs, machine_id, usage_scenario,
                created_at, invalid_run, phases, logs
            FROM runs
            WHERE id = %s
            """
    params = (run_id,)
    return DB().fetch_one(query, params=params, row_factory=psycopg_rows_dict_row)

def get_timeline_query(uri,filename,machine_id, branch, metrics, phase, start_date=None, end_date=None, detail_name=None, limit_365=False, sorting='run'):

    if filename is None or filename.strip() == '':
        filename =  'usage_scenario.yml'

    params = [uri, filename, machine_id, f"%{phase}"]

    branch_condition = 'AND r.branch IS NULL'
    if branch is not None and branch.strip() != '':
        branch_condition = 'AND r.branch = %s'
        params.append(branch)

    metrics_condition = ''
    if metrics is None or metrics.strip() == '' or metrics.strip() == 'key':
        metrics_condition =  "AND (p.metric LIKE '%%_energy_%%' OR metric = 'software_carbon_intensity_global')"
    elif metrics.strip() != 'all':
        metrics_condition =  "AND p.metric = %s"
        params.append(metrics)

    start_date_condition = ''
    if start_date is not None and start_date.strip() != '':
        start_date_condition =  "AND DATE(r.created_at) >= TO_DATE(%s, 'YYYY-MM-DD')"
        params.append(start_date)

    end_date_condition = ''
    if end_date is not None and end_date.strip() != '':
        end_date_condition =  "AND DATE(r.created_at) <= TO_DATE(%s, 'YYYY-MM-DD')"
        params.append(end_date)

    detail_name_condition = ''
    if detail_name is not None and detail_name.strip() != '':
        detail_name_condition =  "AND p.detail_name = %s"
        params.append(detail_name)

    limit_365_condition = ''
    if limit_365:
        limit_365_condition = "AND r.created_at >= CURRENT_DATE - INTERVAL '365 days'"

    sorting_condition = 'r.commit_timestamp ASC, r.created_at ASC'
    if sorting is not None and sorting.strip() == 'run':
        sorting_condition = 'r.created_at ASC, r.commit_timestamp ASC'

    query = f"""
            SELECT
                r.id, r.name, r.created_at, p.metric, p.detail_name, p.phase,
                p.value, p.unit, r.commit_hash, r.commit_timestamp,
                row_number() OVER () AS row_num
            FROM runs as r
            LEFT JOIN phase_stats as p ON
                r.id = p.run_id
            WHERE
                r.uri = %s
                AND r.filename = %s
                AND r.end_measurement IS NOT NULL
                AND r.machine_id = %s
                AND p.phase LIKE %s
                {branch_condition}
                {metrics_condition}
                {start_date_condition}
                {end_date_condition}
                {detail_name_condition}
                {limit_365_condition}
                AND r.commit_timestamp IS NOT NULL
            ORDER BY
                p.metric ASC, p.detail_name ASC,
                p.phase ASC, {sorting_condition}

            """
    return (query, params)

def determine_comparison_case(ids):

    query = '''
            WITH uniques as (
                SELECT uri, filename, machine_id, commit_hash, COALESCE(branch, 'main / master') as branch FROM runs
                WHERE id = ANY(%s::uuid[])
                GROUP BY uri, filename, machine_id, commit_hash, branch
            )
            SELECT
                COUNT(DISTINCT uri ), COUNT(DISTINCT filename), COUNT(DISTINCT machine_id),
                COUNT(DISTINCT commit_hash ), COUNT(DISTINCT branch)
            FROM uniques
    '''

    data = DB().fetch_one(query, (ids, ))
    if data is None or data == [] or data[1] is None: # special check for data[1] as this is aggregate query which always returns result
        raise RuntimeError('Could not determine compare case')

    [repos, usage_scenarios, machine_ids, commit_hashes, branches] = data

    # If we have one or more measurement in a phase_stat it will currently just be averaged
    # however, when we allow comparing runs we will get same phase_stats but with different repo etc.
    # these cannot be just averaged. But they have to be split and then compared via t-test
    # For the moment I think it makes sense to restrict to two repositories. Comparing three is too much to handle I believe if we do not want to drill down to one specific metric

    # Currently we support six cases:
    # case = 'Repository' # Case D : RequirementsEngineering Case
    # case = 'Branch' # Case C_3 : SoftwareDeveloper Case
    # case = 'Usage Scenario' # Case C_2 : SoftwareDeveloper Case
    # case = 'Machine' # Case C_1 : DataCenter Case
    # case = 'Commit' # Case B: DevOps Case
    # case = 'Repeated Run' # Case A: Blue Angel
    # case = 'Multi-Commit' # Case D: Evolution of repo over time

    if repos == 2: # diff repos
        if usage_scenarios <= 2: # diff repo, diff usage scenarios. diff usage scenarios are NORMAL for now
            if machine_ids == 2: # diff repo, diff usage scenarios, diff machine_ids
                raise RuntimeError('Different repos & machines not supported')
            if machine_ids == 1: # diff repo, diff usage scenarios, same machine_ids
                if branches <= 2:
                    if commit_hashes <= 2: # diff repo, diff usage scenarios, same machine_ids,  same branches, diff/same commits_hashes
                        # for two repos we expect two different hashes, so this is actually a normal case
                        # even if they are identical we do not care, as the repos are different anyway
                        case = 'Repository' # Case D
                    else:
                        raise RuntimeError('Different repos & more than 2 different commits commits not supported')
                else:
                    raise RuntimeError('Different repos & more than 2 branches not supported')
            else:
                raise RuntimeError('Less than 1 or more than 2 Machines and different repos not supported.')
        else:
            raise RuntimeError('Only 2 or less usage scenarios for different repos not supported.')
    elif repos == 1: # same repos
        if usage_scenarios == 2: # same repo, diff usage scenarios
            if machine_ids == 2: # same repo, diff usage scenarios, diff machines
                raise RuntimeError('Different usage scenarios & machines not supported')
            if branches <= 1:
                if commit_hashes == 1: # same repo, diff usage scenarios, same machines, same branches, same commit hashes
                    case = 'Usage Scenario' # Case C_2
                else: # same repo, diff usage scenarios, same machines, same branches, diff commit hashes
                    raise RuntimeError('Different usage scenarios & commits not supported')
            else: # same repo, diff usage scenarios, same machines, diff branches
                raise RuntimeError('Different usage scenarios & branches not supported')
        elif usage_scenarios == 1: # same repo, same usage scenario
            if machine_ids == 2: # same repo, same usage scenarios, diff machines
                if branches <= 1:
                    if commit_hashes == 1: # same repo, same usage scenarios, diff machines, same branches, same commit hashes
                        case = 'Machine' # Case C_1
                    else: # same repo, same usage scenarios, diff machines, same branches, diff commit hashes
                        raise RuntimeError('Different machines & commits not supported')
                else: # same repo, same usage scenarios, diff machines, diff branches
                    raise RuntimeError('Different machines & branches not supported')

            elif machine_ids == 1: # same repo, same usage scenarios, same machines
                if branches <= 1:
                    if commit_hashes == 2: # same repo, same usage scenarios, same machines, diff commit hashes
                        case = 'Commit' # Case B
                    elif commit_hashes > 2: # same repo, same usage scenarios, same machines, many commit hashes
                        raise RuntimeError('Multiple commits comparison not supported. Please switch to Timeline view')
                    else: # same repo, same usage scenarios, same machines, same branches, same commit hashes
                        case = 'Repeated Run' # Case A
                else: # same repo, same usage scenarios, same machines, diff branch
                    # diff branches will have diff commits in most cases. so we allow 2, but no more
                    if commit_hashes <= 2:
                        case = 'Branch' # Case C_3
                    else:
                        raise RuntimeError('Different branches and more than 2 commits not supported')
            else:
                raise RuntimeError('Less than 1 or more than 2 Machines per repo not supported.')
        else:
            raise RuntimeError('Less than 1 or more than 2 Usage scenarios per repo not supported.')

    else:
        # The functionality I imagine here is, because comparing more than two repos is very complex with
        # multiple t-tests / ANOVA etc. and hard to grasp, only a focus on one metric shall be provided.
        raise RuntimeError('Less than 1 or more than 2 repos not supported for overview. Please apply metric filter.')

    return case

def get_phase_stats(ids):
    query = """
            SELECT
                a.phase, a.metric, a.detail_name, a.value, a.type, a.max_value, a.min_value, a.unit,
                b.uri, c.description, b.filename, b.commit_hash, COALESCE(b.branch, 'main / master') as branch
            FROM phase_stats as a
            LEFT JOIN runs as b on b.id = a.run_id
            LEFT JOIN machines as c on c.id = b.machine_id

            WHERE
                a.run_id = ANY(%s::uuid[])
            ORDER BY
                a.phase ASC,
                a.metric ASC,
                a.detail_name ASC,
                b.uri ASC,
                b.machine_id ASC,
                b.filename ASC,
                b.commit_hash ASC,
                branch ASC,
                b.created_at ASC
            """
    data = DB().fetch_all(query, (ids, ))
    if data is None or data == []:
        raise RuntimeError('Data is empty')
    return data

# Would be interesting to know if in an application server like gunicor @cache
# Will also work for subsequent requests ...?
'''  Object structure
    comparison_case: str
    statistics: dict
                                        // MEAN of the detailed-metric over all repos -> non-sense
                                        // MEAN of the detailed-metric over all usage_scenarios -> debateable
                                        // MEAN of the detailed-metric per machines  -> interesting
                                        // MEAN of the detailed-metric per commits -> debateable

                                        // Case A: No T-Test necessary, as only repetition. STDDEV only important
                                        // Case B: comparison as T-test betweeen every commit ???
                                        // => Technically possible, but we will most likely only have 1 commit.
                                        // => So maybe only compare last commit against all before?
                                        // => Yes, that sounds best! Still
                                        // Case C_1: comparison as T-test betweeen the two machines
                                        // => Impossible if we have only one sample
                                        // => Problematic if samples are < 20
                                        // Case C_2: comparison as T-test betweeen the two usage scenarios
                                        // => Impossible if we have only one sample
                                        // => Problematic if samples are < 20
                                        // Case C_3: comparison as T-test betweeen the two usage scenarios
                                        // => Impossible if we have only one sample
                                        // => Problematic if samples are < 20
                                        // Case D: comparison as T-test betweeen the two repos
                                        // => Impossible if we have only one sample
                                        // => Problematic if samples are < 20

                                        // although we can have 2 commits on 2 repos, we do not keep
                                        // track of the multiple commits here as key
                                        // currently the system is limited to compare only two runs until we have
                                        // figured out how big our StdDev is and how many runs we can run per day
                                        // at all (and how many repetitions are needed and possbile time-wise)
                                        repo/usage_scenarios/machine/commit/:
                                            mean: // mean per commit/repo etc.
                                            stddev:
                                            ci:
                                            p_value: // Only case A for last key vs compare to the rest. one-sided t-test
                                            is_significant: // Only case A .... one-sided t-test
                                            values: [] // 1 for stats.html. 1+ for cases
                                            max_values: []

    data: dict -> key: repo/usage_scenarios/machine/commit/branch
        run_1: dict
        run_2: dict
        ...
        run_x : dict  -> key: phase_name
            [BASELINE]: dict
            [INSTALLATION]: dict
            ....
            [PHASE]: dict -> key: metric_name
                -mean: - // will NOT be implemented. See explanation
                    // mean of the phase for all the metrics and their details
                    // this really only makes sense for all the energy values, and then only for selected ones ...
                    // actually only really helpful if we have multiple metrics that report the same value
                    // this shall NOT be supported
                // we reserve this level for phase-level data
                // atm there is only the data key
                core_energy_powermetrics_component: dict
                ...
                ane_energy_powermetrics_component: dict
                    clean_name: str
                    explanation: str
                    source: str
                    ....
                    mean: float
                        // mean of the metric over all details per per repo / usage_scenario etc.
                        // => Interesting for multiple docker containers
                        repo/usage_scenarios/machine/commit/: number
                        repo/usage_scenarios/machine/commit/: number
                        ...
                    stddev: float
                    p-value: float

                    data: dict -> key: detail_name
                        [COMPONENT]: dict
                        [SYSTEM]: dict
                        ...
                        [MACHINE]:
                                mean: float // mean of the metric for this detail for this phase
                                stddev: float
                                ci: float
                                max: float
                                values: list // the actual values
            ...
'''
def get_phase_stats_object(phase_stats, case):

    phase_stats_object = {
        'comparison_case': case,
        'comparison_details': set(),
        'data': {}
    }

    for phase_stat in phase_stats:
        [
            phase, metric_name, detail_name, value, metric_type, max_value, min_value, unit,
            repo, machine_description, filename, commit_hash, branch
        ] = phase_stat # unpack

        phase = phase.split('_', maxsplit=1)[1] # remove the 001_ prepended stuff again, which is only for ordering

        if case == 'Repository':
            key = repo # Case D : RequirementsEngineering Case
        elif case == 'Branch':
            key = branch # Case C_3 : SoftwareDeveloper Case
        elif case == 'Usage Scenario':
            key = filename # Case C_2 : SoftwareDeveloper Case
        elif case == 'Machine':
            key = machine_description # Case C_1 : DataCenter Case
        else:
            key = commit_hash # No comparison case / Case A: Blue Angel / Case B: DevOps Case

        if phase not in phase_stats_object['data']: phase_stats_object['data'][phase] = {}

        if metric_name not in phase_stats_object['data'][phase]:
            phase_stats_object['data'][phase][metric_name] = {
                'type': metric_type,
                'unit': unit,
                #'mean': None, # currently no use for that
                #'stddev': None,  # currently no use for that
                #'ci': None,  # currently no use for that
                #'p_value': None,  # currently no use for that
                #'is_significant': None,  # currently no use for that
                'data': {},
            }

        if detail_name not in phase_stats_object['data'][phase][metric_name]['data']:
            phase_stats_object['data'][phase][metric_name]['data'][detail_name] = {
                'name': detail_name,
                # 'mean': None, # mean for a detail over multiple machines / branches makes no sense
                # 'max': max_value, # max for a detail over multiple machines / branches makes no sense
                # 'min': min_value, # min for a detail over multiple machines / branches makes no sense
                # 'stddev': None, # stddev for a detail over multiple machines / branches makes no sense
                # 'ci': None, # since we only compare two keys atm this  could no be calculated.
                'p_value': None, # comparing the means of two machines, branches etc. Both cases must have multiple values for this to get populated
                'is_significant': None, # comparing the means of two machines, branches etc. Both cases must have multiple values for this to get populated
                'data': {},
            }

        detail_data = phase_stats_object['data'][phase][metric_name]['data'][detail_name]['data']
        if key not in detail_data:
            detail_data[key] = {
                'mean': None, # this is the mean over all repetitions of the detail_name for the key
                'max': max_value,
                'min': min_value,
                'max_mean': None,
                'min_mean': None,
                'stddev': None,
                'ci': None,
                'p_value': None, # only for the last key the list compare to the rest. one-sided t-test
                'is_significant': None, # only for the last key the list compare to the rest. one-sided t-test
                'values': [],
            }
            phase_stats_object['comparison_details'].add(key)

        detail_data[key]['values'].append(value)

        # since we do not save the min/max values we need to to the comparison here in every loop again
        # all other statistics are derived later in add_phase_stats_statistics()
        detail_data[key]['max'] = max((x for x in [max_value, detail_data[key]['max']] if x is not None), default=None)
        detail_data[key]['min'] = min((x for x in [min_value, detail_data[key]['min']] if x is not None), default=None)

    phase_stats_object['comparison_details'] = list(phase_stats_object['comparison_details'])

    return phase_stats_object

'''
    Here we need to traverse the object again and calculate all the averages we need
    This could have also been done while constructing the object through checking when a change
    in phase / detail_name etc. occurs., however this is more efficient
'''
def add_phase_stats_statistics(phase_stats_object):

    for _, phase_data in phase_stats_object['data'].items():
        for _, metric in phase_data.items():
            for _, detail in metric['data'].items():
                for _, key_obj in detail['data'].items():

                    # if a detail has multiple values we calculate a std.dev and the one-sided t-test for the last value

                    key_obj['mean'] = key_obj['values'][0] # default. might be overridden
                    key_obj['max_mean'] = key_obj['values'][0] # default. might be overridden
                    key_obj['min_mean'] = key_obj['values'][0] # default. might be overridden

                    if len(key_obj['values']) > 1:
                        t_stat = get_t_stat(len(key_obj['values']))

                        # JSON does not recognize the numpy data types. Sometimes int64 is returned
                        key_obj['mean'] = float(np.mean(key_obj['values']))
                        key_obj['stddev'] = float(np.std(key_obj['values']))
                        key_obj['max_mean'] = np.max(key_obj['values']) # overwrite with max of list
                        key_obj['min_mean'] = np.min(key_obj['values']) # overwrite with min of list
                        key_obj['ci'] = key_obj['stddev']*t_stat

                        if len(key_obj['values']) > 2:
                            data_c = key_obj['values'].copy()
                            pop_mean = data_c.pop()
                            _, p_value = scipy.stats.ttest_1samp(data_c, pop_mean)
                            if not np.isnan(p_value):
                                key_obj['p_value'] = p_value
                                if key_obj['p_value'] > 0.05:
                                    key_obj['is_significant'] = False
                                else:
                                    key_obj['is_significant'] = True

    ## builds stats between the keys
    if len(phase_stats_object['comparison_details']) == 2:
        # since we currently allow only two comparisons we hardcode this here
        # this is then needed to rebuild if we would allow more
        key1 = phase_stats_object['comparison_details'][0]
        key2 = phase_stats_object['comparison_details'][1]

        # we need to traverse only one branch of the tree like structure, as we only need to compare matching metrics
        for _, phase_data in phase_stats_object['data'].items():
            for _, metric in phase_data.items():
                for _, detail in metric['data'].items():
                    if key1 not in detail['data'] or key2 not in detail['data']:
                        continue

                    # Welch-Test because we cannot assume equal variances
                    _, p_value = scipy.stats.ttest_ind(detail['data'][key1]['values'], detail['data'][key2]['values'], equal_var=False)

                    if not np.isnan(p_value):
                        detail['p_value'] = p_value
                        if detail['p_value'] > 0.05:
                            detail['is_significant'] = False
                        else:
                            detail['is_significant'] = True

    return phase_stats_object

@cache
def get_t_stat(length):
    #alpha = .05
    if length <= 1: return None
    dof = length-1
    t_crit = np.abs(scipy.stats.t.ppf((.05)/2,dof)) # for two sided!
    return t_crit/np.sqrt(length)


# regular methods in a class automatically take the instance as an argument
# class methods automatically take the class as the first argument
# to convert regular methods to class methods, add decorator @classmethod
# that decorator alters the functionality of the method so that we recieve class as our first argument
# the common convention for class variables is cls, like 'self' is for instances

# class methods provide multiple ways or alternative constructors to create an object

# Let's look at static methods
# static methods, unlike class methods and regular methods don't pass anything automatically
# they behave like regular functions but we include them in the class because
# they have some logical connection to the class
# a method is a static method if you don't access the instance or the class anywhere within the function
# lets create a function that takes in a date and returns if it is a workday or not
# to create a static method, add decorator @staticmethod

class Employee:

    no_of_emps = 0
    raise_percent = 1.04

    def __init__(self, firstname="", lastname="", pay=0):
        self.firstname = firstname
        self.lastname = lastname
        self.pay = pay
        self.email = self.firstname + "." + self.lastname + "@gmail.com"
        Employee.no_of_emps += 1

    def fullname(self):
        return "{} {}".format(self.firstname, self.lastname)

    def apply_raise(self):
        self.pay = self.pay * self.raise_percent

    @classmethod
    def set_raise_amt(cls, amount):
        cls.raise_percent = amount

    @classmethod
    def from_string(cls, emp_str):
        first, last, pay = emp_str.split("-")
        return cls(first, last, pay)

    # python has these weekday method
    # it returns 0 for monday and 6 for sunday and rest in between
    @staticmethod
    def is_workday(day):
        if day.weekday() == 5 or day.weekday() == 6:
            return False
        else:
            return True

emp_1 = Employee(firstname="Neeraj", lastname="Krishna", pay=170000)
emp_2 = Employee(firstname="John", lastname="Doe", pay=200000)

print(Employee.raise_percent)
print(emp_1.raise_percent)
print(emp_2.raise_percent)
# all of them output 1.04

# let's change the raise amount
Employee.set_raise_amt(1.05)  # this is the same as saying Employee.raise_amt = 1.05

print(Employee.raise_percent)
print(emp_1.raise_percent)
print(emp_2.raise_percent)
# all of them output 1.05

# let's say a person is entering their data in this format
emp_str_1 = 'Mark-Zuckerberg-100000000'
# let's create this instance from the employee class
first, last, pay = emp_str_1.split('-')
emp_3 = Employee(first, last, pay)
print(emp_3.firstname)  # it works!

# if this is a common case, we dont want them doing everything manually
# so, lets create an alternative constructor that allows them to pass the string
# and we can create an employee from the string

# let's check our new class method
emp_str_2 = "Bill-Gates-2000000"
emp_4 = Employee.from_string(emp_str_2)
print(emp_4.email)  # it works!

import datetime
my_date = datetime.date(2018, 8, 12)

# let's check our static method
print(Employee.is_workday(my_date)) # it fookin works!

Object Oriented Programming/3. ClassMethods and StaticMethods.py


"""
Webglue
-------
"""

import logging
from lxk_printer_device.ews import ews

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)

def get_setting(ip_address, setting):
    """
    Get Setting value using the EWS webglue.
    'Setting' value can be found when inspecting the EWS html
    and get the id of the setting.

    :Parameters:

        1. ip_address, ``<class 'str'>``
        2. setting, ``<class 'str'>`` (i.e. ScreenTimeout, SetHostname and TcpIpDomainName.)

    :Returns: ``<class 'int'>`` or ``<class 'str'>``
    """
    LOGGER.debug(ip_address)
    LOGGER.debug(setting)

    data = ews.get_content(
        ip_address,
        "/webglue/content?c={}".format(setting),
        attempts=3
    )
    retval = int(data['nodes']['settings'][str(data['nodes']['nodes'] \
    [setting]['setting'])]['val'])
    LOGGER.debug(retval)
    return retval


import sys
from PyQt5.QtWidgets import (
    QMainWindow, QApplication,
    QLabel, QCheckBox, QComboBox, QLineEdit,QLineEdit,
    QSpinBox, QDoubleSpinBox, QSlider,QVBoxLayout,QWidget
)
from PyQt5.QtCore import Qt

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")
        layout = QVBoxLayout()

        label = QLabel("Hello")
        font = label.font()
        font.setPointSize(30)
        label.setFont(font)
        label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

        checkBox = QCheckBox()
        checkBox.setText("check")
        checkBox.setCheckState(Qt.Checked)
        checkBox.stateChanged.connect(self.show_state)

        comboBox = QComboBox()
        comboBox.addItems(["One","Two","Three"])
        comboBox.currentIndexChanged.connect( self.index_changed )
        comboBox.currentTextChanged.connect( self.text_changed )

        layout.addWidget(label)
        layout.addWidget(checkBox)
        layout.addWidget(comboBox)

        widget = QWidget()
        widget.setLayout(layout)

        self.setCentralWidget(widget)

    def show_state(self, s):
        print(s == Qt.Checked)
        print(s)

    def index_changed(self, i): # i is an int
        print(i)

    def text_changed(self, s): # s is a str
        print(s)

app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()


import matplotlib.pyplot as plt
import scipy
import VoigtFitting.VoigtFitting as VoigtFitting
import time
#import AbsorptionLine, ResolutionConvolution,FitManager

Fit = VoigtFitting.FitManager()

Ion = Fit.CreateIonGroup('FeII',['FeII_1608','FeII_1611'])
Ion.AddComponent('A',3.0e15, 1500000,4.9887)
Ion.AddComponent('B',1.0e14, 500000,4.9887)

#print Fit.CalcLnLikelihood()

Wavelength = scipy.arange(9000.0,10000.0,0.005)
Flux = Fit.ComputeFlux(Wavelength)

plt.plot(Wavelength,Flux,'-',color='black',label=r'$\delta$-function instrument profile',lw=2)

#Gaussian instrument profile:
Flux_convolve =  Fit.Convolve(1.25/2.35482,Wavelength)

plt.plot(Wavelength,Flux_convolve,'-',color='blue',label='Gaussian intrument profile',lw=2)

#Arbitrary instrument profile:
#Flux_convolve1 =  Fit.Convolve('Data/GaussInstrumentProfile.txt',Wavelength)
#plt.plot(Wavelength,Flux_convolve1,':',color='blue',label='Convolved with gaussian',lw=3)

#Arbitrary instrument profile:
Flux_convolve1 =  Fit.Convolve('Data/Prof.txt',Wavelength)
plt.plot(Wavelength,Flux_convolve1,'--',color='red',label='Measured instrument profile',lw=2)

#Arbitrary instrument profile:
#Flux_convolve1 =  Fit.Convolve('Data/Prof1.txt',Wavelength)
#plt.plot(Wavelength,Flux_convolve1,'--',color='green',label='Measured instrument profile',lw=2)

plt.xlim((9627,9657))
plt.ylim((-0.1,1.2))
plt.legend(loc=4)
plt.grid()
plt.xlabel('Wavelength [Angstrom]',fontsize=16)
plt.ylabel('Normalized flux',fontsize=16)
plt.show()


import json
import os
import time

import requests
from dotenv import load_dotenv

from API_functions import get_proxies_by_api, parse_data

load_dotenv()

proxy_login = os.getenv('PROXY_LOGIN')
proxy_pass = os.getenv('PROXY_PASSWORD')
proxy_ip = os.getenv('PROXY_HOST')
proxy_url = os.getenv('CHECK_URL')
ipinfo_token = os.getenv('IPINFO_TOKEN')

def show_in_bot(func):
    """Декоратор для вывода результата работы функции в бот"""
    def wrapper(message, bot):
        text = str(message.text)
        msg = func(text)
        bot.send_message(message.chat.id, msg)
    return wrapper

@show_in_bot
def check_one_port(port):
    """Проверка одного порта"""
    return check_req(port)

@show_in_bot
def check_all(text):
    """Проверка всех мобильных прокси на аккаунте"""
    if text == '+':
        proxy_list = parse_data(get_proxies_by_api())
        msg = ''
        valid = 0
        for i in range(len(proxy_list)):
            port = proxy_list[i][2]
            name = proxy_list[i][0]
            msg_one = check_req(port, name)
            if '\u2705' in msg_one:
                valid += 1
            msg += msg_one
        msg += f'Валидных прокси: {valid} из {len(proxy_list)}'
        return msg
    return 'Check canceled'

@show_in_bot
def check_country(text):
    """Проверка прокси определенной страны"""
    proxy_list = parse_data(get_proxies_by_api())
    msg = ''
    for i in range(len(proxy_list)):
        name = proxy_list[i][0]
        country = name.split(',')[0]
        if country == str(text):
            port = proxy_list[i][2]
            msg_one = check_req(port, name)
            msg += msg_one
    if msg == '':
        return 'Wrong country'
    return msg

def check_req(port, name=None):
    """Тестовый запрос через прокси и анализ ответа"""
    try:
        r = requests.get(
            proxy_url,
            proxies={
                'http': ('http://'
                         f'{proxy_login}:{proxy_pass}@{proxy_ip}:{port}'),
                'https': ('https://'
                          f'{proxy_login}:{proxy_pass}@{proxy_ip}:{port}')
            },
            timeout=(5, 30),
        )
        if r.status_code == 200 and name is None:
            return f'\u2705 Порт {port}\n'
        elif r.status_code == 200 and name is not None:
            return f'\u2705 {name}\n'
        elif r.status_code != 200 and name is None:
            return f'\u274c Порт {port}\n'
        elif r.status_code != 200 and name is not None:
            return f'\u274c {name}\n'
        return 'Something went wrong'
    except (requests.exceptions.ProxyError, requests.exceptions.ReadTimeout):
        if name is None:
            return f'\u2753 Порт {port}\n'
        return f'\u2753 {name}\n'

def check_ipinfo(port):
    """Получает IP-адрес с ipinfo.io"""
    try:
        r = requests.get(
            f'https://ipinfo.io/json?token={ipinfo_token}',
            proxies={
                'http': ('http://'
                         f'{proxy_login}:{proxy_pass}@{proxy_ip}:{port}'),
                'https': ('https://'
                          f'{proxy_login}:{proxy_pass}@{proxy_ip}:{port}')
            }
        )
        resp = json.loads(r.text)
        return resp['ip']
    except requests.exceptions.ProxyError:
        return 'Bad'

@show_in_bot
def check_rotation(port):
    """Проверка наличия ротации IP-адреса"""
    ip1 = check_ipinfo(port)
    time.sleep(300)
    ip2 = check_ipinfo(port)

    if ip1 == 'Bad' or ip2 == 'Bad':
        return '\u274c Bad Requests'
    if ip1 != ip2:
        return '\u2705 Rotation works'
    else:
        return '\u274c No rotation'


from email import message
from django.shortcuts import redirect, render,HttpResponseRedirect
from . models import*
from django.urls import reverse
from django.db import IntegrityError
from django.utils.timezone import datetime #important if using timezones

def index(request):
    today = datetime.today()
    all_lists = ToDoList.objects.all()
    all_items = ToDoListItems.objects.all()
    return render(request,'todo.html',{'all_lists' : all_lists,'all_items':all_items,'today':today})

def addtodoitem(request):
    x = request.POST['content']
    if x != '':
        content_item = ToDoList(title = x)
        try:
            content_item.save()
        except IntegrityError as err:
           return HttpResponseRedirect('/')
    return HttpResponseRedirect('/')

def deleteitem(request,id):
    id = ToDoList.objects.get(id=id)
    id.delete()
    return HttpResponseRedirect(reverse('index'))

def todoitemlist(request,id):
    today = datetime.today()
    todolist = ToDoList.objects.get(id=id)
    all = ToDoListItems.objects.filter(todolist=todolist).order_by('-id')
    return render(request,'todo_item_list.html',{'todolist' : todolist,'all':all,'today':today})

def deletelistitem(request,id,t_id):
    id = ToDoListItems.objects.get(id=id)
    id.delete()
    todolist = ToDoList.objects.get(id=t_id)
    all = ToDoListItems.objects.filter(todolist=todolist).order_by('-id')
    return render(request,'todo_item_list.html',{'todolist' : todolist,'all':all})

def addlistitem(request,id):
    todolist = ToDoList.objects.get(id=id)
    title = request.POST['title']
    desc = request.POST['desc']
    date = request.POST['date']
    plan = ToDoListItems(content=title,description=desc,todolist=todolist,due_date=date)
    all = ToDoListItems.objects.filter(todolist=todolist).order_by('-id')
    try:
        plan.save()
    except IntegrityError as err:
        return HttpResponseRedirect('/')
    return render(request,'todo_item_list.html',{'todolist' : todolist,'all':all})


from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget

from gauss_noise_reduction import *

# Kepek beolvasasa es tombbe helyezese
Lenna = cv2.imread('img/original_images/Lenna_(test_image).png', cv2.IMREAD_GRAYSCALE)
Chemical_plant = cv2.imread('img/original_images/chemical_plant.tiff', cv2.IMREAD_GRAYSCALE)
Clock = cv2.imread('img/original_images/clock.tiff', cv2.IMREAD_GRAYSCALE)
Wall = cv2.imread('img/original_images/brick_wall.tiff', cv2.IMREAD_GRAYSCALE)
images = {"Lenna": Lenna,
          "Chemical plant": Chemical_plant,
          "Clock": Clock,
          "Wall": Wall}
kernels = {"3x3": 1,
           "5x5 (time consuming)": 3}
fullkernels = {"3x3": 3,
               "5x5 (time consuming)": 5}
range_sigmas = {"10": 10,
                "20": 20,
                "40": 40,
                "60": 60,
                "80": 80,
                "100": 100}

def window():
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    win = QWidget()
    win.setGeometry(200, 200, 300, 300)
    win.setWindowTitle("Menu")
    layout = QVBoxLayout()
    # Algoritmusvalaszto
    labelAlgorithm = QtWidgets.QLabel()
    labelAlgorithm.setText("Algorithm: ")
    layout.addWidget(labelAlgorithm)
    comboBoxAlgorithm = QtWidgets.QComboBox(win)
    comboBoxAlgorithm.addItems(
        ["Kuwahara", "Sigma", "Gradient inverse weighted method", "Gradient inverse weighted method upgrade",
         "Bilateral",
         "Bilateral with integral histogram"])
    layout.addWidget(comboBoxAlgorithm)
    # Sigma a zajositashoz
    labelSigma = QtWidgets.QLabel(win)
    labelSigma.setText("Sigma value: ")
    layout.addWidget(labelSigma)
    comboBoxSigma = QtWidgets.QComboBox(win)
    comboBoxSigma.addItems(["20", "40", "80"])
    layout.addWidget(comboBoxSigma)
    # Kernelmeret
    labelKernelSize = QtWidgets.QLabel(win)
    labelKernelSize.setText("Kernel size: ")
    layout.addWidget(labelKernelSize)
    comboBoxKernel = QtWidgets.QComboBox(win)
    comboBoxKernel.addItems(kernels)
    layout.addWidget(comboBoxKernel)
    # Input kep
    labelInputImage = QtWidgets.QLabel(win)
    labelInputImage.setText("Input image: ")
    layout.addWidget(labelInputImage)
    comboBoxInput = QtWidgets.QComboBox(win)
    comboBoxInput.addItems(images)
    layout.addWidget(comboBoxInput)
    # Range sigma
    labelRangeSigma = QtWidgets.QLabel(win)
    labelRangeSigma.setText("Range sigma: ")
    layout.addWidget(labelRangeSigma)
    labelRangeSigma.hide()
    sliderRangeSigma = QtWidgets.QSlider(Qt.Horizontal)
    sliderRangeSigma.setMinimum(1)
    sliderRangeSigma.setMaximum(100)
    sliderRangeSigma.setValue(20)
    layout.addWidget(sliderRangeSigma)
    sliderRangeSigma.hide()
    # Spatial sigma
    labelSpatialSigma = QtWidgets.QLabel(win)
    labelSpatialSigma.setText("Spatial sigma: ")
    layout.addWidget(labelSpatialSigma)
    labelSpatialSigma.hide()
    sliderSpaceSigma = QtWidgets.QSlider(Qt.Horizontal)
    sliderSpaceSigma.setMinimum(1)
    sliderSpaceSigma.setMaximum(100)
    sliderSpaceSigma.setValue(60)
    layout.addWidget(sliderSpaceSigma)
    sliderSpaceSigma.hide()
    # GIW_new tobbszori alkalmazas
    labelRepeatTimes = QtWidgets.QLabel(win)
    labelRepeatTimes.setText("Repeat times: ")
    labelRepeatTimes.hide()
    layout.addWidget(labelRepeatTimes)
    comboBoxRepeatTimes = QtWidgets.QComboBox(win)
    comboBoxRepeatTimes.addItems(["1", "2", "3"])
    layout.addWidget(comboBoxRepeatTimes)
    comboBoxRepeatTimes.hide()
    # Futtatas
    btnRun = QtWidgets.QPushButton(win)
    btnRun.setText("Run algorithm")
    btnRun.setCheckable(True)
    layout.addWidget(btnRun)
    comboBoxAlgorithm.currentIndexChanged.connect(lambda: update_window())

    def update_window():
        comboBoxKernel.clear()
        labelKernelSize.show()
        comboBoxKernel.show()
        labelRangeSigma.hide()
        sliderRangeSigma.hide()
        labelSpatialSigma.hide()
        sliderSpaceSigma.hide()
        labelRepeatTimes.hide()
        comboBoxRepeatTimes.hide()
        if comboBoxAlgorithm.currentText() == "Kuwahara":
            comboBoxKernel.clear()
            comboBoxKernel.addItem("5x5 (time consuming)")
        elif comboBoxAlgorithm.currentText() == "Gradient inverse weighted method upgrade":
            comboBoxKernel.addItems(kernels)
            labelRepeatTimes.show()
            comboBoxRepeatTimes.show()
        elif comboBoxAlgorithm.currentText() == "Bilateral":
            comboBoxKernel.addItems(kernels)
            labelRangeSigma.show()
            sliderRangeSigma.show()
            labelSpatialSigma.show()
            sliderSpaceSigma.show()
        else:
            comboBoxKernel.addItems(kernels)

    win.setLayout(layout)
    win.show()

    btnRun.clicked.connect(
        lambda: call_algorithm(comboBoxAlgorithm.currentText(), comboBoxSigma.currentText(),
                               comboBoxInput.currentText(), comboBoxKernel.currentText(),
                               sliderRangeSigma.value(), sliderSpaceSigma.value(), comboBoxRepeatTimes.currentText()))

    sys.exit(app.exec_())

def call_algorithm(algorithm, sigmaparam, image, kernelsize, range_sigmaparam, space_sigmaparam, giw_repeat_times):
    global final
    print("\n")
    print(algorithm)
    print("Deviation of the additive noise: ", sigmaparam)
    print("\n")
    sigma = int(sigmaparam)
    print('Applying the filter...')
    if algorithm == "Kuwahara":
        final = kuwahara(images[image], sigma)
    elif algorithm == "Gradient inverse weighted method":
        final = gradient_inverse_weighted_method(images[image], sigma, kernels[kernelsize])
    elif algorithm == "Sigma":
        final = sigmaAlgorithm(images[image], sigma, kernels[kernelsize])
    elif algorithm == "Bilateral":
        final = bilateral(images[image], sigma, fullkernels[kernelsize], range_sigmaparam,
                          space_sigmaparam)
    elif algorithm == "Gradient inverse weighted method upgrade":
        if giw_repeat_times == "1":
            final = gradient_inverse_weighted_method_upgrade(images[image], sigma, kernels[kernelsize], False)
        elif giw_repeat_times == "2":
            first = gradient_inverse_weighted_method_upgrade(images[image], sigma, kernels[kernelsize], False)
            final = gradient_inverse_weighted_method_upgrade(first, sigma, kernels[kernelsize],
                                                             True)
        else:
            first = gradient_inverse_weighted_method_upgrade(images[image], sigma, kernels[kernelsize], False)
            second = gradient_inverse_weighted_method_upgrade(first, sigma, kernels[kernelsize], True)
            final = gradient_inverse_weighted_method_upgrade(second, sigma, kernels[kernelsize], True)
    elif algorithm == "Bilateral with integral histogram":
        final = bilateral_with_integral_histogram(images[image], sigma, fullkernels[kernelsize])
    print('Filter applied!\n')
    psnr_function(images[image], final)
    ssim_function(images[image], final)
    # cv2.imwrite('img/denoised images/%(algorithm)s_%(noise)s_%(kernel)s.jpg' % {"algorithm": algorithm, "noise": sigma,
    #                                                                             "kernel": kernelsize}, final)
    cv2.imshow('Image after denoising', final)

window()
cv2.waitKey(0)
cv2.destroyAllWindows()


import numpy as np
import cv2 as cv

def nothing(x):
    pass

def defineColor():
    global r
    global g
    global b
    img = np.zeros((300, 512, 3), np.uint8)
    cv.namedWindow('image')
    # create trackbars for color change
    cv.createTrackbar('R', 'image', 0, 255, nothing)
    cv.createTrackbar('G', 'image', 0, 255, nothing)
    cv.createTrackbar('B', 'image', 0, 255, nothing)
    # create switch for ON/OFF functionality
    next_step = '0 : OFF \n1 : O'
    cv.createTrackbar(next_step, 'image', 0, 1, nothing)
    while 1:
        cv.imshow('image', img)
        k = cv.waitKey(1) & 0xFF
        if k == 27:
            cv.destroyAllWindows()
            return None

        # get current positions of four trackbars
        r = cv.getTrackbarPos('R', 'image')
        g = cv.getTrackbarPos('G', 'image')
        b = cv.getTrackbarPos('B', 'image')
        turn_on = cv.getTrackbarPos(next_step, 'image')
        if turn_on == 1:
            return [r, g, b]

        img[:] = [b, g, r]

lower_color = np.array(defineColor())
upper_color = np.array(defineColor())

cap = cv.VideoCapture(0)
while 1:
    # Take each frame
    _, frame = cap.read()
    # Convert BGR to HSV
    hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
    # define range of blue color in HSV
    lower_blue = np.array([110, 50, 50])
    upper_blue = np.array([130, 255, 255])
    # Threshold the HSV image to get only blue colors
    mask = cv.inRange(hsv, lower_color, upper_color)
    # Bitwise-AND mask and original image
    res = cv.bitwise_and(frame, frame, mask=mask)
    cv.imshow('frame', frame)
    cv.imshow('mask', mask)
    cv.imshow('res', res)
    k = cv.waitKey(5) & 0xFF
    if k == 27:
        break
cv.destroyAllWindows()


import logging
from collections import namedtuple
import csv

from utilities.to_numeric import to_numeric

def generic_csv_reader(csv_file, encoding='ISO-8859-1'):
    """Reads and parses the csv file into a more usable format"""
    print(f'Reading file {csv_file}')
    logging.debug(f'Reading file: {csv_file}')
    TempShape = namedtuple("Shape", ("bbox", "parts", "points", "shapeType", "shapeTypeName"))
    file = []
    with open(csv_file, encoding=encoding) as csvfile:
        reader = csv.DictReader(csvfile)
        for entry in reader:
            entry = {k.lower(): to_numeric(v) for k, v in entry.items()}
            if 'x' in entry:
                entry["shape"] = TempShape([entry['x'], entry['y'], entry['x'], entry['y']],
                                           [],
                                           [(entry['x'], entry['y'])],
                                           1,
                                           "POINT")
                entry['coordinate'] = \
                    entry['location'] = \
                    entry['point'] = \
                    (entry['x'], entry['y'])
            elif 'lon' in entry:
                entry["shape"] = TempShape([entry['lon'], entry['lat'], entry['lon'], entry['lat']],
                                           [],
                                           [(entry['lon'], entry['lat'])],
                                           1,
                                           "POINT")
                entry['coordinate'] = \
                    entry['location'] = \
                    entry['point'] = \
                    (entry['lon'], entry['lat'])
            file.append(entry)
        return file


# Import Modules
import pandas as pd
from datetime import date
import csv
from csv import DictWriter
from csv import DictReader
import time
import json
import urllib.request as ur
import urllib.parse as prs
from datetime import datetime, timedelta

###---Global Variables----
# Sets the name of the CSV file we'll be using to store data
file = "ontario_covid_data.csv"

# Gets today's date
today = date.today()

# Determines how many days back to check for data in the CSV file
DATESBACK = 10

# Creating the datasourceinfo variable which stores information about how to query different datasets
datasources = {
    "CaseData": {
        "id": "ed270bb8-340b-41f9-a7c6-e8ef587e6d11",
        "datename": "Reported Date",
        "fields": [
            "Total Cases",
            "Number of patients hospitalized with COVID-19",
            "Number of patients in ICU due to COVID-19",
        ],
    },
    "VaccineData": {
        "id": "8a89caa9-511c-4568-af89-7f2174b4378c",
        "datename": "report_date",
        "fields": [
            "total_doses_administered",
            "total_individuals_at_least_one",
            "total_individuals_fully_vaccinated",
            "total_individuals_3doses",
        ],
    },
}

###---Functions---
# get list of fields from CSV file
def OpenCSV():
    global localData
    global field_names
    with open(file) as csv_file:
        csv_reader = csv.DictReader(csv_file)
        dict_from_csv = dict(list(csv_reader)[0])
        field_names = list(dict_from_csv.keys())
    localData = pd.read_csv(file)
    ## Set the index of the CSV file to 'date' so we can reference rows by dates
    localData.set_index("date", inplace=True)
    localData.head()

# Function to pass in a new row to the CSV file
def addRow(elements):
    # Open file in append mode
    with open(file, "a+", newline="") as write_obj:
        # Create a writer object from csv module
        dict_writer = DictWriter(write_obj, fieldnames=field_names)
        # Add dictionary as row in the csv
        dict_writer.writerow(elements)

# Function to add given data to a given row and column
def addValue(row, column, value):
    localData.loc[row, column] = value
    localData.to_csv(file)

# Function to add a given date as a new row to the CSV
def dateCheck(day):
    try:
        localData.loc[day]
        return True
    except KeyError:
        return False

# Function to check blank fields for a given date
def blankchecker(date, dataset):
    fieldquery = []
    for i in datasources[dataset]["fields"]:
        try:
            checker = 1 + int(localData.loc[date, i])
        except ValueError:
            fieldquery.append(i)
    if fieldquery:
        return fieldquery

# Function to build a query and get data
def querier(dataset, qfields, qdate):
    urlstart = "https://data.ontario.ca/api/3/action/datastore_search?"
    resourceid = "resource_id=" + datasources[dataset]["id"]
    fieldQuery = "fields=%s" % (",".join(qfields))
    reqdate = 'filters={"' + datasources[dataset]["datename"] + '":["' + qdate + '"]}'
    queryurl = urlstart + resourceid + "&" + fieldQuery + "&" + reqdate
    queryurl = queryurl.replace(" ", "%20")
    fileobj = ur.urlopen(queryurl)
    return json.loads(fileobj.read())

# Stub of a function to get blank data. Need to run this in a loop for every blank cell in each dataset for each day...
def blankfiller(date, dataset):
    qfields = blankchecker(date, dataset)
    if qfields is not None:
        reqdata = querier(dataset, qfields, date)
        if reqdata["result"]["total"] > 0:
            for i in qfields:
                addValue(date, i, reqdata["result"]["records"][0][i])
                OpenCSV()

if __name__ == "__main__":
    import argparse
    import os

    parser = argparse.ArgumentParser()
    parser.add_argument("--csvdir", help="Directory of csv file")
    args = parser.parse_args()
    if args.csvdir:
        file = os.path.join(args.csvdir, file)
    # Open the CSV
    OpenCSV()

    # Add a blank line to end of CSV if one doesn't exist. (This is needed to add any new rows to the table.)
    with open(file, "r") as f:
        lines = f.readlines()
        if "\n" in lines:
            print("new line char found")

    # Check if there are any missing rows and create if not
    checkdate = today
    for _ in range(DATESBACK):
        OpenCSV()
        if dateCheck(str(checkdate)) is False:
            addRow({"date": checkdate})
        checkdate = checkdate - timedelta(days=1)

    # Populate missing values
    checkdate = today
    for _ in range(DATESBACK):
        for i in datasources:
            OpenCSV()
            blankfiller(str(checkdate), i)
        checkdate = checkdate - timedelta(days=1)

    OpenCSV()

    localData.sort_values(["date"], axis=0, ascending=[False], inplace=True)

    localData.to_csv(file)


#!/usr/bin/env python

import re
import sys
import argparse
import itertools
from collections import OrderedDict
from clldutils.path import Path
import numpy as np

import csvw
import pycldf

import pyclts
from segments import Tokenizer, Profile

try:
    bipa = pyclts.CLTS().bipa
except AttributeError:
    # You might have an old CLTS
    bipa = pyclts.TranscriptionSystem("bipa")

sounds = list(bipa.sounds)
sounds.extend([
    "ᵐb", "ᶮd͡ʒ", "ⁿd͡ʒ", "ᵑg", "ᵑk", "ᵐp", "ⁿs", "ᶮt͡ʃ", "ⁿt͡s", "ᵐb̥", "ⁿd̥", "ᵑgʷ", "ⁿd͡zʲ",
    "b͡β", "d͡z", "ɖ͡ʐ", "d͡ʒ", "d͡ɮ", "t͡s", "t͡ɕ", "t͡ʃ", "t͡ç", "b͡v", "c͡ç",
    "k͡p", "g͡b", "ᵑg͡b",
    "ᵐbː", "ⁿdː", "ᵑgː", "ɓː", "ɗː",
    "tːʰ", "c͡çː", "kːʰ", "kːʷ", "c͡çːʰ", "kːʰʲ", "hʲ", "ᵐbʲ",
    "lˀ", "mˀ", "nˀ", "sˀ", "wˀ", "ˀɗ", "ˀl", "ˀm", "ˀn", "ˀw",
    "w̥",
    "lˑ", "tˑ", "kˑ", "sˑ", "ŋˑ", "nˑ",
    "ŋʲ",
    "hː", "qː",
    "ʎ̝",
    "ä", "äː",
    "ĭ", "ɪ̆", "ɛ̆", "ɐ̆", "ɜ̆", "ɔ̆", "ʊ̆", "ŭ", "ʉ̞̆",
    "a̤", "ə̤",
    'ɛ' '̘', 'i' '̘', 'u' '̘', 'a' '̘', 'ɔ' '̘',  'æ' '̘', "ɔ̞", "ɪ̞",
    "aiː", "aĩ", "aĭ",
    "e͡i", "a͡i", "o͡i", "u͡i", "a͡e", "o͡e", "e͡o", "a͡o", "i͡u", "e͡u", "a͡u", "o͡u",
])
tokenizer = Tokenizer(Profile(*({"Grapheme": x, "mapping": x} for x in sounds)),
                      errors_ignore = lambda c: c)

from pylexirumah import get_dataset, repository

def needleman_wunsch(x, y, lodict={}, gop=-2.5, gep=-1.75, local=False, indel=''):
    """Needleman-Wunsch algorithm with affine gaps penalties.

    This code implements the NW algorithm for pairwise string
    alignment with affine gap penalties.

    'lodict' must be a dictionary with all symbol pairs as keys and
    match scores as values, or a False value (including an empty
    dictionary) to denote (-1, 1) scores. gop and gep are gap
    penalties for opening/extending a gap; alternatively, you can set
    'gop' to None and provide element/gap alignment costs.
    indel takes the character used to denote an indel.

    Returns the alignment score and one optimal alignment.

    >>> needleman_wunsch("AAAAABBBB", "AACAABBCB")
    (5.0, [('A', 'A'), ('A', 'A'), ('A', 'C'), ('A', 'A'), ('A', 'A'), ('B', 'B'), ('B', 'B'), ('B', 'C'), ('B', 'B')])
    >>> needleman_wunsch("banana", "mancala", local=True)
    (2.0, [('a', 'a'), ('n', 'n')])
    >>> needleman_wunsch("abc", "t", lodict={('a', ''): 0, ('b', ''): -2, ('c', ''): -0.5}, gop=None)
    (-1.5, [('a', ''), ('b', 't'), ('c', '')])

    """
    n, m = len(x), len(y)
    dp = np.zeros((n + 1, m + 1))
    pointers = np.zeros((n + 1, m + 1), np.int32)
    if not local:
        for i1, c1 in enumerate(x):
            if gop is None:
                dp[i1 + 1, 0] = lodict.get((c1, indel), gep)
            else:
                dp[i1 + 1, 0] = dp[i1, 0]+(gep if i1 + 1 > 1 else gop)
            pointers[i1 + 1, 0] = 1
        for i2, c2 in enumerate(y):
            if gop is None:
                dp[0, i2 + 1] = lodict.get((indel, c2), gep)
            else:
                dp[0, i2 + 1] = dp[0, i2]+(gep if i2 + 1 > 1 else gop)
            pointers[0, i2 + 1] = 2
    for i1, c1 in enumerate(x):
        for i2, c2 in enumerate(y):
            match = dp[i1, i2] + lodict.get(
                (c1, c2),
                1 if c1 == c2 else -1)
            insert = dp[i1, i2 + 1] + (
                lodict.get((c1, indel), gep) if gop is None else
                gep if pointers[i1, i2 + 1] == 1 else gop)
            delet = dp[i1 + 1, i2] + (
                lodict.get((indel, c2), gep) if gop is None else
                gep if pointers[i1 + 1, i2] == 2 else gop)
            pointers[i1 + 1, i2 + 1] = p = np.argmax([match, insert, delet])
            max_score = [match, insert, delet][p]
            if local and max_score < 0:
                max_score = 0
            dp[i1 + 1, i2 + 1] = max_score
    alg = []
    if local:
        i, j = np.unravel_index(dp.argmax(), dp.shape)
    else:
        i, j = n, m
    score = dp[i, j]
    while (i > 0 or j > 0):
        pt = pointers[i, j]
        if pt == 0:
            i -= 1
            j -= 1
            alg = [(x[i], y[j])] + alg
        if pt == 1:
            i -= 1
            alg = [(x[i], indel)] + alg
        if pt == 2:
            j -= 1
            alg = [(indel, y[j])] + alg
        if local and dp[i, j] == 0:
            break
    return score, alg

def resolve_brackets(string):
    """Resolve a string into all description without brackets

    For a `string` with matching parentheses, but without nested parentheses,
    yield every combination of the contents of any parenthesis being present or
    absent.

    >>> list(resolve_brackets("no brackets"))
    ["no brackets"]

    >>> sorted(list(resolve_brackets("(no )bracket(s)")))
    ["bracket", "brackets", "no bracket", "no brackets"]

    """
    if "(" in string:
        opening = string.index("(")
        closing = string.index(")")
        for form in resolve_brackets(string[:opening] + string[closing+1:]):
            yield form.strip().strip("_")
        for form in resolve_brackets(string[:opening] + string[opening+1:closing] + string[closing+1:]):
            yield form.strip().strip("_")
    else:
        yield string

class Transducer:
    def __init__(self, rules):
        self.rules = rules
        self.wordboundary = "_"

    def __repr__(self):
        return "Transducer({:})".format(self.rules)

    def __str__(self):
        return " / ".join("{:} → {:}".format(before, after) for before, after in self.rules)

    def __call__(self, line):
        """Apply tranducer rules, left to right then first to last, to line.

        Apply the first fitting replacement rule (a before/after pair) to the
        leftmost bit of the string, then shift right past the replaced section and
        again apply the first matching rule, and so forth until you reach the end
        of the string. Note this is different to applying each rule in order
        whereever it fits, see the examples!

        Examples
        --------

        >>> string = "qaqqqqq"
        >>> rules = [("qq", "a"), ("aq", "b")]
        >>> Transducer(rules)(string)
        'qbaa'
        >>> for before, after in rules:
        ...   string.replace(before, after)
        'qaab'

        """
        line = self.wordboundary + line + self.wordboundary
        start = 0
        output = ""
        while start < len(line):
            oldStart = start
            for (left, right) in self.rules:
                match = False
                end = len(line) + 1
                while end > start:
                    if left == line[start:end]:
                        output += right
                        start = end
                        match = True
                        break
                    else:
                        end -= 1
                if match:
                    break
            if start == oldStart:
                output += line[start]
                start += 1
        return output.strip(self.wordboundary)

    def undo(self, line):
        """Undo – as much as possible, due to mergers – the effect this.

        Assume that every instance of a rule result on the right hand side is
        the result of application of this transducer. Assume furthermore that
        every pattern matched has been produced by the first rule that could
        produce such a pattern.

        >>> string = "qaqqqqq"
        >>> rules = [("qq", "a"), ("aq", "b")]
        >>> t = Transducer(rules)
        >>> t(string)
        'qbaa'
        >>> t.undo(t(string)) == string
        True

        """
        line = self.wordboundary + line + self.wordboundary
        start = 0
        output = ""
        while start < len(line):
            oldStart = start
            for (right, left) in self.rules:
                match = False
                end = len(line) + 1
                while end > start:
                    if left == line[start:end]:
                        output += right
                        start = end
                        match = True
                        break
                    else:
                        end -= 1
                if match:
                    break
            if start == oldStart:
                output += line[start]
                start += 1
        return output.strip(self.wordboundary)

def load_orthographic_profile(transducer_files, root=repository.parent, transducer_cache={}):
    if transducer_files is None:
        return None

    orthographic_profile = []
    for file in transducer_files:
        if not file:
            continue
        try:
            transducer_cache[file]
        except KeyError:
            # That file is not in our cache yet, we have to load it and
            # turn it into a function.
            substitutions = []
            for rule in (root / file).open(encoding="utf-8"):
                rule = rule.strip("\n")
                rule = rule.strip("\r")
                if "//" in rule:
                    rule = rule[:rule.index("//")]
                if not rule.strip():
                    continue
                if "[" in rule or "#def" in rule:
                    raise NotImplementedError("Context groups are not supported yet.")
                try:
                    before, after = rule.split("\t")
                except ValueError:
                    print(rule)
                    raise
                substitutions.append((before, after))
            transducer_cache[file] = Transducer(substitutions)
        orthographic_profile.append(transducer_cache[file])
    return orthographic_profile

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Import word lists from a new source into LexiRumah.")
    parser.add_argument("directory", nargs="?",
                        type=Path, default="./",
                        help="The folder containing the wordlist description,"
                        " derived from the standard template. (default: The"
                        " current working directory.)")
    parser.add_argument("--wordlist",
                        type=Path, default=repository,
                        help="The Wordlist to expand. (default: LexiRumah.)")
    parser.add_argument("--override",
                        default='none',
                        choices=["none", "all", "ask", "ask-per-source", "mark"],
                        help="Instead of just checking all forms and reporting"
                        "those that don't match, just overwrite everything"
                        "systematically.")
    parser.add_argument("--check-stress",
                        default=False, action="store_true",
                        help="By default, we ignore stress marks in comparisons."
                        " This option dumbs down the comparisons to report changes"
                        " in stress marking.")
    parser.add_argument("--match",
                        default=None,
                        help="Check only forms in which one of the columns"
                        " given has this substring.")

    parser.add_argument(
        "--step",
        default=["report", "override", "fill"],
        type=str.split,
        help="""What to do for the 3 checking steps Value→Form, Form→Segments and
        Form→Orthography: Keep <quiet>, <report> differences, <override>
        differences or <fill> empty cells. A list of three space-separated
        entries from these options. (Default: report override fill)""")
    args = parser.parse_args()

    if args.check_stress:
        def drop_stress(string):
            return string
    else:
        def drop_stress(string):
            return string and string.replace("ˈ", "").replace("ˌ", "")
    if args.override == 'none':
        def maybe_extend(collection, new, old):
            collection.extend(old)
    elif args.override == 'all':
        def maybe_extend(collection, new, old):
            collection.extend(new)
    elif args.override == 'ask-per-source':
        def maybe_extend(collection, new, old):
            if not new:
                return
            if input() == "y":
                collection.extend(new)
            else:
                collection.extend(old)
    elif args.override == 'ask':
        def maybe_extend(collection, new, old):
            for new_row, old_row in zip(new, old):
                if new_row == old_row:
                    collection.append(old_row)
                else:
                    print(old_row)
                    print(new_row)
                    if input() == "y":
                        collection.append(new_row)
                    else:
                        collection.append(old_row)
    elif args.override == 'mark':
        def maybe_extend(collection, new, old):
            for new_row, old_row in zip(new, old):
                try:
                    if new_row != old_row:
                        old_row[c_source].insert(0, "corr")
                except AttributeError:
                    old_row[c_source].insert(0, "corr")
                collection.append(old_row)

    dataset = get_dataset(args.wordlist)
    if dataset.module != 'Wordlist':
        raise ValueError(
            "This script can only import wordlist data to a CLDF Wordlist.")

    language_orthographies = {None: None}

    c_languageid = dataset["LanguageTable", "id"].name

    for line in dataset["LanguageTable"].iterdicts():
        transducer_files = line.get("Orthography") # This property is not codified by CLDF
        if not transducer_files:
            language_orthographies[line[c_languageid]] = None
        else:
            language_orthographies[line[c_languageid]] = load_orthographic_profile(transducer_files)

    transcription_systems = {None: None}

    c_segments = dataset["FormTable", "segments"].name
    c_source = dataset["FormTable", "source"].name
    c_language = dataset["FormTable", "languageReference"].name
    c_value = dataset["FormTable", "value"].name
    c_form = dataset["FormTable", "form"].name
    c_value = dataset["FormTable", "value"].name
    c_id = dataset["FormTable", "id"].name
    c_orth = "Local_Orthography" # This property is not codified by CLDF

    message = print

    lines = []
    original_lines_of_this_source = []
    new_lines_of_this_source = []
    previous_source = None
    for line in dataset["FormTable"].iterdicts():
        # Load the line's main source, that is, the first entry in the sources list.

        if args.match:
            for value in line.values():
                if args.match in str(value):
                    break
            else:
                new_lines_of_this_source.append(line)
                continue

        try:
            main_source = line[c_source][0]
        except (IndexError, KeyError):
            main_source = None
            message("Source not found for form {:}".format(line[c_id]))

        if main_source != previous_source:
            maybe_extend(
                lines,
                new_lines_of_this_source,
                original_lines_of_this_source)
            original_lines_of_this_source = []
            new_lines_of_this_source = []
            previous_source = main_source
            print(main_source)

        original_lines_of_this_source.append(line.copy())

        if not line[c_value] or line[c_value] == '-':
            if args.step[1] == "quiet":
                pass
            else:
                if line[c_form]:
                    message("Form {:} is not given in source, but had a form "
                            "{:} specified.".format(line[c_id], line[c_form]))
                line[c_form] = None

        # Load the orthographic profile of that main source.
        try:
            # First, see whether we have it in cache
            orthographic_profile = transcription_systems[main_source]
        except KeyError:
            # Otherwise, look up the name of the orthographic profile specified in
            # the source metadata.
            source = dataset.sources[main_source]
            try:
                transducer_files = source["orthographic_profile"].split(":")
            except KeyError:
                # It is permitted to not specify an orthographic profile in a
                # source. Then we assume the source is in ideosyncratic and rely on
                # forms being given explicitly. NOTE how this is different from
                # specifying an empty orthographic profile: An empty profile means
                # that no transducers are applied, i.e. that the data is already in
                # IPA.
                transducer_files = None

            # Now we get the list of transducer functions to apply.
            orthographic_profile = load_orthographic_profile(transducer_files)
            if orthographic_profile:
                print(*(str(o) for o in orthographic_profile))
            transcription_systems[main_source] = orthographic_profile

        if args.step[0] == 'quiet':
            form = line[c_form] or ''
        else:
            if orthographic_profile is None:
                # There is no way to do automatic transcription: Check that a form is given.
                form = line[c_form] or ''
                if not form:
                    message(
                        "Form {:} has ideosyncratic orthography and original value"
                        " <{:}>, but no form was given.".format(line[c_id], line[c_value]))
            else:
                if "/" in line[c_value]:
                    message(
                        "Form {:} is listed as <{:}>, which is likely an "
                        "invalid cell containing two different forms.".format(line[c_id], line[c_value]))

                # Apply substitutions to form
                form = (line[c_value] or '').strip()
                for transducer in orthographic_profile:
                    form = transducer(form)

            if form != line[c_form]:
                resolutions = [drop_stress(r) for r in resolve_brackets(form)]
                if len(resolutions) > 1 and drop_stress(line[c_form]) in resolutions:
                    variant = resolutions.index(drop_stress(line[c_form]))
                    resolution = list(resolve_brackets(form))[variant]
                    if len(resolution) > len(line[c_form]):
                        message("Form {:} has original value <{:}>, which contains brackets. Canonically, it would be [{:}] according to the orthography. Variant form [{:}] was given explicitly. Taking form [{:}] as compromise.".format(line[c_id], line[c_value], form, line[c_form], resolution))
                        form = resolution
                    else:
                        message("Form {:} has original value <{:}>, which contains brackets. Canonically, it would be [{:}] according to the orthography. Variant form [{:}] was given explicitly.".format(line[c_id], line[c_value], form, line[c_form]))
                        form = line[c_form]
                elif not line[c_form]:
                    if len(resolutions) > 1:
                        form = [drop_stress(r) for r in resolve_brackets(form)][0]
                    message(
                        "Form {:} has original value <{:}>, which corresponds to"
                        " [{:}] according to the orthography; no form given."
                        "".format(line[c_id], line[c_value], form, line[c_form]))
                elif line[c_form] != drop_stress(form):
                    message(
                        "Form {:} has original value <{:}>, which should correspond to"
                        " [{:}] according to the orthography, but form [{:}] was given."
                        "".format(line[c_id], line[c_value], form, line[c_form]))

            if args.step[0] == "override" or (args.step[0] == "fill" and not line[c_form]):
                line[c_form] = form

        # Segment form and check with BIPA The segments cannot deal cleanly with
        # suprasegmentals (syllable boundaries, syllable stress), so those are
        # ignored explicitly or implicitly.
        if args.step[1] == "quiet":
            segments = [bipa[x] for x in line[c_segments]]
        else:
            segments = []
            for part in re.split('[.ˈˌ]', line[c_form] or ''):
                if not part:
                    continue
                for x in tokenizer(part, errors="ignore").split(" "):
                    sound = bipa[x]
                    if isinstance(sound, pyclts.models.UnknownSound):
                        message(
                            "Form {:} [{:}] contains non-BIPA segment '{:}'."
                            "".format(
                                line[c_id], form, x))
                    segments.append(sound)

            if (line[c_segments]) and ([str(bipa[x or '']) for x in line[c_segments]] !=
                [str(x) for x in segments]):
                    message(
                        "Form {:} has form [{:}], which should correspond to segments"
                        " [{:}], but segments [{:}] were given."
                        "".format(
                            line[c_id],
                            line[c_form],
                            " ".join(map(str, segments)),
                            " ".join([s or '' for s in line[c_segments]])))

            if args.step[1] == "override" or (args.step[1] == "fill" and not line[c_segments]):
                line[c_segments] = segments

        if args.step[2] == "quiet":
            pass
        else:
            language_orthography = language_orthographies[line[c_language]] or ""

            # Check the form's orthography.
            if not language_orthography:
                if not line[c_orth]:
                    message("Form {:} [{:}] is not given in the local orthography,"
                            " and no way to derive it was given.".format(
                                line[c_id], line[c_form]))

            # For checking, transform the local orthography to the form by
            # applying the language's orthography.
            orth_form = line[c_orth] or ""
            match = False
            if orth_form:
                expected_form = orth_form
                for transducer in language_orthography:
                    expected_form = transducer(expected_form)

                if drop_stress(expected_form) == drop_stress(line[c_form]):
                    match = True

            # If that does not work, try tranforming the form to the local
            # orthography by reverse-applying the language's orthography.
            expected_orth = line[c_form]
            for transducer in reversed(language_orthography):
                expected_orth = transducer.undo(expected_orth)
            expected_orth = expected_orth.replace("_", " ").replace("+", "-").strip()

            if match:
                pass
            elif expected_orth != orth_form and orth_form:
                message(
                    "Form {:} is given in the local orthography as <{:}>, but"
                    " phonetics [{:}] would correspond to <{:}>.".format(
                        line[c_id], line[c_orth], form, expected_orth))
            elif orth_form:
                pass
            else:
                message(
                    "Form {:} is not given in the local orthography. From its"
                    " phonetics [{:}], taking <{:}>.".format(
                        line[c_id], form, expected_orth))

            if args.step[2] == "override" or (args.step[2] == "fill" and not line[c_orth]):
                line[c_orth] = expected_orth

        new_lines_of_this_source.append(line)

    maybe_extend(
        lines,
        new_lines_of_this_source,
        original_lines_of_this_source)

    if args.override != 'none':
        dataset["FormTable"].write(lines)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3
from functools import wraps

from . import variables as v, app

DB_WRITE_ATTEMPTS = 100
DB_WRITE_ATTEMPTS_TIMEOUT = 1  # in seconds
DB_CONNECTION_TIMEOUT = 10

class LockedDatabase(Exception):
    """
    Dedicated class to make sure we're not silently catching locked DBs.
    """
    pass

def catch_operationalerrors(method):
    """
    sqlite.OperationalError is raised immediately if another DB connection
    is open, reading something that we're trying to change

    So let's catch it and try again

    Also see https://github.com/mattn/go-sqlite3/issues/274
    """
    @wraps(method)
    def wrapper(self, *args, **kwargs):
        attempts = DB_WRITE_ATTEMPTS
        while True:
            try:
                return method(self, *args, **kwargs)
            except sqlite3.OperationalError as err:
                if 'database is locked' not in err:
                    # Not an error we want to catch, so reraise it
                    raise
                attempts -= 1
                if attempts == 0:
                    # Reraise in order to NOT catch nested OperationalErrors
                    raise LockedDatabase('Database is locked')
                # Need to close the transactions and begin new ones
                self.kodiconn.commit()
                if self.artconn:
                    self.artconn.commit()
                if app.APP.monitor.waitForAbort(DB_WRITE_ATTEMPTS_TIMEOUT):
                    # PKC needs to quit
                    return
                # Start new transactions
                self.kodiconn.execute('BEGIN')
                if self.artconn:
                    self.artconn.execute('BEGIN')
    return wrapper

def _initial_db_connection_setup(conn):
    """
    Set-up DB e.g. for WAL journal mode, if that hasn't already been done
    before. Also start a transaction
    """
    conn.execute('PRAGMA journal_mode = WAL;')
    conn.execute('PRAGMA cache_size = -8000;')
    conn.execute('PRAGMA synchronous = NORMAL;')
    conn.execute('BEGIN')

def connect(media_type=None):
    """
    Open a connection to the Kodi database.
        media_type: 'video' (standard if not passed), 'plex', 'music', 'texture'
    """
    if media_type == "plex":
        db_path = v.DB_PLEX_PATH
    elif media_type == 'plex-copy':
        db_path = v.DB_PLEX_COPY_PATH
    elif media_type == "music":
        db_path = v.DB_MUSIC_PATH
    elif media_type == "texture":
        db_path = v.DB_TEXTURE_PATH
    else:
        db_path = v.DB_VIDEO_PATH
    conn = sqlite3.connect(db_path,
                           timeout=DB_CONNECTION_TIMEOUT,
                           isolation_level=None)
    attempts = DB_WRITE_ATTEMPTS
    while True:
        try:
            _initial_db_connection_setup(conn)
        except sqlite3.OperationalError as err:
            if 'database is locked' not in err:
                # Not an error we want to catch, so reraise it
                raise
            attempts -= 1
            if attempts == 0:
                # Reraise in order to NOT catch nested OperationalErrors
                raise LockedDatabase('Database is locked')
            if app.APP.monitor.waitForAbort(0.05):
                # PKC needs to quit
                raise LockedDatabase('Database was locked and we need to exit')
        else:
            break
    return conn


import os

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from starlette.responses import HTMLResponse, FileResponse
from starlette.staticfiles import StaticFiles

import models
import schemas
import crud
from database import SessionLocal, engine

if not os.path.exists('.\mysqldb'):
    os.makedirs('.\mysqldb')

models.Base.metadata.create_all(bind=engine)

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/", response_class=HTMLResponse)
def read_root():
    return FileResponse("static/index.html")

@app.post("/restaurant/", response_model=schemas.Restaurant)
def create_restaurant(restaurant: schemas.RestaurantCreate, db: Session = Depends(get_db)):
    return crud.create_restaurant(db=db, restaurant=restaurant)

@app.get("/restaurants/", response_model=list[schemas.Restaurant])
def read_restaurants(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    restaurants = crud.get_restaurants(db, skip, limit)
    return restaurants

@app.get("/restaurants/{restaurant_id}", response_model=schemas.Restaurant)
def read_restaurants(restaurant_id: int, db: Session = Depends(get_db)):
    db_restaurant = crud.get_restaurant(db, restaurant_id=restaurant_id)

    if db_restaurant is None:
        raise HTTPException(status_code=404, detail="Restaurant not found")

    return db_restaurant

@app.post("/owner/", response_model=schemas.Owner)
def create_owner(owner: schemas.OwnerCreate, db: Session = Depends(get_db)):
    return crud.create_owner(db=db, owner=owner)

@app.get("/owners/", response_model=list[schemas.Owner])
def read_owners(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    owners = crud.get_owners(db, skip, limit)

    return owners

@app.post("/menuitem/", response_model=schemas.MenuItem)
def create_menu_item(menu_item: schemas.MenuItemCreate, db: Session = Depends(get_db)):
    return crud.create_menu_item(db=db, menu_item=menu_item)

@app.get("/menuitems/{restaurant_id}", response_model=list[schemas.MenuItem])
def read_menu_items(restaurant_id: int, db: Session = Depends(get_db)):
    menu_items = crud.get_menu_items(db, restaurant_id)

    if not menu_items:
        raise HTTPException(status_code=404, detail="Restaurant not found or no items on menu")

    return menu_items

@app.delete("/restaurants/{restaurant_id}/menuitems/{menu_item_id}", response_model=schemas.MenuItem)
def delete_menu_item(restaurant_id: int, menu_item_id: int, db: Session = Depends(get_db)):
    deleted_menu_item = crud.delete_menu_item(db, restaurant_id, menu_item_id)

    if deleted_menu_item is None:
        raise HTTPException(status_code=404, detail="Restaurant or menu not found")

    return deleted_menu_item


import random
import sys
import pygame as pg

WIDTH, HEIGHT = 1600, 900
delta = {
    pg.K_UP:(0,-5),
    pg.K_DOWN:(0,+5),
    pg.K_RIGHT:(+5,0),
    pg.K_LEFT:(-5,0)
}

accs = [a for a in range(1,11)]

def check_bound(rect:pg.Rect) -> tuple[bool,bool]:
    """
    こうかとんRectと爆弾Rectが画面外 or 画面内かを判定する関数
    引数：こうかとんRectか爆弾Rect
    戻り値:横方向・縦方向の真理値タプル
    """
    yoko,tate = True,True
    if rect.left < 0 or WIDTH < rect.right:
        yoko = False
    if rect.top < 0 or HEIGHT < rect.bottom:
        tate = False
    return yoko,tate

def main():
    pg.display.set_caption("逃げろ！こうかとん")
    screen = pg.display.set_mode((WIDTH, HEIGHT))
    bg_img = pg.image.load("ex02/fig/pg_bg.jpg")
    kk_img = pg.image.load("ex02/fig/3.png")
    kk_rot = {
        (0, 0): kk_img,
        (0, -5): pg.transform.rotozoom(pg.transform.flip(kk_img, True, False), 90, 1),
        (5, -5): pg.transform.rotozoom(pg.transform.flip(kk_img, True, False), 45, 1),
        (5, 0): pg.transform.flip(kk_img, True, False),
        (5, 5): pg.transform.rotozoom(pg.transform.flip(kk_img, True, False), -45, 1),
        (0, 5): pg.transform.rotozoom(pg.transform.flip(kk_img, True, False), -90, 1),
        (-5, 5): pg.transform.rotozoom(kk_img, 45, 1),
        (-5, 0): kk_img,
        (-5, -5): pg.transform.rotozoom(kk_img, -45, 1)
    }
    kk_rect = kk_img.get_rect() #練習３
    kk_rect.center = 900,400
    #bd_img = pg.Surface((20,20))  #練習１

    clock = pg.time.Clock()
    tmr = 0

    bd_imgs = []
    for r in range(1,11):
        bd_img = pg.Surface((20*r,20*r))
        pg.draw.circle(bd_img,(255,0,0),(10*r,10*r),10*r)
        bd_img.set_colorkey((0,0,0))
        bd_imgs.append(bd_img)

    vx,vy = +5,+5 #練習２
    bd_rect = bd_imgs[min(tmr//500,9)].get_rect()
    x = random.randint(0,WIDTH)
    y = random.randint(0,HEIGHT)
    #爆弾rectの中心座標を乱数指定する

    bd_rect.center = x,y

    while True:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                return
            if kk_rect.colliderect(bd_rect):  # 練習５
                print("ゲームオーバー")
                return   # ゲームオーバー
        key_list = pg.key.get_pressed()#練習３
        sum_mv = [0,0]
        for k, mv in delta.items():
            if key_list[k]:
                sum_mv[0] += mv[0]
                sum_mv[1] += mv[1]
        kk_rot_img = kk_rot[tuple(sum_mv)]

        kk_rect.move_ip(sum_mv)
        if check_bound(kk_rect) != (True,True):
            kk_rect.move_ip(-sum_mv[0],-sum_mv[1])

        screen.blit(bg_img, [0, 0])
        screen.blit(kk_rot_img, kk_rect)
        #bd_rect.move_ip(vx,vy) #練習２
        yoko,tate = check_bound(bd_rect)
        if not yoko:
            vx *= -1
        if not tate:
            vy *= -1
        avx,avy = vx*accs[min(tmr//500,9)],vy*accs[min(tmr//500,9)]
        bd_rect.move_ip(avx,avy)
        bd_img = bd_imgs[min(tmr//500,9)]
        screen.blit(bd_img,bd_rect)
        pg.display.update()
        tmr += 1
        clock.tick(50)

if __name__ == "__main__":
    pg.init()
    main()
    pg.quit()
    sys.exit()