import json
import shutil
from datetime import timedelta
from pathlib import Path
from typing import Union, List
from unittest import mock

import pytest
from django.conf import settings
from django.utils import timezone

from app_root.players.models import (
    PlayerShipOffer,
    PlayerFactory,
    PlayerFactoryProductOrder,
    PlayerJob,
)
from app_root.players.utils_import import InitdataHelper
from app_root.servers.models import RunVersion, SQLDefinition, EndPoint, TSProduct
from app_root.servers.utils_import import SQLDefinitionHelper
from app_root.strategies.commands import ShopPurchaseItem
from app_root.strategies.dumps import ts_dump, ts_dump_factory
from app_root.strategies.managers import (
    jobs_find,
    trains_find_match_with_job,
    trains_max_capacity,
    jobs_find_union_priority,
    daily_offer_get_slots,
    factory_order_product,
    factory_collect_product,
)
from app_root.strategies.utils import Strategy
from app_root.users.models import User
from app_root.utils import get_curr_server_str_datetime_s
from core.tests.factory import AbstractFakeResp
from core.utils import convert_datetime, hash10

from core.utils import create_datetime as dt

def prepare(initdata_filepath: Union[List[Path], Path]):
    user = User.objects.create_user(
        username="test", android_id="test", game_access_token="1", player_id="1"
    )
    version = RunVersion.objects.create(user_id=user.id, level_id=1)

    db_helper = SQLDefinitionHelper(version=version)
    instance = SQLDefinition.objects.create(
        version="206.013",
        checksum="077e2eff27bdd5cb079319c6d40f0916d9671b20",
        # url='https://cdn.trainstation2.com/client-resources/client-data-206.013.sqlite',
        # download_path=settings.DJANGO_PATH / 'fixtures' / '206.013.sqlite'
        url="https://cdn.trainstation2.com/client-resources/client-data-207.003.sqlite",
        download_path=settings.DJANGO_PATH / "fixtures" / "207.003.sqlite",
    )
    db_helper.read_sqlite(instance=instance)

    init_helper = InitdataHelper(version=version)
    if not isinstance(initdata_filepath, list):
        initdata_filepath = [initdata_filepath]

    for filepath in initdata_filepath:
        init_helper.parse_data(data=filepath.read_text(encoding="UTF-8"))

    EndPoint.objects.create(
        name=EndPoint.ENDPOINT_COMMAND_PROCESSING,
        name_hash=hash10(EndPoint.ENDPOINT_COMMAND_PROCESSING),
        url="a",
    )

    return version

@pytest.mark.django_db
@pytest.mark.parametrize(
    "init_filenames",
    [
        [
            "init_data/gaolious_2022.12.30-1.json",
            "init_data/gaolious_2022.12.30-2.json",
        ]
    ],
)
def test_ship_offer(multidb, init_filenames):
    ###########################################################################
    # prepare
    paths = [settings.DJANGO_PATH / "fixtures" / path for path in init_filenames]
    version = prepare(initdata_filepath=paths)

    assert version
    assert version.guild_id
    assert PlayerShipOffer.objects.count() == 1

@pytest.mark.django_db
@pytest.mark.parametrize(
    "init_filenames",
    [
        [
            "strategies/contract_check/2_2.json",
            "strategies/contract_check/2_3.json",
        ]
    ],
)
def test_strategy(multidb, init_filenames):
    ###########################################################################
    # prepare
    class FakeResp(AbstractFakeResp):
        text = """{"Success":true,"RequestId":"0a645537-2b6d-4c99-81ef-d968877ca303","Time":"2023-01-23T12:43:06Z","Data":{"CollectionId":1,"Commands":[]}}"""

    paths = [settings.DJANGO_PATH / "fixtures" / path for path in init_filenames]
    version = prepare(initdata_filepath=paths)

    txt = paths[-1].read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now
        ts_dump(version=version)

        with mock.patch("app_root.mixins.CrawlingHelper.post") as post:
            post.side_effect = FakeResp
            strategy = Strategy(user_id=version.user_id)
            strategy.run()

@pytest.mark.django_db
@pytest.mark.parametrize(
    "init_filename, event_count, union_count, story_count, side_count",
    [
        ("strategies/6/6_0.json", 0, 0, 3, 0),
        ("init_data/gaolious1_2022.12.29.json", 0, 0, 4, 0),
        ("init_data/gaolious_2022.12.30-1.json", 3, 4, 6, 5),
        ("init_data/gaolious_2023.01.09_contract.json", 0, 4, 4, 5),
        ("init_data/gaolious_2023.01.09_gifts.json", 0, 3, 4, 5),
        ("init_data/gaolious_2023.01.11_jobs.json", 3, 3, 4, 5),
        ("init_data/gaolious_2023.01.14_fulltest.json", 3, 4, 4, 5),
        ("init_data/gaolious_2023.01.14_idle_destinations.json", 3, 4, 4, 5),
        ("init_data/gaolious_2023.01.14_results.json", 3, 4, 4, 5),
        ("init_data/gaolious_2023.01.11_jobs.json", 3, 3, 4, 5),
    ],
)
def test_prepare_material(
    multidb, init_filename, event_count, union_count, story_count, side_count
):
    ###########################################################################
    # prepare
    initdata_filepath = settings.DJANGO_PATH / "fixtures" / init_filename

    version = prepare(initdata_filepath=initdata_filepath)

    jobs = list(jobs_find(version, event_jobs=True))
    assert len(jobs) == event_count, "event only"
    for job in jobs:
        trains = list(trains_find_match_with_job(version=version, job=job))
        assert trains

    jobs = list(jobs_find(version, union_jobs=True))
    assert len(jobs) == union_count, "union only"
    for job in jobs:
        trains = list(trains_find_match_with_job(version=version, job=job))
        assert trains

    jobs = list(jobs_find(version, story_jobs=True))
    assert len(jobs) == story_count, "story only"
    for job in jobs:
        trains = list(trains_find_match_with_job(version=version, job=job))
        assert trains

    jobs = list(jobs_find(version, side_jobs=True))
    assert len(jobs) == side_count, "side only"
    for job in jobs:
        trains = list(trains_find_match_with_job(version=version, job=job))
        assert trains

@pytest.mark.django_db
@pytest.mark.parametrize(
    "init_filename, event_count, union_count, story_count, side_count",
    [
        ("strategies/6/6_0.json", 0, 0, 3, 0),
        ("init_data/gaolious1_2022.12.29.json", 0, 0, 4, 0),
        ("init_data/gaolious_2022.12.30-1.json", 3, 4, 6, 5),
        ("init_data/gaolious_2023.01.09_contract.json", 0, 4, 4, 5),
        ("init_data/gaolious_2023.01.09_gifts.json", 0, 3, 4, 5),
        ("init_data/gaolious_2023.01.11_jobs.json", 3, 3, 4, 5),
        ("init_data/gaolious_2023.01.14_fulltest.json", 3, 4, 4, 5),
        ("init_data/gaolious_2023.01.14_idle_destinations.json", 3, 4, 4, 5),
        ("init_data/gaolious_2023.01.14_results.json", 3, 4, 4, 5),
    ],
)
def test_find_job_materials(
    multidb, init_filename, event_count, union_count, story_count, side_count
):
    ###########################################################################
    # prepare

    initdata_filepath = settings.DJANGO_PATH / "fixtures" / init_filename

    version = prepare(initdata_filepath=initdata_filepath)

    txt = initdata_filepath.read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now

        # for job in jobs_find(version=version):
        #     materials = jobs_find_sources(version=version, job=job)
        #
        #     assert materials

@pytest.mark.django_db
@pytest.mark.parametrize(
    "init_filename, event_count, union_count, story_count, side_count",
    [
        ("strategies/6/6_0.json", 0, 0, 3, 0),
        ("init_data/gaolious1_2022.12.29.json", 0, 0, 4, 0),
        ("init_data/gaolious_2022.12.30-1.json", 3, 4, 6, 5),
        ("init_data/gaolious_2023.01.09_contract.json", 0, 4, 4, 5),
        ("init_data/gaolious_2023.01.09_gifts.json", 0, 3, 4, 5),
        ("init_data/gaolious_2023.01.11_jobs.json", 3, 3, 4, 5),
        ("init_data/gaolious_2023.01.14_fulltest.json", 3, 4, 4, 5),
        ("init_data/gaolious_2023.01.14_idle_destinations.json", 3, 4, 4, 5),
        ("init_data/gaolious_2023.01.14_results.json", 3, 4, 4, 5),
    ],
)
def test_dump(
    multidb, init_filename, event_count, union_count, story_count, side_count
):
    ###########################################################################
    # prepare
    initdata_filepath = settings.DJANGO_PATH / "fixtures" / init_filename

    version = prepare(initdata_filepath=initdata_filepath)

    txt = initdata_filepath.read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now

        ts_dump(version=version)

@pytest.mark.django_db
@pytest.mark.parametrize(
    "init_filename",
    [
        "strategies/6/6_0.json",
        "init_data/gaolious1_2022.12.29.json",
        "init_data/gaolious_2022.12.30-1.json",
        "init_data/gaolious_2023.01.09_contract.json",
        "init_data/gaolious_2023.01.09_gifts.json",
        "init_data/gaolious_2023.01.11_jobs.json",
        "init_data/gaolious_2023.01.14_fulltest.json",
        "init_data/gaolious_2023.01.14_idle_destinations.json",
        "init_data/gaolious_2023.01.14_results.json",
    ],
)
def test_available_max_capacity(multidb, init_filename):
    ###########################################################################
    # prepare
    initdata_filepath = settings.DJANGO_PATH / "fixtures" / init_filename

    version = prepare(initdata_filepath=initdata_filepath)

    txt = initdata_filepath.read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now

        ret = trains_max_capacity(version=version, region=1)
        assert len(ret) >= 1

@pytest.mark.django_db
@pytest.mark.parametrize(
    "init_filename",
    [
        # 'strategies/6/6_0.json',
        "init_data/gaolious1_2022.12.29.json",
        # 'init_data/gaolious_2022.12.30-1.json',
        # 'init_data/gaolious_2023.01.09_contract.json',
        # 'init_data/gaolious_2023.01.09_gifts.json',
        # 'init_data/gaolious_2023.01.11_jobs.json',
        # 'init_data/gaolious_2023.01.14_fulltest.json',
        # 'init_data/gaolious_2023.01.14_idle_destinations.json',
        # 'init_data/gaolious_2023.01.14_results.json',
        # 'init_data/gaolious_2023.02.05.json',
        # 'init_data/gaolious_2023.02.07.json',
    ],
)
def test_shop_purchase_item(multidb, init_filename, fixture_send_commands):
    ###########################################################################
    # prepare
    initdata_filepath = settings.DJANGO_PATH / "fixtures" / init_filename

    version = prepare(initdata_filepath=initdata_filepath)

    txt = initdata_filepath.read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now
        cnt1, cnt2 = 0, 0
        daily_offer_items = daily_offer_get_slots(
            version=version,
            available_video=True,
            availble_gem=False,
            available_gold=False,
        )
        for offer_item in daily_offer_items:
            cmd = ShopPurchaseItem(version=version, offer_item=offer_item)
            cmd.post_processing({})
            cnt1 += 1

        daily_offer_items = daily_offer_get_slots(
            version=version,
            available_video=True,
            availble_gem=False,
            available_gold=False,
        )
        for offer_item in daily_offer_items:
            cmd = ShopPurchaseItem(version=version, offer_item=offer_item)
            cmd.post_processing({})
            cnt2 += 1

        assert cnt1 >= 0
        assert cnt2 == 0

@pytest.mark.django_db
@pytest.mark.parametrize(
    "init_filename",
    [
        # 'strategies/6/6_0.json',
        # 'init_data/gaolious1_2022.12.29.json',
        # 'init_data/gaolious_2022.12.30-1.json',
        # 'init_data/gaolious_2023.01.09_contract.json',
        # 'init_data/gaolious_2023.01.09_gifts.json',
        # 'init_data/gaolious_2023.01.11_jobs.json',
        # 'init_data/gaolious_2023.01.14_fulltest.json',
        # 'init_data/gaolious_2023.01.14_idle_destinations.json',
        # 'init_data/gaolious_2023.01.14_results.json',
        # 'init_data/gaolious_2023.02.05.json',
        "init_data/gaolious_2023.02.07.json",
    ],
)
def test_job_prioirty(multidb, init_filename):
    ###########################################################################
    # prepare
    initdata_filepath = settings.DJANGO_PATH / "fixtures" / init_filename

    version = prepare(initdata_filepath=initdata_filepath)

    txt = initdata_filepath.read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now
        jobs_find_union_priority(version=version)

def convert_text(txt: str):
    data = json.loads(txt, strict=False)
    t = timezone.now().isoformat(sep="T", timespec="seconds").replace("+00:00", "Z")
    data.update({"Time": t})

    return json.dumps(data, separators=(",", ":"))

@pytest.fixture(scope="function")
def fixture_endpoint():
    with mock.patch("app_root.strategies.utils.EndpointHelper.get") as patch:
        patch.return_value = convert_text(
            (
                settings.DJANGO_PATH
                / "fixtures"
                / "endpoints"
                / "gaolious1_2022.12.29.json"
            ).read_text("utf-8")
        )
        yield patch

@pytest.fixture(scope="function")
def fixture_login():
    with mock.patch("app_root.strategies.utils.LoginHelper.post") as patch:
        patch.return_value = convert_text(
            (
                settings.DJANGO_PATH
                / "fixtures"
                / "login"
                / "gaolious1_2022.12.29_with_device_id.json"
            ).read_text("utf-8")
        )
        yield patch

@pytest.fixture(scope="function")
def fixture_definition():
    sqllite_filepath = settings.DJANGO_PATH / "fixtures" / "207.004.sqlite"

    def FakeDownload(instance, *args, **kwargs):
        download_filename = Path(instance.download_path)
        shutil.copy(sqllite_filepath, download_filename)
        return 1_000_000

    with mock.patch("app_root.strategies.utils.SQLDefinitionHelper.get") as patch:
        patch.return_value = convert_text(
            (
                settings.DJANGO_PATH
                / "fixtures"
                / "definition"
                / "gaolious1_2022.12.29.json"
            ).read_text("utf-8")
        )

        with mock.patch(
            "app_root.strategies.utils.SQLDefinitionHelper.download_data"
        ) as dn:
            dn.side_effect = FakeDownload
            yield dn

@pytest.fixture(scope="function")
def fixture_init_20230207():
    filename = "gaolious_2023.02.07.json"
    with mock.patch("app_root.strategies.utils.InitdataHelper.get") as patch:
        patch.side_effect = [
            convert_text(
                (settings.DJANGO_PATH / "fixtures" / "init_data" / filename).read_text(
                    "utf-8"
                )
            ),
            "{}",
        ]
        yield patch

@pytest.fixture(scope="function")
def fixture_init_20230210():
    filename = "gaolious_2023.02.10.json"
    with mock.patch("app_root.strategies.utils.InitdataHelper.get") as patch:
        patch.side_effect = [
            convert_text(
                (settings.DJANGO_PATH / "fixtures" / "init_data" / filename).read_text(
                    "utf-8"
                )
            ),
            "{}",
        ]
        yield patch

@pytest.fixture(scope="function")
def fixture_leader_board():
    with mock.patch("app_root.strategies.utils.LeaderboardHelper.run") as patch:
        yield patch

@pytest.fixture(scope="function")
def fixture_start_game():
    with mock.patch("app_root.strategies.utils.StartGame.run") as patch:
        yield patch

class FakeRunCommand(object):
    commands: List
    version: RunVersion

    def __init__(self, version, commands):
        self.commands = commands
        self.version = version

    def run(self):
        for cmd in self.commands:
            print(f"* FakeCmd : {cmd.COMMAND} {cmd.get_parameters()}")
            cmd.post_processing({})

@pytest.fixture(scope="function")
def fixture_send_commands():
    with mock.patch("app_root.strategies.commands.RunCommand") as patch:
        patch.side_effect = FakeRunCommand
        yield patch

@pytest.fixture(scope="function")
def fixture_sleep():
    with mock.patch("app_root.strategies.commands.sleep") as patch:
        yield patch

@pytest.fixture(scope="function")
def fixture_use_cache():
    with mock.patch("app_root.strategies.utils.USE_CACHE", True) as patch:
        yield patch

@pytest.mark.django_db
@pytest.mark.parametrize("user_name, run_version_id", [("gaolious", 13)])
def test_materials_find_redundancy(
    multidb,
    user_name,
    run_version_id,
    fixture_send_commands,
    fixture_sleep,
    fixture_use_cache,
):
    ###########################################################################
    # prepare
    user = User.objects.create_user(
        username=user_name, android_id="test", game_access_token="1", player_id="1"
    )
    version = RunVersion.objects.create(id=run_version_id, user_id=user.id, level_id=1)
    initdata_filepath = version.get_account_path() / "startgame_post.txt"
    txt = initdata_filepath.read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now
        s = Strategy(user.id)
        s.run()

@pytest.mark.django_db
@pytest.mark.parametrize(
    "user_name, run_version_id",
    [
        ("gaolious", 111),
        # ('gaolious', 34),  # 가능 상태
    ],
)
def test_prepare_contract(
    multidb,
    user_name,
    run_version_id,
    fixture_send_commands,
    fixture_sleep,
    fixture_use_cache,
):
    ###########################################################################
    # prepare
    user = User.objects.create_user(
        username=user_name, android_id="test", game_access_token="1", player_id="1"
    )
    version = RunVersion.objects.create(id=run_version_id, user_id=user.id, level_id=1)
    initdata_filepath = version.get_account_path() / "startgame_post.txt"
    txt = initdata_filepath.read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])
    version.now = now
    version.save()

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now
        s = Strategy(user.id)
        s.run()

@pytest.mark.django_db
@pytest.mark.parametrize(
    "user_name, run_version_id",
    [
        ("gaolious1", 2),  # 약 1분정도 남은 상태.
        # ('gaolious', 34),  # 가능 상태
    ],
)
def test_factory_order_product(
    multidb,
    user_name,
    run_version_id,
    fixture_send_commands,
    fixture_sleep,
    fixture_use_cache,
):
    ###########################################################################
    # prepare
    user = User.objects.create_user(
        username=user_name, android_id="test", game_access_token="1", player_id="1"
    )
    version = RunVersion.objects.create(id=run_version_id, user_id=user.id, level_id=1)
    initdata_filepath = version.get_account_path() / "startgame_post.txt"
    txt = initdata_filepath.read_text(encoding="utf-8")
    json_data = json.loads(txt, strict=False)
    now = convert_datetime(json_data["Time"])
    version.now = now
    version.save()

    with mock.patch("django.utils.timezone.now") as p:
        p.return_value = now
        s = Strategy(user.id)
        s.run()
        prod = TSProduct.objects.filter(article_id=224).first()
        order_list = list(
            PlayerFactoryProductOrder.objects.filter(
                player_factory__factory_id=prod.factory_id
            )
            .order_by("index")
            .all()
        )
        before = ts_dump_factory(version=version, factory_id=prod.factory_id)
        print("[INIT]")
        print("\n".join(before))

        for order in order_list:
            order.refresh_from_db()
            factory_collect_product(version=version, order=order)
            after1 = ts_dump_factory(
                version=version, factory_id=order.player_factory.factory_id
            )
            print("[After Collect]")
            print("\n".join(after1))

            factory_order_product(version=version, product=prod)
            after2 = ts_dump_factory(
                version=version, factory_id=order.player_factory.factory_id
            )
            print("[After add]")
            print("\n".join(after2))

@pytest.mark.django_db
def test_create_player_job():
    json_data = [
        {
            "JobLocationId": 200013,
            "CurrentArticleAmount": 2300,
            "Sequence": 0,
            "JobLevel": 1,
            "RequiredArticle": {"Amount": 2300, "Id": 200006},
            "ExpiresAt": "2023-06-02T12:00:00Z",
            "Duration": 3600,
            "CompletableFrom": "2023-03-25T15:48:48Z",
            "JobType": 49,
            "Reward": {
                "Items": [
                    {"Amount": 850, "Id": 8, "Value": 100000},
                    {"Id": 8, "Value": 100003, "Amount": 500},
                ]
            },
            "FinishedAt": "2023-03-25T14:48:48Z",
            "Id": "e75bbb80-5934-4c84-9d21-beb0bcc35171",
            "Requirements": [
                {"Value": 0, "Type": "relative_region"},
                {"Type": "content_category", "Value": 3},
                {"Value": 1, "Type": "era"},
            ],
        }
    ]

    a, b = PlayerJob.create_instance(data=json_data, version_id=1)

    CompletableFrom = dt(2023, 3, 26, 0, 48, 48)
    FinishedAt = dt(2023, 3, 25, 23, 48, 48)

    assert a
    assert a[0]
    job: PlayerJob = a[0]

    now = FinishedAt - timedelta(minutes=1)
    assert not job.is_completed(now)
    assert not job.is_collectable(now)

    now = CompletableFrom - timedelta(minutes=1)
    assert job.is_completed(now)
    assert not job.is_collectable(now)

    now = CompletableFrom + timedelta(minutes=1)
    assert job.is_completed(now)
    assert job.is_collectable(now)


valores = [100, 44, 53, 4, 62, 64, 99, 28, 8, 8]

indiceMenorValor = 0
for i in range(len(valores)):
    if valores[indiceMenorValor] > valores[i]:
        indiceMenorValor = i

print("A lista tem os seguintes valores:")
for i in range(len(valores)):
    print(f"{valores[i]} ", end="")
print(f"\nO índice do menor valor é {indiceMenorValor} e seu valor é {valores[indiceMenorValor]}.")


# coding: utf-8

"""
    Container Security APIs

    All features of the Container Security are available through REST APIs.
Access support information at www.qualys.com/support/

Permissions:
User must have the Container module enabled
User must have API ACCESS permission  # noqa: E501

    The version of the OpenAPI document: 1.0.0
    Generated by: https://openapi-generator.tech
"""

import pprint
import re  # noqa: F401

import six

from qualys_cs_api.configuration import Configuration

class RegistryRepoResponse(object):
    """NOTE: This class is auto generated by OpenAPI Generator.
    Ref: https://openapi-generator.tech

    Do not edit the class manually.
    """

    """
    Attributes:
      openapi_types (dict): The key is attribute name
                            and the value is attribute type.
      attribute_map (dict): The key is attribute name
                            and the value is json key in definition.
    """
    openapi_types = {
        'repo_name': 'str',
        'total_images': 'int',
        'total_scanned_images': 'int',
        'total_vulnerable_images': 'int'
    }

    attribute_map = {
        'repo_name': 'repoName',
        'total_images': 'totalImages',
        'total_scanned_images': 'totalScannedImages',
        'total_vulnerable_images': 'totalVulnerableImages'
    }

    def __init__(self, repo_name=None, total_images=None, total_scanned_images=None, total_vulnerable_images=None, local_vars_configuration=None):  # noqa: E501
        """RegistryRepoResponse - a model defined in OpenAPI"""  # noqa: E501
        if local_vars_configuration is None:
            local_vars_configuration = Configuration()
        self.local_vars_configuration = local_vars_configuration

        self._repo_name = None
        self._total_images = None
        self._total_scanned_images = None
        self._total_vulnerable_images = None
        self.discriminator = None

        if repo_name is not None:
            self.repo_name = repo_name
        if total_images is not None:
            self.total_images = total_images
        if total_scanned_images is not None:
            self.total_scanned_images = total_scanned_images
        if total_vulnerable_images is not None:
            self.total_vulnerable_images = total_vulnerable_images

    @property
    def repo_name(self):
        """Gets the repo_name of this RegistryRepoResponse.  # noqa: E501

        :return: The repo_name of this RegistryRepoResponse.  # noqa: E501
        :rtype: str
        """
        return self._repo_name

    @repo_name.setter
    def repo_name(self, repo_name):
        """Sets the repo_name of this RegistryRepoResponse.

        :param repo_name: The repo_name of this RegistryRepoResponse.  # noqa: E501
        :type: str
        """

        self._repo_name = repo_name

    @property
    def total_images(self):
        """Gets the total_images of this RegistryRepoResponse.  # noqa: E501

        :return: The total_images of this RegistryRepoResponse.  # noqa: E501
        :rtype: int
        """
        return self._total_images

    @total_images.setter
    def total_images(self, total_images):
        """Sets the total_images of this RegistryRepoResponse.

        :param total_images: The total_images of this RegistryRepoResponse.  # noqa: E501
        :type: int
        """

        self._total_images = total_images

    @property
    def total_scanned_images(self):
        """Gets the total_scanned_images of this RegistryRepoResponse.  # noqa: E501

        :return: The total_scanned_images of this RegistryRepoResponse.  # noqa: E501
        :rtype: int
        """
        return self._total_scanned_images

    @total_scanned_images.setter
    def total_scanned_images(self, total_scanned_images):
        """Sets the total_scanned_images of this RegistryRepoResponse.

        :param total_scanned_images: The total_scanned_images of this RegistryRepoResponse.  # noqa: E501
        :type: int
        """

        self._total_scanned_images = total_scanned_images

    @property
    def total_vulnerable_images(self):
        """Gets the total_vulnerable_images of this RegistryRepoResponse.  # noqa: E501

        :return: The total_vulnerable_images of this RegistryRepoResponse.  # noqa: E501
        :rtype: int
        """
        return self._total_vulnerable_images

    @total_vulnerable_images.setter
    def total_vulnerable_images(self, total_vulnerable_images):
        """Sets the total_vulnerable_images of this RegistryRepoResponse.

        :param total_vulnerable_images: The total_vulnerable_images of this RegistryRepoResponse.  # noqa: E501
        :type: int
        """

        self._total_vulnerable_images = total_vulnerable_images

    def to_dict(self):
        """Returns the model properties as a dict"""
        result = {}

        for attr, _ in six.iteritems(self.openapi_types):
            value = getattr(self, attr)
            if isinstance(value, list):
                result[attr] = list(map(
                    lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
                    value
                ))
            elif hasattr(value, "to_dict"):
                result[attr] = value.to_dict()
            elif isinstance(value, dict):
                result[attr] = dict(map(
                    lambda item: (item[0], item[1].to_dict())
                    if hasattr(item[1], "to_dict") else item,
                    value.items()
                ))
            else:
                result[attr] = value

        return result

    def to_str(self):
        """Returns the string representation of the model"""
        return pprint.pformat(self.to_dict())

    def __repr__(self):
        """For `print` and `pprint`"""
        return self.to_str()

    def __eq__(self, other):
        """Returns true if both objects are equal"""
        if not isinstance(other, RegistryRepoResponse):
            return False

        return self.to_dict() == other.to_dict()

    def __ne__(self, other):
        """Returns true if both objects are not equal"""
        if not isinstance(other, RegistryRepoResponse):
            return True

        return self.to_dict() != other.to_dict()


# coding=utf-8
""" spectrum_in.py describes the input spectrum of OLE, i.e. spectrum.
    spectrum is a dictionary containing two fields:
        laser_position: a list of bool indicating if a laser is turned on or not
        signals: a list of dictionaries each of them, describing one channel in the WDM comb

        The laser_position is defined respect to a frequency grid of 6.25 GHz space and the first slot is at the
        frequency described by the variable f0 in the dictionary sys_param in the file "general_parameters.py"

        Each dictionary element of the list 'signals' describes the profile of a WDM channel:
            b_ch: the -3 dB channel bandwidth (for a root raised cosine, it is equal to the symbol rate)
            roll_off: the roll off parameter of the root raised cosine shape
            p_ch: the channel power [W]
            p_nli: power of accumulated NLI in b_ch [W]
            p_ase: power of accumulated ASE noise in b_ch [W]
"""

n_ch = 41

spectrum = {
    'laser_position': [0, 0, 0, 1, 0, 0, 0, 0] * n_ch,
    'signals': [{
        'b_ch': 0.032,
        'roll_off': 0.15,
        'p_ch': 1E-3,
        'p_nli': 0,
        'p_ase': 0
        } for _ in range(n_ch)]
}


import numpy as np
from scipy.signal import firwin2, filtfilt

from .fft import rfftfreq, rfft, irfft
from .utils import _npads, _smart_pad, _trim

def _apply_notches(X, notches, rate, fft=True):
    """Low-level code which applies notch filters.

    Parameters
    ----------
    X : ndarray, (n_time, n_channels)
        Input data.
    notches : ndarray
        Frequencies to notch filter.
    rate : float
        Number of samples per second for X.
    fft : bool
        Whether to filter in the time or frequency domain.

    Returns
    -------
    Xp : ndarray, (n_time, n_channels)
        Notch filtered data.
    """
    delta = 1.
    if fft:
        fs = rfftfreq(X.shape[0], 1. / rate)
        fd = rfft(X, axis=0)
    else:
        nyquist = rate / 2.
        n_taps = 1001
        gain = [1, 1, 0, 0, 1, 1]
    for notch in notches:
        if fft:
            window_mask = np.logical_and(fs > notch - delta, fs < notch + delta)
            window_size = window_mask.sum()
            window = np.hamming(window_size)
            fd[window_mask] = fd[window_mask] * (1. - window)[:, np.newaxis]
        else:
            freq = np.array([0, notch - delta, notch - delta / 2.,
                             notch + delta / 2., notch + delta, nyquist]) / nyquist
            filt = firwin2(n_taps, freq, gain)
            Xp = filtfilt(filt, np.array([1]), X, axis=0)
    if fft:
        Xp = irfft(fd, n=X.shape[0], axis=0)
    return Xp

def apply_linenoise_notch(X, rate, fft=True):
    """Apply notch filters at 60 Hz and its harmonics.

    Filters +/- 1 Hz around the frequencies.

    Parameters
    ----------
    X : ndarray, (n_time, n_channels)
        Input data.
    rate : float
        Number of samples per second for X.
    fft : bool
        Whether to filter in the time or frequency domain.

    Returns
    -------
    Xp : ndarray, (n_time, n_channels)
        Notch filtered data.
    """

    nyquist = rate / 2.
    noise_hz = 60.
    npad = rate
    if nyquist < noise_hz:
        return X
    notches = np.arange(noise_hz, nyquist, noise_hz)
    npads, to_removes, _ = _npads(X, npad)
    X = _smart_pad(X, npads)

    Xp = _apply_notches(X, notches, rate, fft=fft)
    Xp = _trim(Xp, to_removes)
    return Xp


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

import cPickle
from utils import read_probes, read_links, read_link_ids, read_link_lengths, read_probe_ids, read_probe_times

def main():
    probe_file = 'data/probe_points.csv'
    link_file = 'data/links.csv'

    probes = read_probes(probe_file)
    probe_ids = read_probe_ids(probe_file)
    probe_times = read_probe_times(probe_file)

    links = read_links(link_file)
    link_ids = read_link_ids(link_file)
    link_lengths = read_link_lengths(link_file)

    with open("data/probes.pkl", "wb") as fout:
        cPickle.dump(probes, fout, 2)
    with open("data/probe_ids.pkl", "wb") as fout:
        cPickle.dump(probe_ids, fout, 2)
    with open("data/probe_times.pkl", "wb") as fout:
        cPickle.dump(probe_times, fout, 2)

    with open("data/links.pkl", "wb") as fout:
        cPickle.dump(links, fout, 2)
    with open("data/link_ids.pkl", "wb") as fout:
    	cPickle.dump(link_ids, fout, 2)
    with open("data/link_lengths.pkl", "wb") as fout:
    	cPickle.dump(link_lengths, fout, 2)

if __name__ == "__main__":
    main()


"""
https://leetcode.com/problems/minimum-length-of-string-after-deleting-similar-ends/
"""

class Solution:
    def minimumLength(self, s: str) -> int:
        N = len(s)
        if N == 1:
            return 1

        l, r = 0, N - 1
        while l < r and s[l] == s[r]:
            while l + 1 < r and s[l + 1] == s[l]:
                l += 1

            while r - 1 > l and s[r - 1] == s[r]:
                r -= 1

            l += 1
            r -= 1

        return r - l + 1

print(Solution().minimumLength('aabccabba'))


import datetime
from tqdm import tqdm
import time

def get_date():
    date = datetime.datetime.now()
    date = str(date.strftime('%d/%m/%Y'))
    return date

"""
total students roll numbers
can keep list of strings as roll numbers can also have alphabets
Example: 18H51A05A0
"""
master_data = [x for x in range(1, 11)]

# Welcome message
print("\t\t\t     Welcome Back AbhisheK!!!\n\n\t\t\t\tHave A Great Day.\n")
time.sleep(1)

"""
Added loading to make it look nice (optional)
"""
print('Loading Attendance Program.........\n')
for i in tqdm(range(int(30))):
    time.sleep(0.1)

print("\nEnter Roll Numbers Space Seperated In Same Line Everytime:")
roll_numbers = list(map(int, input().split(' ')))
# wrong stores wrong roll numbers entered, human error
present, wrong = [], []
absent = master_data.copy()

for roll in roll_numbers:
    if roll in absent:
        absent.remove(roll)
        present.append(roll)
    else:
        wrong.append(roll)

print("\nToday's Date: ", get_date())
print('Total Students: ', len(master_data))

print('Present Students: ', len(present))
present.sort()
for i in present:
    print(i)
print('\nAbsent Students: ', len(absent))
absent.sort()
for i in absent:
    print(i)
print('\nWrong or Re-Entered Roll Numbers Entered: ', len(wrong))
wrong.sort()
for i in wrong:
    print(i)
"""
input in single line, as it makes easy to delete few roll numbers while entering input also
Input: 1 2 3 3 30
"""


import os
import sys
import numpy as np

import torch
from torch import nn
import torch.nn.functional as F

class Cell(nn.Module):
    def __init__(self, in_dim, hidden_dim, out_dim, activation_function=torch.tanh,
                 activation_function_out=F.relu, state_initializer=None):
        super(Cell, self).__init__()
        self.in_dim = in_dim
        self.state_size = hidden_dim
        self.hidden_dim = hidden_dim
        self.out_dim = out_dim
        self.act_fun = activation_function
        self.act_fun_out = activation_function_out
        self.state_init = state_initializer
        self.a = None
        self.init_state()  # state

    def update_state(self, s):
        self.a = s

    def init_state(self, initializer=None):
        if self.state_init is not None:
            if callable(self.state_init):
                self.a = self.state_init(1, self.hidden_dim)
                assert self.hidden_dim in self.a.shape
            if isinstance(self.state_init, int):
                self.a = torch.zeros(1, self.hidden_dim) + initializer
        else:
            self.a = torch.zeros(1, self.hidden_dim)
        self.a.requires_grad = False

    def reset_state(self):
        self.init_state()

# Simple Recurrent Cell
class RNN(Cell):
    def __init__(self, in_dim, hidden_dim, out_dim, activation_function=F.tanh,
                 activation_function_out=F.relu, state_initializer=None):
        # in_dim = number of featurse for one element of time series
        super(RNN, self).__init__(in_dim, hidden_dim, out_dim, activation_function=activation_function,
                 activation_function_out=activation_function_out, state_initializer=state_initializer)
        self.w_in = nn.Linear(in_features=self.state_size+in_dim,
                              out_features=self.state_size)
        self.w_y = nn.Linear(self.state_size, out_dim)

    def forward(self, x, state=None):
        if state is None:
            state = self.a
        ax = self.w_in(torch.cat((state, x), 1))
        new_state = self.act_fun(ax)
        y = self.w_y(new_state)  # ? already use the new a here?
        y = self.act_fun_out(y)
        return y, new_state

# GRU cell ???
class GRU(Cell):
    def __init__(self, in_dim, hidden_dim, out_dim, activation_function=F.tanh,
                 activation_function_out=F.relu, state_initializer=None):
        # in_dim = number of featurse for one element of time series
        super(GRU, self).__init__(in_dim, hidden_dim, out_dim, activation_function=activation_function,
                 activation_function_out=activation_function_out, state_initializer=state_initializer)

        comb_size = hidden_dim+in_dim
        self.w_in = nn.Linear(in_features=comb_size, out_features=comb_size )
        self.w_y = nn.Linear(hidden_dim, out_dim)
        self.act_fun = activation_function
        self.act_fun_out = activation_function_out
        self.gate_update = nn.Linear(in_features=comb_size , out_features=hidden_dim)
        self.gate_relevance = nn.Linear(in_features=comb_size, out_features=comb_size)

    def forward(self, x):
        ax = torch.cat((self.a, x))
        gr = torch.sigmoid(self.gate_relevance(ax))
        ar = torch.mul(gr, self.a)
        arx = torch.cat((ar, x))
        new_state = self.act_fun(self.w_in(arx))
        gu = torch.sigmoid(self.gate_update(ax))
        new_state = torch.mul(gu, new_state) + torch.mul((1-gu), self.a)
        y = self.w_y(new_state)
        y = self.act_fun_out(y)
        return y, new_state

# LSTM cell
class LSTM(Cell):
    def __init__(self, in_dim, hidden_dim, out_dim, activation_function=F.tanh,
                 activation_function_out=F.relu, state_initializer=None):
        super(LSTM, self).__init__(in_dim, hidden_dim, out_dim, activation_function=activation_function,
                 activation_function_out=activation_function_out, state_initializer=state_initializer)
        raise NotImplementedError('LSMT not implemented')


# -*- coding=utf-8 -*-

import logging
import json

from applications.message import helper
from applications.message.const import ERR_MSG_CONNECT_MSG_CENTER_FAIL, ERR_CODE_CONNECT_MSG_CENTER_FAIL, ERR_MSG_WRONG_MSG_FORMAT, ERR_CODE_WRONG_MSG_FORMAT
from applications.message.helper import _remote_call

logger = logging.getLogger(__name__)

def send_msg(recipients, category, content, api_version='1'):
    """
        向消息中心发送消息
        suggest: use celery task to send msg
    """
    if not category or not content:
        logger.error('try to send message but without category or content')
        return helper.response200({'c': ERR_CODE_WRONG_MSG_FORMAT, 'm': ERR_MSG_WRONG_MSG_FORMAT})
    payload = {
        'function_name': 'create_msg',
        'parameter': json.dumps({
            'api_version': api_version,
            'recipients': recipients,   # format: account_id,user_type,school_id, e.g. 2,2,1;2,2,1;2,2,1
            'category_code': category,
            'content': json.dumps(content, ensure_ascii=False),
        }, ensure_ascii=False)
    }
    try:
        logger.info('send message to message_center:')
        logger.info(payload)
        remote_response = _remote_call(payload)
    except Exception as e:
        logger.exception(e)
        return helper.response200({'c': ERR_CODE_CONNECT_MSG_CENTER_FAIL, 'm': ERR_MSG_CONNECT_MSG_CENTER_FAIL})
    logger.info('message_center return response:')
    logger.info(remote_response)
    # remote_response  e.g:
    # {'tracking_num': str(tracking.id)}
    return remote_response


# -*- coding: utf-8 -*-
"""
Created on Mon Oct 28 21:11:38 2019

@author: Paulo
"""

from PIL import Image
from nightmares.images_defines import GDOT_SAVE_FOLDER
from nightmares.utils import mkdirs
from os.path import join, isdir
from os import listdir as ls
import tqdm

OUTPUT_FOLDER = "C:/Users/Paulo/Desktop/Python shit/GarfieldNightmares/outputpng"

def convert_to(source=GDOT_SAVE_FOLDER, dest=OUTPUT_FOLDER, ext="png"):
    """ Convert .gif images from GDOT to .png using PIL.
        Arguments:
            source: path to the folder where the /year/month directories are.
            dest: destination/save path.
            ext: extension to save. See PIL.Image for supported extensions.
    """
    for year_folder in tqdm.tqdm(ls(source)):
        mkdirs(dest,[year_folder])
        year_save_folder = join(dest, year_folder)
        year_folder = join(source, year_folder)
        if not isdir(year_folder): continue
        for month_folder in ls(year_folder):
            mkdirs(year_save_folder,[month_folder])
            month_save_folder = join(year_save_folder, month_folder)
            month_folder = join(year_folder, month_folder)
            if not isdir(month_folder): continue
            images = ls(month_folder)
            for image in images:
                image_filepath = join(month_folder, image)
                with Image.open(image_filepath) as img:
                    image_save_path = join(month_save_folder, image.split(".")[0] + "." + ext)
                    img.save(image_save_path)

if __name__ == "__main__":
    convert_to()


with open("./input.txt") as file:
    data = file.read().splitlines()

commands = []
for line in data:
    if line.startswith("on"):
        x, y, z = line.removeprefix("on ").split(",")
        val = True
    else:
        x, y, z = line.removeprefix("off ").split(",")
        val = False
    x = x[2:].split("..")
    y = y[2:].split("..")
    z = z[2:].split("..")
    bounds = (*map(int, x), *map(int, y), *map(int, z))
    commands.append((bounds, val))

grid = {}
for (x_min, x_max, y_min, y_max, z_min, z_max), val in commands:
    for x in range(max(x_min, -50), min(x_max, 50) + 1):
        for y in range(max(y_min, -50), min(y_max, 50) + 1):
            for z in range(max(z_min, -50), min(z_max, 50) + 1):
                grid[(x, y, z)] = val
print(sum(grid.values()))


import struct
from io import BytesIO

structure = {
    'source_port': ('>H', 2),
    'destination_port': ('>H', 2),
    'sequence_number': ('>I', 4),
    'acknowledgment_number': ('>I', 4),
    'data_offset': ('>H', 2),  # read two bytes and fill flags structure ad well
    'window_size': ('>H', 2),
    'checksum': ('>H', 2),
    'urgent_pointer': ('>H', 2)
}

class TCPDecoder:
    def __init__(self) -> None:
        self._stream = BytesIO()
        self.res = {}

    def _unpack(self, fmt, num):
        return struct.unpack(fmt, self._stream.read(num))[0]

    def get_source_port(self):
        fmt, num = structure['source_port']
        return self._unpack(fmt, num)

    def get_destination_port(self):
        fmt, num = structure['destination_port']
        return self._unpack(fmt, num)

    def get_sequence_number(self):
        fmt, num = structure['sequence_number']
        return self._unpack(fmt, num)

    def get_acknowledgment_number(self):
        fmt, num = structure['acknowledgment_number']
        return self._unpack(fmt, num)

    @staticmethod
    def get_decoded_flags(flags: str):
        return {
            'NS': flags[0],
            'CWR': flags[1],
            'ECE': flags[2],
            'URG': flags[3],
            'ACK': flags[4],
            'PSH': flags[5],
            'RST': flags[6],
            'SYN': flags[7],
            'FIN': flags[8]
        }

    def get_data_offset(self):
        # header length consists of 4 bits
        fmt, num = structure['data_offset']
        _unpacked = self._unpack(fmt, num)

        data = format(_unpacked, 'b')
        self.res['data_offset'] = int(data[:4], 2)
        self.res['reserved'] = data[4:7]

        flags = data[7:]

        self.res['flags'] = self.get_decoded_flags(flags)

    def get_window_size(self):
        fmt, num = structure['window_size']
        return self._unpack(fmt, num)

    def get_checksum(self):
        fmt, num = structure['checksum']
        return hex(self._unpack(fmt, num))

    def get_urgent_pointer(self):
        fmt, num = structure['urgent_pointer']
        return hex(self._unpack(fmt, num))

    def get_options(self):
        # todo: implementation
        # The length of this field is determined by the data offset field.
        # Depending on Option-Kind value, the next two fields may be set.
        options = {
            'option_kind': None,  # required
            'option_length': None,
            'option_data': None
        }
        return options

    def decode(self, row_data: bytes):
        self._stream.write(row_data)
        self._stream.seek(0)

        self.res = dict(
            source_port=self.get_source_port(),
            destination_port=self.get_destination_port(),
            sequence_number=self.get_sequence_number(),
            acknowledgment_number=self.get_acknowledgment_number(),
        )
        self.get_data_offset()
        self.res['window_size'] = self.get_window_size()
        self.res['checksum'] = self.get_checksum()
        self.res['urgent_pointer'] = self.get_urgent_pointer()
        self.res['options'] = self.get_options()
        return self.res

tcp_decoder = TCPDecoder()
with open('data.raw', 'rb') as f:
    content = f.read()

print(tcp_decoder.decode(content))


from losses import Loss
from layers import Layer
from tqdm import trange

class Network:
    def __init__(self):
        self.layers = []
        self.loss = None
        self.loss_prime = None
        self.err_log = []
        self.err_log_val = []

    # add layer to network
    def add(self, layer: Layer):
        self.layers.append(layer)

    # set loss to use
    def use(self, loss: Loss):
        self.loss = loss

    # predict output for given input
    def predict(self, input_data):
        # sample dimension first
        samples = len(input_data)
        result = []

        # run network over all samples
        for i in range(samples):
            # forward propagation
            output = input_data[i]
            for layer in self.layers:
                output = layer.forward_propagation(output)
            result.append(output)

        return result

    # train the network

    def fit(self, x_train, y_train, epochs=5, learning_rate=0.1):
        """
        Fit function does the training.
        Training data is passed 1-by-1 through the network layers during forward propagation.
        Loss (error) is calculated for each input and back propagation is performed via partial
        derivatives on each layer.
        """
        # sample dimension first
        samples = len(x_train)
        # samples_val = len(x_val)
        t = trange(epochs, desc="Error | Epoch", leave=True)

        # training loop
        for i in t:
            err = 0
            for j in range(samples):
                # forward propagation
                output = x_train[j]
                for layer in self.layers:
                    output = layer.forward_propagation(output)

                # compute loss (for display purpose only)
                err += self.loss(y_train[j], output)

                # backward propagation
                error = self.loss(y_train[j], output, derivative=True)
                for layer in reversed(self.layers):
                    error = layer.backward_propagation(error, learning_rate)

            # err_val = 0
            # for j in range(samples_val):
            #     # forward propagation
            #     output = x_val[j]
            #     for layer in self.layers:
            #         output = layer.forward_propagation(output)

            #     # compute loss (for display purpose only)
            #     err_val += self.loss(y_val[j], output)

            # calculate average error on all samples
            err /= samples
            # err_val /= samples_val

            self.err_log.append(err)
            # self.err_log_val.append(err_val)
            t.set_description(
                f"Epoch: {i} | Train loss: {err:.4}",
                refresh=True,
            )


import time
import warnings
import numpy as np
import numba as nb
import pandas as pd
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from sklearn.metrics import mean_squared_error as mse_func, mean_absolute_error as mae_func
from citylearnutils.utils import seed_everything

N_DAYS = 8
N_TARGETS = 10
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"

DATA_PATH = "./data"
FORECAST_PATH = f"{DATA_PATH}/external/forecasts/demand"
MODEL_PATH = f"{DATA_PATH}/citylearn_challenge_2022_phase_1/models"

DEMAND_MODEl_PARAMS = {
    "training": {
        "batch_size": 256,
        "n_epochs": 25,
        "grad_clip": 0.1,
        "seed": 0
    },
    "scheduler": {
        "max_lr": 1.54e-05,
        "pct_start": 0.2,
        "div_factor": 1,
        "final_div_factor": 10000
    },
    "optimizer": {
        "weight_decay": 1e-07
    },
    "model": {
        "n_features": 282,
        "n_targets": 10,
        "n_hidden": 926,
        "dropout": 0.1,
        "emb_dim": 2
    }
}

@nb.njit()
def _uniform_update(x_new, value, count):
    return value + (x_new - value) / (count + 1)

@nb.njit()
def _average_update(x_new, value, baseline, count, decays):
    blend = value + (baseline - value) * (1 - decays) ** count
    return blend + decays * (x_new - blend)

class ExponentialSmoother():

    def __init__(self, daily_baseline, weekly_baseline):
        self.decays = np.array([0.125, 0.25, 0.5], dtype=np.float32)
        n_decays = len(self.decays)

        self.daily_x = np.repeat(daily_baseline, n_decays).reshape(24, -1)  # average
        self.daily_b = np.zeros((24, 1), np.float32)  # baseline
        self.daily_n = np.zeros(24, np.int32)  # count

        self.weekly_x = np.repeat(weekly_baseline, n_decays).reshape(168, -1)  # average
        self.weekly_b = np.zeros((168, 1), np.float32)  # baseline
        self.weekly_n = np.zeros(168, np.int32)  # count

        self.time_index = 0

    def fit_transform(self, x, n_lags=5, n_leads=N_TARGETS):
        # update daily state
        i = self.time_index % 24
        self.daily_b[i] = _uniform_update(x, self.daily_b[i], self.daily_n[i])
        self.daily_x[i] = _average_update(x, self.daily_x[i], self.daily_b[i], self.daily_n[i], self.decays)
        self.daily_n[i] += 1

        # update weekly state
        j = self.time_index % 168
        self.weekly_b[j] = _uniform_update(x, self.weekly_b[j], self.weekly_n[j])
        self.weekly_x[j] = _average_update(x, self.weekly_x[j], self.weekly_b[j], self.weekly_n[j], self.decays)
        self.weekly_n[j] += 1

        # extract current values
        i = np.arange(self.time_index - n_lags, self.time_index + n_leads) % 24
        j = np.arange(self.time_index - n_lags, self.time_index + n_leads) % 168
        y = np.append(self.daily_x[i], self.weekly_x[j])

        # update time
        self.time_index += 1

        return y

class DemandDataset(Dataset):

    def __init__(self, time_of_day, day_of_week, features, targets, device):
        super(DemandDataset, self).__init__()
        self.time_of_day = torch.tensor(time_of_day, dtype=torch.long, device=device)
        self.day_of_week = torch.tensor(day_of_week, dtype=torch.long, device=device)
        self.features = torch.tensor(features, dtype=torch.float32, device=device)
        self.targets = torch.tensor(targets, dtype=torch.float32, device=device)
        self.device = device

    def __len__(self):
        return len(self.targets)

    def __getitem__(self, index):
        time_of_day = self.time_of_day[index]
        day_of_week = self.day_of_week[index]
        features = self.features[index]
        targets = self.targets[index]
        return time_of_day, day_of_week, features, targets

class SkipMLP(nn.Module):

    def __init__(self, n_in, n_hidden, dropout=0.1):
        super(SkipMLP, self).__init__()
        self.linear1 = nn.Linear(n_in, n_hidden)
        self.linear2 = nn.Linear(n_hidden, n_in)
        self.activation = nn.ReLU()
        self.dropout = nn.Dropout(dropout)

    def forward(self, x0):
        x = self.activation(self.linear1(x0))
        x = self.linear2(self.dropout(x))
        return x + x0

class MLP(nn.Module):

    def __init__(self, n_in, n_hidden, n_out, dropout=0.1):
        super(MLP, self).__init__()
        self.linear1 = nn.Linear(n_in, n_hidden)
        self.linear2 = nn.Linear(n_hidden, n_out)
        self.activation = nn.ReLU()
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        x = self.activation(self.linear1(x))
        x = self.linear2(self.dropout(x))
        return x

class DemandModel(nn.Module):

    def __init__(self, n_features, n_targets, n_hidden, dropout, emb_dim):
        super(DemandModel, self).__init__()
        self.tod_embedding = nn.Embedding(24, emb_dim)
        self.dow_embedding = nn.Embedding(24, emb_dim)
        self.mlp1 = MLP(n_features + 2 * emb_dim, n_hidden, n_hidden, dropout)
        self.mlp2 = SkipMLP(n_hidden, n_hidden, dropout)
        self.mlp3 = MLP(n_hidden, n_hidden, n_targets, dropout)

    def forward(self, time_of_day, day_of_week, features):
        tod = self.tod_embedding(time_of_day)
        dow = self.dow_embedding(day_of_week)
        x = self.mlp1(torch.cat([tod, dow, features], 1))
        x = self.mlp2(x)
        x = self.mlp3(x)
        return x.clamp_min(0)

if __name__ == "__main__":

    # preprocess data
    with warnings.catch_warnings():
        warnings.simplefilter(action="ignore", category=pd.errors.PerformanceWarning)

        # load data
        data = pd.read_csv(f"{DATA_PATH}/external/observations.csv")

        # create baseline forecast
        data["time_of_day"] = np.tile(np.arange(24), 365 * 5)
        data["day_of_week"] = np.concatenate([np.tile(np.repeat(np.arange(7), 24), 53)[:8760]] * 5)

        daily_baseline = data.groupby(["time_of_day"]).non_shiftable_load.mean().values
        weekly_baseline = data.groupby(["day_of_week", "time_of_day"]).non_shiftable_load.mean().values

        # lags
        lags = np.arange(N_DAYS * 24)
        lag_cols = [f"lag{i}" for i in lags]

        for building_num, df in data.groupby("building_num"):

            df_weekly_baseline = df.groupby(["day_of_week", "time_of_day"]).non_shiftable_load.mean().values
            df_weekly_baseline = (5 * weekly_baseline - df_weekly_baseline) / 4

            for i in lags:
                x = np.append(df.non_shiftable_load, np.tile(df_weekly_baseline, 5))
                data.loc[df.index, f"lag{i}"] = np.roll(x, i)[:len(df)]

        # exponential smoothing
        es_cols = [f"es{i}" for i in range(6 * 5 + 6 * N_TARGETS)]

        for building_num, df in data.groupby("building_num"):
            df_daily_baseline = df.groupby(["time_of_day"]).non_shiftable_load.mean().values
            df_daily_baseline = (5 * daily_baseline - df_daily_baseline) / 4

            df_weekly_baseline = df.groupby(["day_of_week", "time_of_day"]).non_shiftable_load.mean().values
            df_weekly_baseline = (5 * weekly_baseline - df_weekly_baseline) / 4

            es = ExponentialSmoother(df_daily_baseline, df_weekly_baseline)
            features = [es.fit_transform(x) for x in df.non_shiftable_load.values]
            data.loc[df.index, es_cols] = np.vstack(features)

        # targets
        leads = 1 + np.arange(N_TARGETS)
        target_cols = [f"target{i}" for i in leads]

        for building_num, df in data.groupby("building_num"):
            for i in leads:
                x = np.roll(df.non_shiftable_load, -i)
                x = np.append(x[:-N_TARGETS], weekly_baseline[:N_TARGETS])
                data.loc[df.index, f"target{i}"] = x

        # try to defragment dataframe
        feature_cols = lag_cols + es_cols
        data = data.dropna().reset_index(drop=True).copy()
        assert len(data) == 5 * 8760

    # train model and create out-of-fold predictions
    for seed in range(5):
        for drop_building_num in [1, 2, 3, 4, 5, None]:

            eval_start_time = time.time()
            seed_everything(seed)

            # create loaders
            if drop_building_num is None:

                trn_loader = DataLoader(
                    DemandDataset(
                        time_of_day=data.time_of_day.values,
                        day_of_week=data.day_of_week.values,
                        features=data[feature_cols].values,
                        targets=data[target_cols].values,
                        device=DEVICE
                    ),
                    batch_size=DEMAND_MODEl_PARAMS["training"]["batch_size"],
                    shuffle=True
                )

                val_loader = DataLoader(
                    DemandDataset(
                        time_of_day=data.time_of_day.values,
                        day_of_week=data.day_of_week.values,
                        features=data[feature_cols].values,
                        targets=data[target_cols].values,
                        device=DEVICE),
                    batch_size=DEMAND_MODEl_PARAMS["training"]["batch_size"],
                    shuffle=False
                )

            else:

                trn_loader = DataLoader(
                    DemandDataset(
                        time_of_day=data.loc[data.building_num != drop_building_num, "time_of_day"].values,
                        day_of_week=data.loc[data.building_num != drop_building_num, "day_of_week"].values,
                        features=data.loc[data.building_num != drop_building_num, feature_cols].values,
                        targets=data.loc[data.building_num != drop_building_num, target_cols].values,
                        device=DEVICE
                    ),
                    batch_size=DEMAND_MODEl_PARAMS["training"]["batch_size"],
                    shuffle=True
                )

                val_loader = DataLoader(
                    DemandDataset(
                        time_of_day=data.loc[data.building_num == drop_building_num, "time_of_day"].values,
                        day_of_week=data.loc[data.building_num == drop_building_num, "day_of_week"].values,
                        features=data.loc[data.building_num == drop_building_num, feature_cols].values,
                        targets=data.loc[data.building_num == drop_building_num, target_cols].values,
                        device=DEVICE),
                    batch_size=DEMAND_MODEl_PARAMS["training"]["batch_size"],
                    shuffle=False
                )

            # initialize model, optimizer, scheduler
            model = DemandModel(**DEMAND_MODEl_PARAMS["model"])
            model.to(DEVICE)

            optimizer = torch.optim.AdamW(model.parameters(), **DEMAND_MODEl_PARAMS["optimizer"])

            scheduler = torch.optim.lr_scheduler.OneCycleLR(
                optimizer,
                epochs=DEMAND_MODEl_PARAMS["training"]["n_epochs"],
                **DEMAND_MODEl_PARAMS["scheduler"],
                steps_per_epoch=len(trn_loader),
            )

            criterion = nn.MSELoss()

            metrics = []
            for epoch in range(DEMAND_MODEl_PARAMS["training"]["n_epochs"]):

                # training
                model.train()
                start_time = time.time()
                trn_targets, trn_preds = [], []
                for time_of_day, day_of_week, features, targets in trn_loader:

                    optimizer.zero_grad()
                    preds = model(time_of_day, day_of_week, features)

                    trn_targets.append(targets.detach().cpu().numpy())
                    trn_preds.append(preds.detach().cpu().numpy())

                    loss = criterion(preds, targets.to(DEVICE))
                    loss.backward(loss)

                    torch.nn.utils.clip_grad_norm_(model.parameters(), DEMAND_MODEl_PARAMS["training"]["grad_clip"])

                    optimizer.step()
                    scheduler.step()

                # Validation
                model.eval()
                val_targets, val_preds = [], []
                with torch.no_grad():
                    for time_of_day, day_of_week, features, targets in val_loader:
                        preds = model(time_of_day, day_of_week, features)
                        val_targets.append(targets.detach().cpu().numpy())
                        val_preds.append(preds.detach().cpu().numpy())

                # combine results
                trn_targets = np.concatenate(trn_targets)
                val_targets = np.concatenate(val_targets)

                trn_preds = np.concatenate(trn_preds)
                val_preds = np.concatenate(val_preds)

                # calculate metrics
                trn_rmse = np.sqrt(mse_func(trn_targets, trn_preds))
                val_rmse = np.sqrt(mse_func(val_targets, val_preds))

                trn_mae = mae_func(trn_targets, trn_preds)
                val_mae = mae_func(val_targets, val_preds)

                metrics.append(dict(trn_rmse=trn_rmse, trn_mae=trn_mae,
                                    val_rmse=val_rmse, val_mae=val_mae))

                print(f"drop {drop_building_num} - "
                      f"{epoch:02.0f} ({time.time() - start_time:0.1f}s): "
                      f"trn_rmse {trn_rmse:0.5f}  trn_mae {trn_mae:0.5f}  "
                      f"val_rmse {val_rmse:0.5f}  val_mae {val_mae:0.5f}")

            # save
            if drop_building_num is None:
                torch.save(model.state_dict(), f"{MODEL_PATH}/demand_model.{seed}.pt")
            else:
                np.save(f"{FORECAST_PATH}/pred_{drop_building_num}.{seed}", val_preds)
            print()

    # average predictions
    for building_num in range(5):
        preds = 0
        for seed in range(5):
            preds += np.load(f"{FORECAST_PATH}/pred_{building_num+1}.{seed}.npy")/5
        np.save(f"{FORECAST_PATH}/pred_{building_num+1}", preds)


# coding=utf-8
import requests
import json
# url = 'http://www.qq.com'
proxies = {'http': '118.31.220.3:8080'}
url = 'http://www.bisige8.com/forum.php'
params = {'x': '27835'}
headers = {'User-Agent': "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}
response = requests.get(url, params=params, proxies=proxies, headers=headers)

print(response.status_code)


import boto3
from pprint import pprint

aws_mag_console = boto3.session.Session(profile_name=None)
ec2_client = aws_mag_console.client(service_name="ec2", region_name="us-east-1")

response = ec2_client.describe_instances()['Reservations']

for each_item in response:
    for each in each_item['Instances']:
        print ("==============================================")
        print ("The image id is: {}\nThe Instanceid is: {}\nThe instance type is: {}\nThe launch time is: {}\n".format(each['ImageId'],each['InstanceId'],each['InstanceType'],each['LaunchTime']))


import numpy as np

def train_mlp(input_data, labels, n_epochs, bias=0.8, random_state=42, weights=None):
    """
    this is a manual, numpy implementation of the training process for a
    fully-connected network

    written for class homework: deep learning for genomics, CUNY
    Graduate Center Fall 2021
    ============================================================================

    input_data :: 2D np.ndarray, vertically-stacked input vectors

    labels :: 1D np.ndarray, ground-truth labels for error calculation

    n_epochs :: int > 0
    """
    # initialize weight matrices
    if weights is None:
        weights = []
        if random_state == 0:
            weights.append(np.zeros((3,2)))
            weights.append(np.zeros((2,1)))
        elif random_state == 1:
            weights.append(np.ones((3,2)))
            weights.append(np.ones((2,1)))
        elif random_state == 42:
            weights.append(np.array([
                [1.1,0.5],
                [0.1, -0.5],
                [1, 2]
            ]))
            weights.append(np.array([
                [0.2],
                [0.4]
            ]))

        else:
            weights.append(np.random.rand(3, 2))
            weights.append(np.random.rand(2, 1))

    else:
        assert isinstance(weights, np.ndarray)

    # add bias to each weight matrix
    for i, w in enumerate(weights):

        weights[i] = np.vstack((
            w,
            np.array([bias] * weights[i].shape[1]).reshape(1,-1)
        ))

    for j, w in enumerate(weights):
        print('initial weight matrix layer ' + str(j + 1) + ': ')
        print(w)
        print('--------')

    # construct sigmoid lambda function
    sigmoid_function = lambda z: 1 / (1 + (np.e ** (- z)))

    # initialize loss and data array for caching intermediate computations
    loss = None
    data = [input_data]

    for i in range(n_epochs):
        print('========\n========')
        print('epoch ' + str(i + 1))
        print('========\n========')

        # forward pass
        print('........\nforward pass\n........')
        inputs = input_data
        for idx, weight_matrix in enumerate(weights):
            inputs = np.hstack((
                inputs,
                np.ones((len(inputs), 1))
            ))
            inputs = np.matmul(inputs, weight_matrix)
            inputs = sigmoid_function(inputs)
            print('output of layer ' + str(idx) + ': ')
            print(inputs)

            # data.append(np.hstack((inputs, np.ones((len(inputs), 1)))))
            data.append(inputs)

        predictions = inputs

        print('predictions: \t' + str(predictions.ravel()))
        print('ground-truth labels: ' + str(labels))

        loss = np.dot((predictions.ravel() - labels), (predictions.ravel() - labels)) / 2
        print('halved mean-squared error: ' + str(loss))

        # backpropagate
        print('........\nbackward pass\n........')
        errors = [loss]
        grads = []
        reverse_data = list(reversed(data))
        for j, datum in enumerate(reverse_data[0:-1]):
            print('layer -' + str(j + 1))
            if j == 0:
                prev_datum = np.hstack((reverse_data[j + 1],
                                        np.ones((len(reverse_data[j + 1]), 1))))
                grad = np.matmul(prev_datum.T,
                                 (
                                     (labels.reshape(-1,1) - datum) *
                                        (datum * (1 - datum))))
                grad /= len(labels)
                print('gradient matrix for backwards layer ' + str(j) + ': ')
                print(grad)
                grads.append(grad)
            else:
                prev_datum = np.hstack((reverse_data[j + 1],
                                        np.ones((len(reverse_data[j + 1]), 1))))
                grad =  np.matmul(
                    prev_datum.T,
                    grads[j - 1] * datum * (1 - datum)
                )
                grad /= len(datum)
                print('gradient matrix for backwards layer ' + str(j) + ': ')
                print(grad)
                grads.append(grad)

        for j, (w, g) in enumerate(list(zip(weights, list(reversed(grads))))):
            weights[j] = weights[j] + g

        print('new weights: ')
        for j, w in enumerate(weights):
            print(j)
            print(w)
            print('========')
        data = [input_data]


import sys
from collections import deque

maximum = 100000
li = [0 for i in range(maximum+1)] # 이동거리를 저장하기 위한 배열
n, k = map(int,sys.stdin.readline().split())
queue = deque()
def bfs():
    queue.append(n) # 시작점을 queue 에 저장한다.
    while  queue:
        x = queue.popleft() # 이동거리를 x로 두고
        if x == k: # x가 도착점이랑 같아지면 출력
            print(li[x])
            break
        for i in (x - 1, x + 1, x * 2): # -1,+1,x2 하나씩 돌려보면서 queue에 넣는다.
            if 0 <= i <= maximum and not li[i]:
                li[i] = li[x] + 1 # 이동 거리를 하나씩 늘려서 저장한다.
                queue.append(i) # queue에 얼마나 이동했는지 그 거리를 저장한다.

bfs()


#! /usr/bin/env python3

'''
A module providing a socket server based server of the current date and
time in ISO 8601 format on port 8601.
'''

__author__ = 'Russel Winder'
__date__ = '2014-07-11'
__version__ = '1.2'
__copyright__ = 'Copyright © 2007, 2012, 2014  Russel Winder'
__licence__ = 'GNU Public Licence (GPL) v3'

import socketserver

import formatNow

class TimeHandler(socketserver.StreamRequestHandler):
    def handle(self):
        print('Connection from: ', self.client_address)
        self.wfile.write(formatNow.formatNow().encode())

if __name__ == '__main__':
    socketAddress = ('', 8601)
    server = socketserver.TCPServer(socketAddress, TimeHandler)
    print('Serving on {}:{}'.format(*socketAddress))
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print('Got a keyboard interrupt so terminating.')
        server.shutdown()  # Should be unecessary.


from math import acos, pi

import bmesh
import bpy

from .vgroup_utils import get_verts_in_vgroup

def get_max_dim_in_direction(obj, direction, vertex_group_name=None):
    '''Get the furthest point of the mesh in a specified direction'''
    # world matrix
    mat = obj.matrix_world
    far_distance = 0
    far_point = direction

    if vertex_group_name:
        vs = get_verts_in_vgroup(obj, vertex_group_name)
    else:
        vs = obj.data.vertices

    for v in vs:
        point = mat @ v.co
        temp = direction.dot(point)
        # new high?
        if far_distance < temp:
            far_distance = temp
            far_point = point
    return far_point

def select_vertices_bmesh(vids, bm, deselect_others=False):
    '''select vertices using the bmesh module'''
    for v in bm.verts:
        if v.index in vids:
            v.select = True
        elif deselect_others:
            v.select = False
    bm.select_flush(True)
    bm.select_flush(False)

def select_vertices(obj, vs=None, deselect_others=False) -> None:
    '''
    select vertices using the bmesh module
    @obj: the object that holds mesh data
    @vs : vert subset to select
    @deselect_others : deselect all other vertices
    '''
    if vs:
        vids = [v.index for v in vs]
    else:
        vids = [v.index for v in obj.data.vertices]
    if obj.mode == 'EDIT':
        bm = bmesh.from_edit_mesh(obj.data)
    else:
        bm = bmesh.new()
        bm.from_mesh(obj.data)
    select_vertices_bmesh(vids, bm, deselect_others)
    if obj.mode == 'EDIT':
        bmesh.update_edit_mesh(obj.data)
    else:
        bm.to_mesh(obj.data)
        bm.free()

def get_object_center(obj):
    '''Find the center of a mesh object using the outside cage.'''
    vcos = [obj.matrix_world @ v.co for v in obj.data.vertices]

    def findCenter(vcos):
        return (max(vcos) + min(vcos)) / 2

    x, y, z = [[v[i] for v in vcos] for i in range(3)]
    center = [findCenter(axis) for axis in [x, y, z]]
    return center

def is_inside_dot(target_pt_global, mesh_obj, tolerance=0.05):
    '''
    checks if a point is inside a surface, by the dot product method
    @target_pt_global : the point to check
    @mesh_obj : the mesh as surface
    @tolerance : a threshold as inside tolerance
    '''
    # Convert the point from global space to mesh local space
    target_pt_local = mesh_obj.matrix_world.inverted() @ target_pt_global

    # Find the nearest point on the mesh and the nearest face normal
    _, pt_closest, face_normal, _ = mesh_obj.closest_point_on_mesh(target_pt_local)

    # Get the target-closest pt vector
    target_closest_pt_vec = (pt_closest - target_pt_local).normalized()

    # Compute the dot product = |a||b|*cos(angle)
    dot_prod = target_closest_pt_vec.dot(face_normal)

    # Get the angle between the normal and the target-closest-pt vector (from the dot prod)
    angle = acos(min(max(dot_prod, -1), 1)) * 180 / pi

    # Allow for some rounding error
    inside = angle < 90 - tolerance

    return inside

class GeometryIslands:
    """
    Traces the graph of edges and verts to find the islands
    @verts : bmesh vertices
    @islands : list of connected vertex islands, i.e. surfaces
    """

    verts = []
    islands = []
    # wether selected or non selected islands should be searched

    def __init__(self, bmesh_verts):
        self.verts = bmesh_verts
        self.islands = self.make_islands(self.verts)

    def make_vert_paths(self, verts):
        # Init a set for each vertex
        result = {v: set() for v in verts}
        # Loop over vertices to store connected other vertices
        for v in verts:
            for e in v.link_edges:
                other = e.other_vert(v)
                result[v].add(other)
        return result

    def make_island(self, starting_vert, paths):
        # Initialize the island
        island = [starting_vert]
        # Initialize the current vertices to explore
        current = [starting_vert]
        follow = True
        while follow:
            # Get connected vertices that are still in the paths
            eligible = set([v for v in current if v in paths])
            if len(eligible) == 0:
                follow = False  # Stops if no more
            else:
                # Get the corresponding links
                next = [paths[i] for i in eligible]
                # Remove the previous from the paths
                for key in eligible:
                    island.append(key)
                    paths.pop(key)
                # Get the new links as new inputs
                current = set([vert for sub in next for vert in sub])
        return island

    def make_islands(self, bm_verts):
        paths = self.make_vert_paths(bm_verts)
        result = []
        found = True
        while found:
            try:
                # Get one input as long there is one
                vert = next(iter(paths.keys()))
                # Deplete the paths dictionary following this starting vertex
                result.append(self.make_island(vert, paths))
            except StopIteration:
                found = False
        return result

    def get_islands(self):
        return self.islands

    def get_island_count(self):
        return len(self.islands)

    def get_island_by_vertex_index(self, index):
        for island in self.islands:
            if any(v.index == index for v in island):
                return island
        # return next((x for x in self.islands if any(index == v.index for v in x)), None)

    def get_selected_islands(self):
        for island in self.islands:
            if any(v.select for v in island):
                yield island

    def select_linked(self):
        ''' Select all linked vertices for surfaces that have a partial selection'''
        for island in self.get_selected_islands():
            for v in island:
                v.select = True

class SelectionIslands(GeometryIslands):
    '''
    Traces the graph of edges and verts to find the islands
    @verts : bmesh vertices
    @selection_islands : the islands of adjacent selected vertices
    @non_selected_islands : the islands of adjacent non selected vertices
    '''

    def __init__(self, bmesh_verts, selection_state):
        self.verts = [v for v in bmesh_verts if v.select == selection_state]
        self.islands = self.make_islands(self.verts, selection_state)

    def make_vert_paths(self, verts, selection_state):
        # Init a set for each vertex
        result = {v: set() for v in verts}
        # Loop over vertices to store connected other vertices
        for v in verts:
            for e in v.link_edges:
                other = e.other_vert(v)
                if other.select == selection_state:
                    result[v].add(other)
        return result

    def make_islands(self, bm_verts, selection_state):
        paths = self.make_vert_paths(bm_verts, selection_state)
        result = []
        found = True
        while found:
            try:
                # Get one input as long there is one
                vert = next(iter(paths.keys()))
                # Deplete the paths dictionary following this starting vertex
                result.append(self.make_island(vert, paths))
            except StopIteration:
                found = False
        return result


"""According to the Wikipedia's article: "The Game of Life, also known simply as Life, is a cellular automaton devised
by the British mathematician John Horton Conway in 1970."

Given a board with m by n cells, each cell has an initial state live (1) or dead (0). Each cell interacts with its
eight neighbors (horizontal, vertical, diagonal) using the following four rules (taken from the above Wikipedia article):

Any live cell with fewer than two live neighbors dies, as if caused by under-population.
Any live cell with two or three live neighbors lives on to the next generation.
Any live cell with more than three live neighbors dies, as if by over-population..
Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
Write a function to compute the next state (after one update) of the board given its current state.
The next state is created by applying the above rules simultaneously to every cell in the current state,
where births and deaths occur simultaneously.
这个题目按理说应该是很简单的，难的是，in_place解决他，普通方法就是用m*n空间的矩阵去更新他
这个题目和对年龄进行排序有点相同点。可以枚举所有的可能
live+<2 live=die
live+2/3 live=live
live+>3 live=die
die+3 live=live
考虑 in place，对于原来的矩阵，用3表示第一种情况，4表示第二种情况，5表示第三种情况，而且3,4,5意味着上一轮是live，
用6表示第四种情况
这样的话就简单了"""

def gameoflife(board):
    if not board:
        return False
    l_row, l_col = len(board), len(board[0])
    for index_row, row in enumerate(board):
        for index_col, col in enumerate(row):
            live, die = 0, 0
            for (a, b) in [(a, b) for (a, b) in
                           [(index_row - 1, index_col - 1), (index_row - 1, index_col), (index_row - 1, index_col + 1)
                               , (index_row, index_col - 1), (index_row, index_col + 1), (index_row + 1, index_col - 1)
                               , (index_row + 1, index_col), (index_row + 1, index_col + 1)]
                           if 0 <= a < l_row and 0 <= b < l_col]:
                if board[a][b] in [1, 3, 4, 5]:
                    live += 1
                else:
                    die += 1
            if board[index_row][index_col]==1:
                if live<2:
                    board[index_row][index_col]=3
                elif live==2 or live==3:
                    board[index_row][index_col]=4
                else:
                    board[index_row][index_col]=5
            elif live==3:
                board[index_row][index_col]=6

    for index_row, row in enumerate(board):
        for index_col, col in enumerate(row):
            if board[index_row][index_col] in [4,6]:
                board[index_row][index_col]=1
            else:
                board[index_row][index_col]=0


"""
Бот для записи времени по покрасу через телеграм
необходимый функционал:
- записать время
- сохранить фотку в запись времени
- сохранить фотку в модель
- обновить запись времени
- удалить запись времени
"""
import io
import logging
import re

from asgiref.sync import sync_to_async
from django.db.models import Max
from django.utils import timezone
from django.core.files.images import ImageFile
from django.contrib.auth.models import User
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes, MessageHandler, filters

from home_api.private_settings import TOKEN
from work_in_progress.templatetags.wip_filters import duration
from work_in_progress.models import Model, ModelProgress, ModelImage, Artist

CHAT_PROGRESS_RECORDS = {}
CHAT_MODEL_RECORDS = {}
PARSE_MODE = 'MarkdownV2'

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.DEBUG
)

async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    try:
        await get_user(update.effective_chat.username)
    except Exception as ex:
        await context.bot.send_message(chat_id=update.effective_chat.id, text=ex.args[0])
        return
    await context.bot.send_message(chat_id=update.effective_chat.id, text="\n".join(HELP_TEXT), parse_mode=PARSE_MODE)

HELP_TEXT = [
    'Чтобы посмотреть помощь \- `/help`',
    'Чтобы вывести 5 последних моделей над которыми велась работа \- `/list_models`',
    'Чтобы вывести 5 последних записей о покрасе по модели над которой велась работа \- `/list_progress id-модели`',
    'Чтобы записать время \- `/progress id-модели время [описание]` ' +
    'например */progress 1 1ч15м покрасил пушку*',
    'Чтобы добавить фотографию к записанному времени \- `/progress_photo id-записи вемени` ' +
    'например */progress\_photo 219*',
    'Чтобы добавить фотографию к модели \- `/model_photo id-модели` например */model\_photo 100*'
]

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await context.bot.send_message(chat_id=update.effective_chat.id, text='\n'.join(HELP_TEXT), parse_mode=PARSE_MODE)

async def list_models_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Вывести 5 последних моделей над которыми шла работа
    :param update:
    :param context:
    :return:
    """
    try:
        user = await get_user(update.effective_chat.username)
    except Exception as ex:
        await context.bot.send_message(chat_id=update.effective_chat.id, text=ex.args[0])
        return

    models = await get_user_models(user)
    if len(models) == 0:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Нет ни одной модели")
        return

    text = []
    for model in models:
        text.append("%s - %s (%s)" % (model['id'], model['name'], model['status']))
    await context.bot.send_message(chat_id=update.effective_chat.id, text="\n".join(text))

async def list_progress_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Вывести записи 5 последних работ для модели по id
    :param update:
    :param context:
    :return:
    """
    if len(context.args) != 1:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Надо указать ид модели")
    model_id = int(context.args[0])
    try:
        user = await get_user(update.effective_chat.username)
    except Exception as ex:
        await context.bot.send_message(chat_id=update.effective_chat.id, text=ex.args[0])
        return

    try:
        model = await get_model(user, model_id)
    except Model.DoesNotExist:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Модель не надена")
        return

    progress_list = await get_model_progress_list(user, model)
    text = ["Работы по %s:" % model]
    for progress in progress_list:
        text.append("%s - %s %s |%s" % (progress['id'], progress['title'], progress['time'], progress['datetime']))
    await context.bot.send_message(chat_id=update.effective_chat.id, text="\n".join(text))

async def progress_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Записать время в работу над моделью
    ожидаемые аргументы в context:
    1 - ид модели
    2 - время в часах(десятичная часть через точку или запятую)
    3... все оставшиеся аргументы трактуются как описание
    :param update:
    :param context:
    :return:
    """
    if len(context.args) < 2:
        await context.bot.send_message(chat_id=update.effective_chat.id,
                                       text="Мало данных, нужно указать ид модели и время, описание опционально")
        return

    try:
        model_id = int(context.args[0])
        matcher = re.compile(r'((?P<hours>\d+)ч(ас)?\.?)?((?P<minutes>\d+)м(ин)?\.?)?')
        res = matcher.match(context.args[1])
        if len(res.groups()) > 0:
            track_time = float(res.group("hours") or 0) + (float(res.group("minutes") or 0)/60)
        else:
            track_time = float(context.args[1].replace(',', '.'))
    except ValueError:
        await context.bot.send_message(chat_id=update.effective_chat.id,
                                       text="Первый и второй параметры должны быть числами: " +
                                            "1 или 0.5 или 0,5 или 1ч15м или 1час20мин или 20мин или 1ч.30мин.")
        return
    user = await get_user(update.effective_chat.username)
    model = await get_model(user, model_id)
    message = ["Записал время *%s* часов для модели _%s \- %s_" % (duration(track_time), model.id, model.name)]
    title = ""
    if len(context.args) >= 3:
        title = " ".join(context.args[2:])
        message.append("C пояснением: _%s_" % title)

    try:
        model_progress_id = await record_model_progress(user, model_id, track_time, title)
    except Exception:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="При создании записи возникла ошибка")
        return
    message.append("Для прикрепления фотографии выполни: `/progress_photo %s`" % model_progress_id)
    await context.bot.send_message(chat_id=update.effective_chat.id, text="\n".join(message), parse_mode='MarkdownV2')

async def progress_photo_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Инициировать процесс добавления фотографии к записи прогресса по модели
    :param update:
    :param context:
    :return:
    """
    try:
        model_progress_id = int(context.args[0])
    except IndexError:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Нужно указать ид модели")
        return
    except ValueError:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="ид быть целым числом")
        return
    user = await get_user(update.effective_chat.username)
    try:
        model_progress = await get_model_progress(user, model_progress_id)
    except Exception:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Запись работы не найдена")
        return

    message = "Чтобы добавить картинку к записи времени %s %s для модели %s отправь фото в чат" % (
        model_progress['title'], model_progress['time'], model_progress['model_name']
    )
    CHAT_PROGRESS_RECORDS[update.effective_chat.id] = model_progress_id
    await context.bot.send_message(chat_id=update.effective_chat.id, text=message)

async def model_photo_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Инициировать процесс добавления фотографии к модели
    :param update:
    :param context:
    :return:
    """
    model_id = int(context.args[0])
    user = get_user(update.effective_chat.username)
    try:
        model = await get_model(user, model_id)
    except Exception:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Модель не найдена")
        return
    message = "Чтобы добавить картинку для модели %s отправь фото в чат" % model.name
    CHAT_MODEL_RECORDS[update.effective_chat.id] = model_id
    await context.bot.send_message(chat_id=update.effective_chat.id, text=message)

async def upload_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    Сохранение фото в базу
    :param update:
    :param context:
    :return:
    """
    is_progress_photo = update.effective_chat.id in CHAT_PROGRESS_RECORDS.keys()
    is_model_photo = update.effective_chat.id in CHAT_MODEL_RECORDS.keys()

    if not is_progress_photo and not is_model_photo:
        message = "Не достаточно контекста для сохранения фотографии. " \
                  "Выполни команды \"/progress_photo id\" или \"/model_photo id\""
        await context.bot.send_message(chat_id=update.effective_chat.id,
                                       text=message)
    if not update.message.document and not update.message.photo:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Непонятно")
        return

    model_progress_id = model_id = None

    if is_progress_photo:
        model_progress_id = CHAT_PROGRESS_RECORDS[update.effective_chat.id]
        del CHAT_PROGRESS_RECORDS[update.effective_chat.id]
    if is_model_photo:
        model_id = CHAT_MODEL_RECORDS[update.effective_chat.id]
        del CHAT_MODEL_RECORDS[update.effective_chat.id]

    bot = update.get_bot()
    buf = bytearray()
    file_id = None
    if update.message.document:
        document = update.message.document
        file_id = document.file_id
    if update.message.photo:
        count = len(update.message.photo)
        file_id = update.message.photo[count - 1].file_id

    if file_id is None:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Файл не найден")
        return

    file = await bot.get_file(file_id)
    file_name = update.effective_chat.username + "_" + file_id
    await file.download_as_bytearray(buf)
    image_file = ImageFile(io.BytesIO(buf), name=file_name)
    user = await get_user(update.effective_chat.username)
    try:
        await save_image_to_progress(user, model_id, model_progress_id, image_file)
    except Exception:
        await context.bot.send_message(chat_id=update.effective_chat.id, text="Ошибка сохранения картинки")
        return
    await context.bot.send_message(chat_id=update.effective_chat.id, text="Картиночка сохранена")

@sync_to_async
def get_user(telegram_name) -> User:
    """
    Получить пользователя по нику в телеге
    :param telegram_name:
    :return: пользователь
    """
    artists = Artist.objects.filter(telegram_name=telegram_name)
    if artists.count() != 1:
        raise Exception("Пользователь не найден")
    return artists.first().user

@sync_to_async
def get_user_models(user: User) -> list[dict]:
    """
    Получить список последних 5 моделей в работе
    :param user:
    :return:
    """
    models = Model.objects.annotate(
        last_record=Max('modelprogress__datetime')
    ).filter(user=user).order_by('-last_record')[:5]
    result = []
    for model in models:
        result.append({
            'id': model.id,
            'name': model.name,
            'status': model.get_status_display()
        })
    return result

@sync_to_async
def get_model(user: User, model_id: int) -> Model:
    """
    Получить модель по ид
    :param user:
    :param model_id:
    :return:
    """
    return Model.objects.get(id=model_id, user=user)

@sync_to_async
def get_model_progress(user: User, progress_id: int) -> dict:
    """
    Получить объект записи времени по ид
    :param user:
    :param progress_id:
    :return:
    """
    try:
        model_progress = ModelProgress.objects.get(id=progress_id, model__user=user)
    except ModelProgress.DoesNotExist:
        raise Exception("Запись не найдена")
    return {
        'id': model_progress.id,
        'model_id': model_progress.model.id,
        'title': model_progress.title,
        'time': duration(model_progress.time),
        'model_name': model_progress.model.name
    }

@sync_to_async
def get_model_progress_list(user: User, model: Model) -> list[dict]:
    progress_list = ModelProgress.objects.filter(model=model, model__user=user).order_by('-datetime')[:5]
    result = []
    for progress in progress_list:
        result.append({
            'id': progress.id,
            'title': progress.title,
            'time': duration(progress.time),
            'datetime': progress.datetime.strftime('%d-%m-%Y %H:%M:%S')
        })
    return result

@sync_to_async
def record_model_progress(user: User, model_id: int, time: int, title: str) -> int:
    """
    Запись времени работы над моделью
    :param user:
    :param model_id:
    :param time:
    :param title:
    :return:
    """
    model = Model.objects.filter(id=model_id, user=user).first()
    progress = ModelProgress(model=model,
                             status=model.status,
                             time=time,
                             title=title,
                             datetime=timezone.now())
    progress.save()
    return progress.id

@sync_to_async
def save_image_to_progress(user: User, model_id: int, model_progress_id: int, image_file):
    model = None
    model_progress = None
    if model_id:
        model = Model.objects.get(id=model_id, user=user)

    if model_progress_id:
        model_progress = ModelProgress.objects.get(id=model_progress_id, model__user=user)
        model = model_progress.model

    image = ModelImage(image=image_file, progress=model_progress, model=model)
    image.save()

def run_telebot():
    application = ApplicationBuilder().token(TOKEN).build()
    start_handler = CommandHandler('start', start_command)
    application.add_handler(start_handler)

    help_handler = CommandHandler('help', help_command)
    application.add_handler(help_handler)

    models_handler = CommandHandler('list_models', list_models_command)
    application.add_handler(models_handler)

    list_progress_handler = CommandHandler('list_progress', list_progress_command)
    application.add_handler(list_progress_handler)

    progress_handler = CommandHandler('progress', progress_command)
    application.add_handler(progress_handler)

    progress_photo_handler = CommandHandler('progress_photo', progress_photo_command)
    application.add_handler(progress_photo_handler)

    model_photo_handler = CommandHandler('model_photo', model_photo_command)
    application.add_handler(model_photo_handler)

    image_handler = MessageHandler(filters.PHOTO, upload_photo)  # | filters.Document.JPG | filters.Document.IMAGE,
    application.add_handler(image_handler)

    application.run_polling()

if __name__ == '__main__':
    run_telebot()