import copy
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
import pytest
from numpy.testing import (
    assert_allclose,
    assert_array_almost_equal,
    assert_array_equal,
    assert_equal,
)

from mne import pick_info, pick_types
from mne._fiff.constants import FIFF
from mne._fiff.meas_info import _empty_info
from mne.channels import (
    find_layout,
    make_eeg_layout,
    make_grid_layout,
    read_layout,
)
from mne.channels.layout import _box_size, _find_topomap_coords, generate_2d_layout
from mne.defaults import HEAD_SIZE_DEFAULT
from mne.io import read_info, read_raw_kit

io_dir = Path(__file__).parents[2] / "io"
fif_fname = io_dir / "tests" / "data" / "test_raw.fif"
lout_path = io_dir / "tests" / "data"
bti_dir = io_dir / "bti" / "tests" / "data"
fname_ctf_raw = io_dir / "tests" / "data" / "test_ctf_comp_raw.fif"
fname_kit_157 = io_dir / "kit" / "tests" / "data" / "test.sqd"
fname_kit_umd = io_dir / "kit" / "tests" / "data" / "test_umd-raw.sqd"

def _get_test_info():
    """Make test info."""
    test_info = _empty_info(1000)
    loc = np.array(
        [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], dtype=np.float32
    )
    test_info["chs"] = [
        {
            "cal": 1,
            "ch_name": "ICA 001",
            "coil_type": 0,
            "coord_frame": 0,
            "kind": 502,
            "loc": loc.copy(),
            "logno": 1,
            "range": 1.0,
            "scanno": 1,
            "unit": -1,
            "unit_mul": 0,
        },
        {
            "cal": 1,
            "ch_name": "ICA 002",
            "coil_type": 0,
            "coord_frame": 0,
            "kind": 502,
            "loc": loc.copy(),
            "logno": 2,
            "range": 1.0,
            "scanno": 2,
            "unit": -1,
            "unit_mul": 0,
        },
        {
            "cal": 0.002142000012099743,
            "ch_name": "EOG 061",
            "coil_type": 1,
            "coord_frame": 0,
            "kind": 202,
            "loc": loc.copy(),
            "logno": 61,
            "range": 1.0,
            "scanno": 376,
            "unit": 107,
            "unit_mul": 0,
        },
    ]
    test_info._unlocked = False
    test_info._update_redundant()
    test_info._check_consistency()
    return test_info

def test_io_layout_lout(tmp_path):
    """Test IO with .lout files."""
    layout = read_layout(fname="Vectorview-all", scale=False)
    layout.save(tmp_path / "foobar.lout", overwrite=True)
    layout_read = read_layout(
        fname=tmp_path / "foobar.lout",
        scale=False,
    )
    assert_array_almost_equal(layout.pos, layout_read.pos, decimal=2)
    assert layout.names == layout_read.names
    assert "<Layout |" in layout.__repr__()

def test_io_layout_lay(tmp_path):
    """Test IO with .lay files."""
    layout = read_layout(fname="CTF151", scale=False)
    layout.save(str(tmp_path / "foobar.lay"))
    layout_read = read_layout(fname=tmp_path / "foobar.lay", scale=False)
    assert_array_almost_equal(layout.pos, layout_read.pos, decimal=2)
    assert layout.names == layout_read.names

def test_find_topomap_coords():
    """Test mapping of coordinates in 3D space to 2D."""
    info = read_info(fif_fname)
    picks = pick_types(info, meg=False, eeg=True, eog=False, stim=False)

    # Remove extra digitization point, so EEG digitization points match up
    # with the EEG channels
    del info["dig"][85]

    # Use channel locations
    kwargs = dict(ignore_overlap=False, to_sphere=True, sphere=HEAD_SIZE_DEFAULT)
    l0 = _find_topomap_coords(info, picks, **kwargs)

    # Remove electrode position information, use digitization points from now
    # on.
    for ch in info["chs"]:
        ch["loc"].fill(np.nan)

    l1 = _find_topomap_coords(info, picks, **kwargs)
    assert_allclose(l1, l0, atol=1e-3)

    for z_pt in ((HEAD_SIZE_DEFAULT, 0.0, 0.0), (0.0, HEAD_SIZE_DEFAULT, 0.0)):
        info["dig"][-1]["r"] = np.array(z_pt)
        l1 = _find_topomap_coords(info, picks, **kwargs)
        assert_allclose(l1[-1], z_pt[:2], err_msg="Z=0 point moved", atol=1e-6)

    # Test plotting mag topomap without channel locations: it should fail
    mag_picks = pick_types(info, meg="mag")
    with pytest.raises(ValueError, match="Cannot determine location"):
        _find_topomap_coords(info, mag_picks, **kwargs)

    # Test function with too many EEG digitization points: it should fail
    info["dig"].append({"r": [1, 2, 3], "kind": FIFF.FIFFV_POINT_EEG})
    with pytest.raises(ValueError, match="Number of EEG digitization points"):
        _find_topomap_coords(info, picks, **kwargs)

    # Test function with too little EEG digitization points: it should fail
    info._unlocked = True
    info["dig"] = info["dig"][:-2]
    with pytest.raises(ValueError, match="Number of EEG digitization points"):
        _find_topomap_coords(info, picks, **kwargs)

    # Electrode positions must be unique
    info["dig"].append(info["dig"][-1])
    with pytest.raises(ValueError, match="overlapping positions"):
        _find_topomap_coords(info, picks, **kwargs)

    # Test function without EEG digitization points: it should fail
    info["dig"] = [d for d in info["dig"] if d["kind"] != FIFF.FIFFV_POINT_EEG]
    with pytest.raises(RuntimeError, match="Did not find any digitization"):
        _find_topomap_coords(info, picks, **kwargs)

    # Test function without any digitization points, it should fail
    info["dig"] = None
    with pytest.raises(RuntimeError, match="No digitization points found"):
        _find_topomap_coords(info, picks, **kwargs)
    info["dig"] = []
    with pytest.raises(RuntimeError, match="No digitization points found"):
        _find_topomap_coords(info, picks, **kwargs)

def test_make_eeg_layout(tmp_path):
    """Test creation of EEG layout."""
    lout_orig = read_layout(fname=lout_path / "test_raw.lout")
    info = read_info(fif_fname)
    info["bads"].append(info["ch_names"][360])
    layout = make_eeg_layout(info, exclude=[])
    assert_array_equal(
        len(layout.names),
        len([ch for ch in info["ch_names"] if ch.startswith("EE")]),
    )
    layout.save(str(tmp_path / "foo.lout"))
    lout_new = read_layout(fname=tmp_path / "foo.lout", scale=False)
    assert_array_equal(lout_new.kind, "foo")
    assert_allclose(layout.pos, lout_new.pos, atol=0.1)
    assert_array_equal(lout_orig.names, lout_new.names)

    # Test input validation
    pytest.raises(ValueError, make_eeg_layout, info, radius=-0.1)
    pytest.raises(ValueError, make_eeg_layout, info, radius=0.6)
    pytest.raises(ValueError, make_eeg_layout, info, width=-0.1)
    pytest.raises(ValueError, make_eeg_layout, info, width=1.1)
    pytest.raises(ValueError, make_eeg_layout, info, height=-0.1)
    pytest.raises(ValueError, make_eeg_layout, info, height=1.1)

def test_make_grid_layout(tmp_path):
    """Test creation of grid layout."""
    lout_orig = read_layout(fname=lout_path / "test_ica.lout")
    layout = make_grid_layout(_get_test_info())
    layout.save(str(tmp_path / "bar.lout"))
    lout_new = read_layout(fname=tmp_path / "bar.lout")
    assert_array_equal(lout_new.kind, "bar")
    assert_array_equal(lout_orig.pos, lout_new.pos)
    assert_array_equal(lout_orig.names, lout_new.names)

    # Test creating grid layout with specified number of columns
    layout = make_grid_layout(_get_test_info(), n_col=2)
    # Vertical positions should be equal
    assert layout.pos[0, 1] == layout.pos[1, 1]
    # Horizontal positions should be unequal
    assert layout.pos[0, 0] != layout.pos[1, 0]
    # Box sizes should be equal
    assert_array_equal(layout.pos[0, 3:], layout.pos[1, 3:])

def test_find_layout():
    """Test finding layout."""
    pytest.raises(ValueError, find_layout, _get_test_info(), ch_type="meep")

    sample_info = read_info(fif_fname)
    grads = pick_types(sample_info, meg="grad")
    sample_info2 = pick_info(sample_info, grads)

    mags = pick_types(sample_info, meg="mag")
    sample_info3 = pick_info(sample_info, mags)

    # mock new convention
    sample_info4 = copy.deepcopy(sample_info)
    for ii, name in enumerate(sample_info4["ch_names"]):
        new = name.replace(" ", "")
        sample_info4["chs"][ii]["ch_name"] = new

    eegs = pick_types(sample_info, meg=False, eeg=True)
    sample_info5 = pick_info(sample_info, eegs)

    lout = find_layout(sample_info, ch_type=None)
    assert lout.kind == "Vectorview-all"
    assert all(" " in k for k in lout.names)

    lout = find_layout(sample_info2, ch_type="meg")
    assert_equal(lout.kind, "Vectorview-all")

    # test new vector-view
    lout = find_layout(sample_info4, ch_type=None)
    assert_equal(lout.kind, "Vectorview-all")
    assert all(" " not in k for k in lout.names)

    lout = find_layout(sample_info, ch_type="grad")
    assert_equal(lout.kind, "Vectorview-grad")
    lout = find_layout(sample_info2)
    assert_equal(lout.kind, "Vectorview-grad")
    lout = find_layout(sample_info2, ch_type="grad")
    assert_equal(lout.kind, "Vectorview-grad")
    lout = find_layout(sample_info2, ch_type="meg")
    assert_equal(lout.kind, "Vectorview-all")

    lout = find_layout(sample_info, ch_type="mag")
    assert_equal(lout.kind, "Vectorview-mag")
    lout = find_layout(sample_info3)
    assert_equal(lout.kind, "Vectorview-mag")
    lout = find_layout(sample_info3, ch_type="mag")
    assert_equal(lout.kind, "Vectorview-mag")
    lout = find_layout(sample_info3, ch_type="meg")
    assert_equal(lout.kind, "Vectorview-all")

    lout = find_layout(sample_info, ch_type="eeg")
    assert_equal(lout.kind, "EEG")
    lout = find_layout(sample_info5)
    assert_equal(lout.kind, "EEG")
    lout = find_layout(sample_info5, ch_type="eeg")
    assert_equal(lout.kind, "EEG")
    # no common layout, 'meg' option not supported

    lout = find_layout(read_info(fname_ctf_raw))
    assert_equal(lout.kind, "CTF-275")

    fname_bti_raw = bti_dir / "exported4D_linux_raw.fif"
    lout = find_layout(read_info(fname_bti_raw))
    assert_equal(lout.kind, "magnesWH3600")

    raw_kit = read_raw_kit(fname_kit_157)
    lout = find_layout(raw_kit.info)
    assert_equal(lout.kind, "KIT-157")

    raw_kit.info["bads"] = ["MEG 013", "MEG 014", "MEG 015", "MEG 016"]
    raw_kit.info._check_consistency()
    lout = find_layout(raw_kit.info)
    assert_equal(lout.kind, "KIT-157")
    # fallback for missing IDs
    for val in (35, 52, 54, 1001):
        with raw_kit.info._unlock():
            raw_kit.info["kit_system_id"] = val
        lout = find_layout(raw_kit.info)
        assert lout.kind == "custom"

    raw_umd = read_raw_kit(fname_kit_umd)
    lout = find_layout(raw_umd.info)
    assert_equal(lout.kind, "KIT-UMD-3")

    # Test plotting
    lout.plot()
    lout.plot(picks=np.arange(10))
    plt.close("all")

def test_box_size():
    """Test calculation of box sizes."""
    # No points. Box size should be 1,1.
    assert_allclose(_box_size([]), (1.0, 1.0))

    # Create one point. Box size should be 1,1.
    point = [(0, 0)]
    assert_allclose(_box_size(point), (1.0, 1.0))

    # Create two points. Box size should be 0.5,1.
    points = [(0.25, 0.5), (0.75, 0.5)]
    assert_allclose(_box_size(points), (0.5, 1.0))

    # Create three points. Box size should be (0.5, 0.5).
    points = [(0.25, 0.25), (0.75, 0.25), (0.5, 0.75)]
    assert_allclose(_box_size(points), (0.5, 0.5))

    # Create a grid of points. Box size should be (0.1, 0.1).
    x, y = np.meshgrid(np.linspace(-0.5, 0.5, 11), np.linspace(-0.5, 0.5, 11))
    x, y = x.ravel(), y.ravel()
    assert_allclose(_box_size(np.c_[x, y]), (0.1, 0.1))

    # Create a random set of points. This should never break the function.
    rng = np.random.RandomState(42)
    points = rng.rand(100, 2)
    width, height = _box_size(points)
    assert width is not None
    assert height is not None

    # Test specifying an existing width.
    points = [(0.25, 0.25), (0.75, 0.25), (0.5, 0.75)]
    assert_allclose(_box_size(points, width=0.4), (0.4, 0.5))

    # Test specifying an existing width that has influence on the calculated
    # height.
    points = [(0.25, 0.25), (0.75, 0.25), (0.5, 0.75)]
    assert_allclose(_box_size(points, width=0.2), (0.2, 1.0))

    # Test specifying an existing height.
    points = [(0.25, 0.25), (0.75, 0.25), (0.5, 0.75)]
    assert_allclose(_box_size(points, height=0.4), (0.5, 0.4))

    # Test specifying an existing height that has influence on the calculated
    # width.
    points = [(0.25, 0.25), (0.75, 0.45), (0.5, 0.75)]
    assert_allclose(_box_size(points, height=0.1), (1.0, 0.1))

    # Test specifying both width and height. The function should simply return
    # these.
    points = [(0.25, 0.25), (0.75, 0.45), (0.5, 0.75)]
    assert_array_equal(_box_size(points, width=0.1, height=0.1), (0.1, 0.1))

    # Test specifying a width that will cause unfixable horizontal overlap and
    # essentially breaks the function (height will be 0).
    points = [(0.25, 0.25), (0.75, 0.25), (0.5, 0.75)]
    assert_array_equal(_box_size(points, width=1), (1, 0))

    # Test adding some padding.
    # Create three points. Box size should be a little less than (0.5, 0.5).
    points = [(0.25, 0.25), (0.75, 0.25), (0.5, 0.75)]
    assert_allclose(_box_size(points, padding=0.1), (0.9 * 0.5, 0.9 * 0.5))

def test_generate_2d_layout():
    """Test creation of a layout from 2d points."""
    snobg = 10
    sbg = 15
    side = range(snobg)
    bg_image = np.random.RandomState(42).randn(sbg, sbg)
    w, h = [0.2, 0.5]

    # Generate fake data
    xy = np.array([(i, j) for i in side for j in side])
    lt = generate_2d_layout(xy, w=w, h=h)

    # Correct points ordering / minmaxing
    comp_1, comp_2 = [(5, 0), (7, 0)]
    assert lt.pos[:, :2].max() == 1
    assert lt.pos[:, :2].min() == 0
    with np.errstate(invalid="ignore"):  # divide by zero
        assert_allclose(
            xy[comp_2] / float(xy[comp_1]), lt.pos[comp_2] / float(lt.pos[comp_1])
        )
    assert_allclose(lt.pos[0, [2, 3]], [w, h])

    # Correct number elements
    assert lt.pos.shape[1] == 4
    assert len(lt.box) == 4

    # Make sure background image normalizing is correct
    lt_bg = generate_2d_layout(xy, bg_image=bg_image)
    assert_allclose(lt_bg.pos[:, :2].max(), xy.max() / float(sbg))


# -*- coding: utf-8 -*-
# 一些基类的扩展和重写
from enum import Enum as BaseEnum
from enum import EnumMeta as BaseEnumMeta
from enum import _EnumDict
import inspect

class EnumMeta(BaseEnumMeta):
    def __new__(mcs, name, bases, attrs):
        Labels = attrs.get('Labels')

        if Labels is not None and inspect.isclass(Labels):
            del attrs['Labels']
            if hasattr(attrs, '_member_names'):
                attrs._member_names.remove('Labels')

        obj = BaseEnumMeta.__new__(mcs, name, bases, attrs)
        for m in obj:
            try:
                m.label = getattr(Labels, m.name)
            except AttributeError:
                m.label = m.name.replace('_', ' ').title()

        return obj

# reference enumfields of django
class EnumLabel(EnumMeta('Enum', (BaseEnum,), _EnumDict())):

    @classmethod
    def choices(cls):
        return tuple((m.value, m.label) for m in cls)

    def __str__(self):
        return self.label


from __future__ import print_function

import os
from shutil import copyfile

from .utils import get_builds, get_ggd_channels, get_species

SPECIES_LIST = sorted(get_species())
GENOME_BUILDS = sorted(get_builds("*"))
CHANNEL_LIST = [x.encode("ascii") for x in get_ggd_channels()]
GENOMIC_COORDINATE_LIST = [
    "0-based-inclusive",
    "0-based-exclusive",
    "1-based-inclusive",
    "1-based-exclusive",
    "NA",
]

def add_make_metarecipe(p):
    c = p.add_parser(
        "make-meta-recipe",
        help="Make a new ggd data meta-recipe",
        description="Make a ggd data meta-recipe",
    )

    c.add_argument(
        "-c",
        "--channel",
        help="the ggd channel to use. (Default = genomics)",
        choices=[x.decode("ascii") for x in CHANNEL_LIST],
        default="genomics",
    )

    c.add_argument(
        "-d",
        "--dependency",
        default=[],
        action="append",
        help="any software dependencies (in bioconda, conda-forge) or data-dependency (in ggd)"
        + ". May be as many times as needed.",
    )

    c.add_argument(
        "-p",
        "--platform",
        default="noarch",
        help="Whether to use noarch as the platform or the system platform. If set to 'none' the system platform will be used. (Default = noarch. Noarch means no architecture and is platform agnostic.)",
        choices=["noarch", "none"],
    )

    c.add_argument(
        "-s",
        "--species",
        help="The species recipe is for. Use 'meta-recipe` for a metarecipe file",
        default="meta-recipe",
    )

    c.add_argument(
        "-g",
        "--genome-build",
        help="The genome build the recipe is for. Use 'metarecipe' for a metarecipe file",
        default="meta-recipe",
    )

    c.add_argument(
        "-dv",
        "--data-version",
        help="The version of the data (itself) being downloaded and processed (EX: dbsnp-127). Use 'metarecipe' for a metarecipe",
        default="meta-recipe",
    )

    c.add_argument(
        "-cb",
        "--coordinate-base",
        choices=GENOMIC_COORDINATE_LIST,
        help="The genomic coordinate basing for the file(s) in the recipe. Use 'NA' for a metarecipe",
        default="NA",
    )

    c.add_argument(
        "--extra-scripts",
        metavar="Extra Scripts",
        nargs="*",
        help="Any additional scripts used for the metarecipe that are not the main bash script",
    )

    c2 = c.add_argument_group("required arguments")

    c2.add_argument(
        "--authors",
        help="The author(s) of the data metarecipe being created, (This recipe)",
        default=os.environ.get("USER", ""),
    )

    c2.add_argument(
        "-pv",
        "--package-version",
        help="The version of the ggd package. (First time package = 1, updated package > 1)",
        required=True,
    )

    c2.add_argument(
        "-dp",
        "--data-provider",
        required=True,
        help="The data provider where the data was accessed. (Example: UCSC, Ensembl, gnomAD, etc.)",
    )

    c2.add_argument(
        "--summary",
        help="A detailed comment describing the recipe",
        default="",
        required=True,
    )

    c2.add_argument(
        "-k",
        "--keyword",
        help="A keyword to associate with the recipe."
        + " May be specified more that once."
        + " Please add enough keywords to better describe and distinguish the recipe",
        action="append",
        default=[],
        required=True,
    )

    c2.add_argument(
        "-n",
        "--name",
        help="The sub-name of the recipe being created. (e.g. cpg-islands, pfam-domains, gaps, etc.)"
        + " This will not be the final name of the recipe, but will specific to the data gathered and processed by the recipe",
        required=True,
    )

    c2.add_argument(
        "script", help="bash script that contains the commands for the metarecipe.",
    )

    c.set_defaults(func=make_bash)

def make_bash(parser, args):

    import shutil
    import sys

    import yaml

    name = (
        args.name.replace(args.species, "")
        .replace(args.genome_build, "")
        .strip("- ")
        .strip()
    )
    data_provider = (
        args.data_provider.replace(args.species, "")
        .replace(args.genome_build, "")
        .strip("- ")
        .strip()
        .lower()
    )
    name = "{0}-{1}-{2}-v{3}".format(
        args.genome_build, name, data_provider, args.package_version
    ).lower()
    name = name.replace("_", "-")
    assert name.strip() != "{0}--{1}-v{2}".format(
        args.genome_build, data_provider, args.package_version
    ), "The recipe name is required"  ## test for missing name
    assert name.strip() != "{0}-{1}--v{2}".format(
        args.genome_build, args.name.lower(), args.package_version
    ), "The data provider is required"  ## test for missing name
    assert (
        args.summary.strip() != ""
    ), "Please provide a thorough summary of the data package"
    print(
        "\n:ggd:make-recipe: Name of recipe: {0}-{1}-{2}-v{3}\n".format(
            args.genome_build.lower(),
            args.name.lower(),
            data_provider.lower(),
            args.package_version.lower(),
        )
    )
    assert name == "{0}-{1}-{2}-v{3}".format(
        args.genome_build.lower(),
        args.name.lower(),
        data_provider.lower(),
        args.package_version.lower(),
    ), "The recipe name is not formatted correctly. Current name: {}".format(name)

    wildcards = [
        "?",
        "*",
        "[",
        "]",
        "{",
        "}",
        "!",
        "\\",
        "(",
        ")",
        ".",
        "+",
        "^",
        "$",
        "|",
    ]
    for x in wildcards:
        assert (
            x not in name
        ), '\n\n\t"{}" wildcard is not allowed in the recipe name. Please rename the recipe. \n\tRecipe name = {} \n\tList of wildcards not allowed: {}'.format(
            x, name, " ".join(wildcards)
        )

    try:
        os.makedirs(name)
    except OSError:
        shutil.rmtree(name)
        os.makedirs(name)

    from .check_recipe import _check_build

    #    if args.genome_build != "meta-recipe":
    print(":ggd:make-recipe: checking", args.genome_build)
    _check_build(args.species, args.genome_build)

    try:
        recipe_bash = open(args.script).read()
    except IOError as e:
        print(e)
        sys.exit(1)

    # use these to automate inserting some dependencies.
    look = {
        "tabix": "htslib",
        "bgzip": "htslib",
        "perl": "perl",
        "gsort": "gsort",
        "samtools": "samtools",
        "gzip": "zlib",
        "zcat": "zlib",
        "gunzip": "zlib",
        "vt": "vt",
    }
    deps = sorted(
        set(
            [look.get(p, p) for p in args.dependency]
            + [look[prog] for prog in look if prog in recipe_bash]
        )
    )

    from .search import load_json
    from .utils import get_channel_data

    ## Get a list of ggd packages
    ggd_packages = set()
    for channel in CHANNEL_LIST:
        channel = channel.decode("utf-8") if not isinstance(channel, str) else channel
        json_dict = load_json(get_channel_data(channel))
        ggd_packages.update(json_dict["packages"].keys())

    ## Get non-ggd dependencies
    non_ggd_deps = [x for x in deps if x not in ggd_packages]

    ## Check coordinates
    assert (
        args.coordinate_base in GENOMIC_COORDINATE_LIST
    ), "{c} is not an acceptable genomic coordinate base".format(c=args.coordinate_base)
    # ("Please provide a genomic coordinate base from the follow list: {}".format(", ".join(GENOMIC_COORDINATE_LIST)))

    ## Check data version
    assert (
        args.data_version
    ), "Please provide the version of the data this recipe curates"
    assert (
        args.data_version.strip != ""
    ), "Please provide the version of the data this recipe curates"

    if args.platform == "noarch":
        yml1 = {
            "build": {
                "noarch": "generic",
                "binary_relocation": False,
                "detect_binary_files_with_prefix": False,
                "number": 0,
            }
        }
    else:
        yml1 = {
            "build": {
                "binary_relocation": False,
                "detect_binary_files_with_prefix": False,
                "number": 0,
            }
        }
    yml2 = {"extra": {"authors": args.authors}}
    yml3 = {"package": {"name": name, "version": args.package_version}}
    # yml3 = {"package": {"name": "{{ GGD_NAME_ID }}-" + args.data_provider.lower() + "-v" + args.package_version , "version": args.package_version}}
    # yml3 = {"package": {"name": """{{ environ.get("GGD_NAME_ID") }}-""" + args.data_provider.lower() + "-v" + args.package_version , "version": args.package_version}}
    yml4 = {"requirements": {"build": non_ggd_deps[:], "run": deps[:]}}
    yml5 = {"source": {"path": "."}}
    yml6 = {
        "about": {
            "identifiers": {"species": args.species, "genome-build": args.genome_build},
            "keywords": args.keyword,
            "summary": args.summary,
            "tags": {
                "genomic-coordinate-base": args.coordinate_base.strip(),
                "data-version": args.data_version.strip(),
                "data-provider": args.data_provider.strip(),
                "file-type": [],
                "final-files": [],
                "final-file-sizes": {},
                "ggd-channel": args.channel,
            },
        }
    }

    ## Write output with specific key order
    with open(os.path.join(name, "meta.yaml"), "a") as fh:
        fh.write(yaml.dump(yml1, default_flow_style=False))
        fh.write(yaml.dump(yml2, default_flow_style=False))
        fh.write(yaml.dump(yml3, default_flow_style=False))
        fh.write(yaml.dump(yml4, default_flow_style=False))
        fh.write(yaml.dump(yml5, default_flow_style=False))
        fh.write(yaml.dump(yml6, default_flow_style=False))

    with open(os.path.join(name, "post-link.sh"), "w") as fh:
        fh.write(
            """#!/bin/bash
set -eo pipefail -o nounset

new_name="$GGD_METARECIPE_ID-{dp}-v{version}"
#new_name=${to_lower} Requires bash version >= 4.2
new_name="$(echo $new_name | tr '[:upper:]' '[:lower:]')"

if [[ -z $(conda info --envs | grep "*" | grep -o "\/.*") ]]; then
    export CONDA_ROOT=$(conda info --root)
    env_dir=$CONDA_ROOT
    export RECIPE_DIR=$CONDA_ROOT/share/ggd/{species}/{build}/$new_name/{version}
elif [[ $(conda info --envs | grep "*" | grep -o "\/.*") == "base" ]]; then
    export CONDA_ROOT=$(conda info --root)
    env_dir=$CONDA_ROOT
    export RECIPE_DIR=$CONDA_ROOT/share/ggd/{species}/{build}/$new_name/{version}
else
    env_dir=$(conda info --envs | grep "*" | grep -o "\/.*")
    export CONDA_ROOT=$env_dir
    export RECIPE_DIR=$env_dir/share/ggd/{species}/{build}/$new_name/{version}
fi

PKG_DIR=`find "$CONDA_SOURCE_PREFIX/pkgs/" -name "$PKG_NAME-$PKG_VERSION*" | grep -v ".tar.bz2" |  grep "$PKG_VERSION.*$PKG_BUILDNUM$"`

if [ -d $RECIPE_DIR ]; then
    rm -r $RECIPE_DIR
fi

mkdir -p $RECIPE_DIR

SCRIPTS_PATH="$PKG_DIR/info/recipe/"

(cd $RECIPE_DIR && bash $SCRIPTS_PATH/metarecipe.sh $GGD_METARECIPE_ID $SCRIPTS_PATH "$GGD_METARECIPE_ENV_VAR_FILE" "$GGD_METARECIPE_FINAL_COMMANDS_FILE")

cd $RECIPE_DIR

## Add environment variables
#### File
if [[ `find $RECIPE_DIR -type f -maxdepth 1 | wc -l | sed 's/ //g'` == 1 ]] ## If only one file
then
    recipe_env_file_name="ggd_""$new_name""_file"
    recipe_env_file_name="$(echo "$recipe_env_file_name" | sed 's/-/_/g' | sed 's/\./_/g')"
    file_path="$(find $RECIPE_DIR -type f -maxdepth 1)"

elif [[ `find $RECIPE_DIR -type f -maxdepth 1 | wc -l | sed 's/ //g'` == 2 ]] ## If two files
then
    indexed_file=`find $RECIPE_DIR -type f \( -name "*.tbi" -or -name "*.fai" -or -name "*.bai" -or -name "*.crai" -or -name "*.gzi" \) -maxdepth 1`
    if [[ ! -z "$indexed_file" ]] ## If index file exists
    then
        recipe_env_file_name="ggd_""$new_name""_file"
        recipe_env_file_name="$(echo "$recipe_env_file_name" | sed 's/-/_/g' | sed 's/\./_/g')"
        file_path="$(echo $indexed_file | sed 's/\.[^.]*$//')" ## remove index extension
    fi
fi

#### Dir
recipe_env_dir_name="ggd_""$new_name""_dir"
recipe_env_dir_name="$(echo "$recipe_env_dir_name" | sed 's/-/_/g' | sed 's/\./_/g')"

activate_dir="$env_dir/etc/conda/activate.d"
deactivate_dir="$env_dir/etc/conda/deactivate.d"

mkdir -p $activate_dir
mkdir -p $deactivate_dir

echo "export $recipe_env_dir_name=$RECIPE_DIR" >> $activate_dir/env_vars.sh
echo "unset $recipe_env_dir_name">> $deactivate_dir/env_vars.sh

#### File
    ## If the file env variable exists, set the env file var
if [[ ! -z "${file_env_var}" ]]
then
    echo "export $recipe_env_file_name=$file_path" >> $activate_dir/env_vars.sh
    echo "unset $recipe_env_file_name">> $deactivate_dir/env_vars.sh
fi

echo 'Recipe successfully built!'
""".format(
                to_lower="{new_name,,}",
                dp=args.data_provider.lower(),
                species=args.species,
                name=name,
                build=args.genome_build,
                version=args.package_version,
                ext_string="{f#*.}",  ## Bash get extension. (.bed, .bed.gz, etc.)
                filename_string="{f%%.*}",
                file_env_var="{recipe_env_file_name:-}",
            )
        )

    ## Create metarecipe.sh script file
    with open(os.path.join(name, "metarecipe.sh"), "w") as fh:
        fh.write("#!/bin/sh\nset -eo pipefail -o nounset\n")
        fh.write(open(args.script).read())

    ## create empty recipe.sh script
    open(os.path.join(name, "recipe.sh"), "a").close()

    ## Create empty checksum file
    open(os.path.join(name, "checksums_file.txt"), "a").close()

    print("\n:ggd:make-recipe: Wrote output to %s/" % name)
    print(
        "\n:ggd:make-recipe: To test that the recipe is working, and before pushing the new recipe to gogetdata/ggd-recipes, please run: \n\n\t$ ggd check-recipe %s/ --id <Testing ID>\n"
        % name
    )

    ## Copy all extra scripts to the meta recipe directory
    for f in args.extra_scripts:
        copyfile(f, os.path.join(os.getcwd(), name, os.path.basename(f)))

    return True


import pygame, random

pygame.init()
naytto = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Robotin paikka")

robo = pygame.image.load("robo.png")
robon_leveys = robo.get_width()
robon_korkeus = robo.get_height()

def aseta():
    robon_random_x = random.randint(0, 640 - robon_leveys)
    robon_random_y = random.randint(0, 480 - robon_korkeus)
    naytto.blit(robo, (robon_random_x, robon_random_y))
    pygame.display.flip()
    while True:

        for tapahtuma in pygame.event.get():
            if tapahtuma.type == pygame.QUIT:
                pygame.quit()

            if tapahtuma.type == pygame.MOUSEBUTTONDOWN:
                hiiri_x = tapahtuma.pos[0]
                hiiri_y = tapahtuma.pos[1]
                if hiiri_x >= robon_random_x and hiiri_x <= robon_random_x + robon_leveys and hiiri_y >= robon_random_y and hiiri_y <= robon_random_y + robon_korkeus:
                   robon_random_x = random.randint(0, 640 - robon_leveys)
                   robon_random_y = random.randint(0, 480 - robon_korkeus)
                   naytto.fill((0, 0, 0))
                   naytto.blit(robo, (robon_random_x, robon_random_y))
                   pygame.display.flip()

aseta()


from chimera.controllers.console.command import HighLevelCommand, command
from chimera.controllers.console.message import Message, Error

from chimera.controllers.console.controller import ConsoleController
from chimera.controllers.console.commands.object import ObjectCommand

class SiteCommand (HighLevelCommand):

    def __init__ (self):
        HighLevelCommand.__init__ (self, "site", [])

        self._kind  = ["instrument", "controller", "driver"]
        self._kinds = ["instruments", "controllers", "drivers"]

    @command
    def list (self, args, namespace):

        objs = ""
        for o in ConsoleController().getManager().getResources():
            objs += str(o)+'\n'
        objs = objs[:-1]

        return [Message("=== Objects ==="),
                Message(objs)]

    @command
    def add (self, args, namespace):

        if not args or len (args) < 2:
            return Error ("Invalid syntax")

        kind, location = args

        if location in namespace:
            return Error ("%s already on the system." % location)

        ret = False

        if kind == "instrument":
            ret = ConsoleController().getManager().addInstrument (location)
        elif kind == "controller":
            ret = ConsoleController().getManager().addController (location)
        elif kind == "driver":
            ret = ConsoleController().getManager().addDriver (location)
        else:
            return Error("Invalid object type '%s'" % kind)

        if not ret:
            return Error("Couldn't add %s '%s'" % (kind, location))

        # get the object and put on the namespace
        obj = ConsoleController().getObject(location)
        namespace.append (ObjectCommand (location, obj))

        return False

    def complete_add (self, prefix, line, prefix_start, prefix_end):

        "[instrument|controller|driver] /Location/location"

        if prefix_start == 0:
            if not prefix:
                return self._kind
            else:
                return [kind for kind in self._kind if kind.startswith(prefix)]

        return []


from typing import List
import collections

class Solution:
    def separateDigits(self, nums: List[int]) -> List[int]:
        nums = "".join(str(n) for n in nums)
        return [int(d) for d in nums]

        # res = []

        # for n in nums:
        #     temp = collections.deque()
        #     while n:
        #         temp.appendleft(n % 10)
        #         n //= 10
        #     res.extend(temp)

        # return res

        # res = []

        # for n in nums:
        #     temp = []
        #     while n:
        #         temp.append(n % 10)
        #         n //= 10
        #     res.extend(temp[::-1])

        # return res

        # res = []

        # for n in nums:
        #     temp = list(str(n))
        #     res.extend(int(d) for d in temp)

        # return res

def main():
    sol = Solution()
    print(sol.separateDigits([13,25,83,77]))
    print(sol.separateDigits([7,1,3,9]))

if __name__ == '__main__':
    main()


from collections import defaultdict
answer = 0
ball = defaultdict(list)
n,m,k = map(int,input().split())
dx =[-1,-1,0,1,1,1,0,-1] # 8가지 방향.
dy = [0,1,1,1,0,-1,-1,-1]
def move():# 파이어볼 모두 이동.
    global ball
    new = defaultdict(list)
    for x,y in ball:
        for m,s,d in ball[(x,y)]:
            nx = (x+dx[d]*s) # d방향 s칸.
            ny = (y+dy[d]*s)
            nx %= n # 행 열 모두 연결 됨.
            ny %= n
            new[(nx,ny)].append([m,s,d]) # 옮겨진 위치로 다시 할당.
    ball =new # ball 위치 update.

def makeOne():# 2개이상 한칸에 있는 파이어볼 하나로 합치기.
    global ball

    for x,y in ball:
        if len(ball[(x,y)]) >= 2:
            length = len(ball[(x,y)])
            totalM, totalS, even, odd = 0,0,0,0 # 총질량, 속력, 방향이 짝수 홀수인지 확인 위한 변수.
            for m,s,d in ball[(x,y)]:
                totalM += m
                totalS += s

                if d % 2 == 0:
                    even += 1
                else:
                    odd += 1
            if totalM // 5 == 0:# 질량이 0 이면 소멸. del를 쓰면 for반복문에서 ball 이 바뀌므로, 조심.
                ball[(x,y)] = []
                continue
            else:
                ball[(x,y)] = []# 만약 모두 방향이 짝수 또는 홀수라면 0,2,4,6 방향으로 할당.
                if even == length or odd == length:
                    for i in [0,2,4,6]:
                        ball[(x,y)].append([totalM//5, totalS//length,i])
                else:
                    for i in [1,3,5,7]:
                        ball[(x,y)].append([totalM//5, totalS//length,i])
for _ in range(m):# m개 입력
    r,c,m,s,d = map(int,input().split())
    ball[(r-1,c-1)].append([m,s,d])

for _ in range(k):# K번 마법.

    move()
    makeOne()

for x,y in ball:# 총질량합 구하기.
    for m,s,d in ball[(x,y)]:
        answer += m
print(answer)

35주차/마법사상어와파이어볼.py

마법사상어와파이어볼.py


import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
import os
import math
import tqdm

data_dir = '/media/tungthanhlee/DATA/tienthanh/assigned_jpeg'
out_dir = '/media/tungthanhlee/DATA/tienthanh/cropped jpeg'
os.makedirs(out_dir, exist_ok=True)
img_names = next(os.walk(data_dir))[2]
# All the 6 methods for comparison in a list
# templates = []
# templates.append(cv.imread('template4.jpg',0))
# templates.append(cv.imread('template6.jpg',0))
# templates.append(cv.imread('template8.jpg',0))
template = cv.imread('template7.jpg',0)
methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR',
            'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']
for i, img_name in enumerate(tqdm.tqdm(img_names)):
    # print(img_name)
    tm = template.copy()
    img = cv.imread(os.path.join(data_dir, img_name),0)
    ow, oh = img.shape[::-1]
    # print(ow, oh)
    if ow>= 2500 or oh>=2500:
        sr = 1/2
    elif (ow > 2000 and ow < 2500) or (oh > 2000 and oh < 2500):
        sr = (1/2 + 1/6)
    elif ow < 1100 or oh < 1100:
        sr = 4/5
    else:
        sr = 4/5
    r = min(ow, oh)*sr
    w, h = tm.shape[::-1]
    r = r / max(w, h)
    w = int(w*r)
    h = int(h*r)
    tm = cv.resize(tm, (w, h))
    method = eval(methods[5])
    # img2 = img.copy()s
    # img = img2.copy()
    # Apply template Matching
    res = cv.matchTemplate(img,tm,method)
    min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
    # if found is None or max_val > found[0]:
    #     found = (max_val, max_loc, img, w, h)
    # If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
    if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
        top_left = min_loc
    else:
        top_left = max_loc
    img = img[top_left[1]:top_left[1]+h, top_left[0]:top_left[0]+w]
    cv.imwrite(os.path.join(out_dir, img_name), img)
    # bottom_right = (top_left[0] + w, top_left[1] + h)
    # print(top_left, bottom_right)
    # print(img.shape)

    # cv.rectangle(img,top_left, bottom_right, 255, 2)
    # plt.figure(i)
    # plt.subplot(121),plt.imshow(res,cmap = 'gray')
    # plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
    # plt.subplot(122),plt.imshow(img,cmap = 'gray')
    # plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
    # plt.suptitle(methods[3])
    # plt.show()


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
The purpose of this python3 script is to create parser
and run ACE 'generate' command.
"""

import argparse
import math
import pandas as pd
import os
import random
import torch
from importlib import resources
from pathlib import Path
from ortools.sat.python import cp_model
from transformers import BertModel, BertTokenizer
from transformers import AutoTokenizer, AutoModelForMaskedLM
from ..block_assignment import BlockAssignment
from ..block_design import BlockDesign
from ..constants import *
from ..default_parameters import *
from ..logger import get_logger
from ..main import run_ace_sat_solver, run_ace_golfy, run_ace_generate
from ..utilities import *
from ..sequence_features import AceNeuralEngine

logger = get_logger(__name__)

def add_ace_generate_arg_parser(sub_parsers):
    """
    Adds 'generate' parser.

    Parameters
    ----------
    sub_parsers     :   argparse.ArgumentParser subparsers object.

    Returns
    -------
    sub_parsers     :   argparse.ArgumentParser subparsers object.
    """
    parser = sub_parsers.add_parser(
        'generate',
        help='Generates an ELISpot experiment configuration.'
    )
    parser._action_groups.pop()

    # Required arguments
    parser_required = parser.add_argument_group('required arguments')
    parser_required_mutually_exclusive = parser_required.add_mutually_exclusive_group(required=True)
    parser_required_mutually_exclusive.add_argument(
        "--num-peptides",
        dest="num_peptides",
        type=int,
        help="Total number of peptides."
    )
    parser_required_mutually_exclusive.add_argument(
        "--peptides-file",
        dest="peptides_file",
        type=str,
        help="Peptides CSV or Excel file with the following columns: "
             "'peptide_id', 'peptide_sequence'. Please note that only "
             "either this parameter or '--num-peptides' can be supplied."
    )
    parser_required.add_argument(
        "--num-peptides-per-pool",
        dest="num_peptides_per_pool",
        type=int,
        required=True,
        help="Number of peptides per pool (i.e. well). "
             "Please make sure this number is divisible by the total number of peptides."
    )
    parser_required.add_argument(
        "--num-coverage",
        dest="num_coverage",
        type=int,
        required=True,
        help="Total coverage (i.e. number of peptide replicates)."
    )
    parser_required.add_argument(
        "--output-excel-file",
        dest="output_excel_file",
        type=str,
        required=True,
        help="Output Excel (.xlsx) file."
    )

    # Optional arguments
    parser_optional = parser.add_argument_group('optional arguments')
    parser_optional.add_argument(
        "--plate-size",
        dest="plate_size",
        type=int,
        default=PlateWells.WELLS_96,
        required=False,
        help="Number of wells on plate. Allowed values: %s (default: %i)." %
             (', '.join([str(i) for i in PlateWells.ALL]), PlateWells.WELLS_96)
    )
    parser_optional.add_argument(
        "--mode",
        dest="mode",
        type=str,
        default=GenerateModes.GOLFY,
        choices=GenerateModes.ALL,
        required=False,
        help="Configuration generation mode. Allowed values: %s (default: %s)." %
             (', '.join(GenerateModes.ALL), GenerateModes.GOLFY)
    )
    parser_optional.add_argument(
        "--cluster-peptides",
        dest="cluster_peptides",
        type=bool,
        default=True,
        required=False,
        help="Cluster peptides if set to true (default: true)."
    )
    parser_optional.add_argument(
        "--sequence-similarity-function",
        dest="sequence_similarity_function",
        type=str,
        default=GENERATE_SEQUENCE_SIMILARITY_FUNCTION,
        required=False,
        choices=SequenceSimilarityFunctions.ALL,
        help="Sequence similarity function. Allowed values: %s (default: %s)." %
             (', '.join(SequenceSimilarityFunctions.ALL), GENERATE_SEQUENCE_SIMILARITY_FUNCTION)
    )
    parser_optional.add_argument(
        "--sequence-similarity-threshold",
        dest="sequence_similarity_threshold",
        type=float,
        default=GENERATE_SEQUENCE_SIMILARITY_THRESHOLD,
        required=False,
        help="Sequence similarity threshold (default: %f). "
             "A higher threshold leads to more stringent peptide pairing." % GENERATE_SEQUENCE_SIMILARITY_THRESHOLD
    )
    # Golfy optional parameters
    parser_optional_golfy = parser.add_argument_group("optional arguments (applies when '--mode golfy')")
    parser_optional_golfy.add_argument(
        "--golfy-random-seed",
        dest="golfy_random_seed",
        type=int,
        default=GENERATE_GOLFY_RANDOM_SEED,
        required=False,
        help="Random seed for golfy (default: %i)." % GENERATE_GOLFY_RANDOM_SEED
    )
    parser_optional_golfy.add_argument(
        "--golfy-max-iters",
        dest="golfy_max_iters",
        type=int,
        required=False,
        default=GENERATE_GOLFY_MAX_ITERS,
        help="Number of maximum iterations for golfy (default: %i)." % GENERATE_GOLFY_MAX_ITERS
    )
    parser_optional_golfy.add_argument(
        "--golfy-strategy",
        dest="golfy_strategy",
        type=str,
        default=GENERATE_GOLFY_STRATEGY,
        choices=GolfyStrategies.ALL,
        required=False,
        help="Strategy for golfy. Allowed value (default: %s)." % GENERATE_GOLFY_STRATEGY
    )
    parser_optional_golfy.add_argument(
        "--golfy-allow-extra-pools",
        dest="golfy_allow_extra_pools",
        type=eval,
        default=GENERATE_GOLFY_ALLOW_EXTRA_POOLS,
        choices=[True, False],
        required=False,
        help="Allow extra pools for golfy (default: %r)." % GENERATE_GOLFY_ALLOW_EXTRA_POOLS
    )

    # CP-SAT solver optional parameters
    parser_optional_sat_solver = parser.add_argument_group("optional arguments (applies when '--mode cpsat_solver')")
    parser_optional_sat_solver.add_argument(
        "--cpsat-solver-num-processes",
        dest="cpsat_solver_num_processes",
        type=int,
        required=False,
        default=GENERATE_CPSAT_SOLVER_NUM_PROCESSES,
        help="Number of processes for CP-SAT solver (default: %i)." % GENERATE_CPSAT_SOLVER_NUM_PROCESSES
    )
    parser_optional_sat_solver.add_argument(
        "--cpsat-solver-shuffle-iters",
        dest="cpsat_solver_shuffle_iters",
        type=int,
        required=False,
        default=GENERATE_CPSAT_SOLVER_SHUFFLE_ITERS,
        help="Number of iterations to shuffle pool IDs to minimize "
             "number of non-unique pool assignment violations for CP-SAT solver (default: %i)." % GENERATE_CPSAT_SOLVER_SHUFFLE_ITERS
    )
    parser_optional_sat_solver.add_argument(
        "--cpsat-solver-max-peptides-per-block",
        dest="cpsat_solver_max_peptides_per_block",
        type=int,
        default=GENERATE_CPSAT_SOLVER_MAX_PEPTIDES_PER_BLOCK,
        required=False,
        help="Maximum number of peptides per block (default: %i). "
             "The CPSAT-solver divides peptides into the specified number of peptides "
             "if the total number of peptides is bigger than the specified number "
             "(e.g. 220 peptides are divided into 2 blocks of 100 peptides and 1 "
             "block of 20 peptides if --max-peptides-per-block is 100). "
             "Increasing this number from the current default value will likely "
             "make the computation intractable so it is recommended that you "
             "keep this at %i." %
             (GENERATE_CPSAT_SOLVER_MAX_PEPTIDES_PER_BLOCK, GENERATE_CPSAT_SOLVER_MAX_PEPTIDES_PER_BLOCK)
    )
    parser_optional_sat_solver.add_argument(
        "--cpsat-solver-max-peptides-per-pool",
        dest="cpsat_solver_max_peptides_per_pool",
        type=int,
        default=GENERATE_CPSAT_SOLVER_MAX_PEPTIDES_PER_POOL,
        required=False,
        help="Maximum number of peptides per pool (default: %i). "
             "Increasing this number from the current default value will likely "
             "make the computation intractable so it is recommended that you "
             "keep this at %i." %
             (GENERATE_CPSAT_SOLVER_MAX_PEPTIDES_PER_POOL, GENERATE_CPSAT_SOLVER_MAX_PEPTIDES_PER_POOL)
    )
    parser_optional_sat_solver.add_argument(
        "--verbose",
        dest="verbose",
        type=bool,
        required=False,
        default=True,
        help="If True, prints messages. Otherwise, messages are not printed (default: True)."
    )
    parser.set_defaults(which='generate')
    return sub_parsers

def run_ace_generate_from_parsed_args(args):
    """
    Runs ACE 'generate' command using parameters from parsed arguments.

    Parameters
    ----------
    args    :   argparse.ArgumentParser object
                with the following variables:
                num_peptides
                peptides_file
                design_csv_file
                num_peptides_per_pool
                num_coverage
                output_excel_file
                mode
                plate_size
                sequence_similarity_function
                sequence_similarity_threshold
                golfy_random_seed
                golfy_max_iters
                golfy_strategy
                golfy_allow_extra_pools
                cpsat_solver_num_processes
                cpsat_solver_shuffle_iters
                cpsat_solver_max_peptides_per_block
                cpsat_solver_max_peptides_per_pool
                verbose
    """
    # Step 1. Load peptide data
    if args.peptides_file is not None:
        file_name, file_extension = os.path.splitext(args.peptides_file)
        if file_extension == '.xlsx':
            peptides = convert_dataframe_to_peptides(df_peptides=pd.read_excel(args.peptides_file))
        elif file_extension == '.csv':
            peptides = convert_dataframe_to_peptides(df_peptides=pd.read_csv(args.peptides_file))
        else:
            logger.error("--peptides-file must be either a .CSV or .XLSX file. Expected headers: 'peptide_id' and 'peptide_sequence'.")
            exit(1)
        cluster_peptides = args.cluster_peptides
    else:
        peptides = []
        for i in range(1, args.num_peptides + 1):
            peptides.append(('peptide_%i' % i, ''))
        cluster_peptides = False

    # Step 2. Check input parameters
    if len(peptides) % args.num_peptides_per_pool != 0 and args.mode == GenerateModes.CPSAT_SOLVER:
        logger.error('The number of peptides per pool must be divisible by the total number of peptides to for CP-SAT solver.')
        exit(1)

    # Step 3. Generate block design
    block_assignment, block_design = run_ace_generate(
        peptides=peptides,
        num_peptides_per_pool=args.num_peptides_per_pool,
        num_coverage=args.num_coverage,
        trained_model_file=resources.path('acelib.resources.models', 'trained_model_w_data_augmentation_b3000.pt'),
        cluster_peptides=cluster_peptides,
        mode=args.mode,
        sequence_similarity_function=args.sequence_similarity_function,
        sequence_similarity_threshold=args.sequence_similarity_threshold,
        golfy_random_seed=args.golfy_random_seed,
        golfy_strategy=args.golfy_strategy,
        golfy_max_iters=args.golfy_max_iters,
        golfy_allow_extra_pools=args.golfy_allow_extra_pools,
        cpsat_solver_num_processes=args.cpsat_solver_num_processes,
        cpsat_solver_shuffle_iters=args.cpsat_solver_shuffle_iters,
        cpsat_solver_max_peptides_per_block=args.cpsat_solver_max_peptides_per_block,
        cpsat_solver_max_peptides_per_pool=args.cpsat_solver_max_peptides_per_pool,
        plate_size=args.plate_size,
        verbose=args.verbose
    )

    # Step 4. Write design and assignment to an Excel file
    df_assignment = block_assignment.to_dataframe()
    df_assignment_bench_ready = block_assignment.to_bench_ready_dataframe()
    df_assignment.drop('coverage_id', axis=1, inplace=True)
    df_assignment.drop('pool_id', axis=1, inplace=True)
    with pd.ExcelWriter(args.output_excel_file, engine='openpyxl') as writer:
        df_assignment.to_excel(writer, sheet_name='assignment', index=False)
        df_assignment_bench_ready.to_excel(writer, sheet_name='assignment_bench_ready', index=False)
        block_design.peptides_dataframe.to_excel(writer, sheet_name='peptides', index=False)
        block_design.preferred_peptide_pairs_dataframe.to_excel(writer, sheet_name='preferred_peptide_pairs', index=False)
        block_design.metadata_dataframe.to_excel(writer, sheet_name='parameters', index=False)


import uuid
from datetime import datetime, timezone

from dynamodb_manager import DynamoDBManager
from models.task import Task

class TaskController:
    def __init__(self):
        self.dynamodb_manager = DynamoDBManager(
            region_name='us-east-2', table_name='TasksTable')

    def create_task(self, data):
        try:
            code = str(uuid.uuid4())
            current_datetime = datetime.now(timezone.utc).isoformat()
            task = Task(code, data['description'], data['status'],
                        current_datetime, current_datetime)
            self.dynamodb_manager.put_item(task.to_dynamo())
            return code
        except Exception as e:
            raise Exception('Failed to create task') from e

    def _remove_dynamo_attributes(self, task):
        return {k: v['S'] if isinstance(v, dict) and 'S' in v else v for k,
                v in task.items()}

    def get_tasks(self):
        try:
            tasks = self.dynamodb_manager.scan_items()
            formatted_tasks = [
                Task(**self._remove_dynamo_attributes(task))
                .to_dict() for task in tasks]
            return formatted_tasks
        except Exception as e:
            raise Exception('Failed to get tasks') from e

    def get_task(self, code):
        try:
            task = self.dynamodb_manager.get_item({'code': {'S': code}})
            if task:
                return Task(**self._remove_dynamo_attributes(task)).to_dict()
            else:
                raise Exception('Task not found')
        except Exception as e:
            raise Exception('Failed to get task') from e

    def update_task(self, code, data):
        try:
            task = self.get_task(data['code'])
            current_datetime = datetime.now(timezone.utc).isoformat()
            task = Task(code, task.description, data['status'],
                        task.created_at, current_datetime)
            self.dynamodb_manager.put_item(task.to_dynamo())
        except Exception as e:
            raise Exception('Failed to update task') from e

    def delete_task(self, code):
        try:
            self.dynamodb_manager.delete_item({'code': {'S': code}})
        except Exception as e:
            raise Exception('Failed to delete task') from e


import pygame, sys, math
from Wall import *
from Spawner import*

def loadLevel(lev):
    f = open(lev, 'r')
    lines = f.readlines()
    f.close()

    size = 50
    offset = size/2
    tiles = []
    walls = []
    spawners = []

    newLines = []
    for line in lines:
        newline = ""
        for c in line:
            if c != "\n":
                newline += c
        newLines += [newline]

    lines = newLines

    for y, line in enumerate(lines):
        for x, c in enumerate(line):
            if c == "#":
                walls += [Wall([x*size+offset, y*size+offset])]
            elif c == "X":
                spawners += [Spawner([x*size+offset, y*size+offset])]
    tiles = [walls,
             spawners]

    return tiles

#loadLevel("Levels/level.lvl")


import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import imgstore
from utilities import *
import stim_handling as stims
from scipy.interpolate import splrep, splev

def sync_by_reversal(df, col='dir'):
    """
    pass a df with stim information
    returns the df with rotation values adjusted for direction so rotation of new stim is positive
    """
    df = df.sort_values('Timestamp').reset_index()
    df = df[:-10]
    df['reversal'] = 0
    reversals = df[abs(df[col] - df.shift()[col]) ==2].index
    df.loc[reversals, 'reversal'] = 1
    df.loc[df['Time'] > 300, 'reversal'] = 0 #FIXME this is a hack solution to sort out ends
    df['firstStim'] = 0
    firstStim = df[df['Time'] < df['Time'].median()]
    firstStim = firstStim[abs(firstStim[col] - firstStim.shift()[col]) ==1].index
    df.loc[firstStim, 'firstStim'] = 1
    df['lastStim'] = 0
    lastStim = df[df['Time'] > df['Time'].median()]
    lastStim = lastStim[abs(lastStim[col] - lastStim.shift()[col]) ==1].index
    df.loc[lastStim, 'lastStim'] = 1
    return df

def align_by_stim(df, ID, stimAligner='reversal', col='median_dRotation_cArea'):
    alignPoints = list(df[df[stimAligner] == 1]['Timestamp'].values)
    df = df[df[col].isnull() == False]
    trials = pd.DataFrame()
    trialID = 0
    for i in alignPoints:
        data = df.loc[df['Timestamp'].between(i-10.0, i+60.0), ['Timestamp','speed','dir','coh',
                                                                'mean_polarization',
                                                                'mean_radius',
                                                                'median_radius',
                                                                'median_swimSpeed',
                                                                'median_dRotation_cArea',
                                                                'median_dRotation_cMass',
                                                                'std_dRotation_cArea',
                                                                'std_dRotation_cMass',
                                                                'entropy_Ra']]
        data['syncTime'] = pd.to_timedelta(data['Timestamp']-i,'s')
        data['median_dRotation_cArea'] = data['median_dRotation_cArea']*data['dir'].median()*-1.0 #make congruent and positive
        data['median_dRotation_cMass'] = data['median_dRotation_cMass']*data['dir'].median()*-1.0 #make congruent and positive

        data['trialID'] = ID + '_' + str(trialID)
        data['date'] = ID.split('_')[0]
        trialID += 1
        trials = pd.concat([trials, data], axis=0)
    return trials

def getMinMax(series, buffer=0.05):
    MIN = series.min()
    if MIN < 0:
        MIN*= 1.0 + buffer
    else:
        MIN*=1.0 - buffer
    if series.max() < 0:
        MAX = series.max() *(1.0-buffer)
    else:
        MAX = series.max() *(1.0+buffer)
    return (MIN, MAX)

def plot_many_trials(trials, col='median_dRotation_cArea', grouping='trialID', plotTrials=True, fig= None, ax=None, colour=None, XLIM=(-10,60), YLIM=(-1.05,1.05), RESAMPLE='250ms', NORMALIZE_PRESTIM=False, YLABEL='Mean congruent rotation order $\pm$ SEM'):
    #colourList = ['#EA4335','#E16D13','#FBBC05','#34A853','#4285F4','#891185',
    #              '#4285F4','#FBBC05','#34A853','#EA4335','#891185','#E16D13','#0B1C2E','#347598']

    trials.index = trials.syncTime

    if YLIM == 'auto':
        foo = trials.copy()
        YLIM = getMinMax(foo.resample(RESAMPLE).mean().groupby([grouping,'syncTime']).mean()[col], buffer=0.1)

    if fig== None:
        fig = plt.figure()
        ax = fig.add_subplot(111)
    if grouping == 'trialID':
        g = trials.groupby(grouping)
        LW = 0.5
        ALPHA = 0.5
        for a, data in g:
            #data.index = data['syncTime']
            data = data.resample(RESAMPLE).mean()
            ax.plot(data.index, data[col], label=str(a), linewidth=LW, alpha=ALPHA)
    elif grouping == 'byday':
        g = trials.groupby(['date'])
        LW = 0.5
        ALPHA = 0.5
        groupCount = 0
        for date, data in g:
            h = data.groupby('trialID')
            LABEL = date
            for a, vals in h:
                vals = vals.resample(RESAMPLE).mean()
                xvals = [t.total_seconds() for t in vals.index]
                ax.plot(xvals, vals[col], label=LABEL,
                                                linewidth=LW,
                                                alpha=ALPHA,
                                                color=colourList[groupCount])
                LABEL = '_nolegend_'
            groupCount += 1
    else:
        g = trials.groupby([grouping])
        LW = 0.5
        ALPHA = 0.5
        groupCount = 0
        for group, data in g:
            if grouping == 'coh':
                if not group in [0,0.2,0.4,0.6,0.8,1.0]:
                    continue
            N = len(set(data['trialID']))
            LABEL = str(group) + ', N=' + str(int(N))
            if plotTrials == True:
                h = data.groupby('trialID')
                for a, vals in h:
                    ax.plot(vals.index, vals[col], label=LABEL,
                                                    linewidth=LW,
                                                    alpha=ALPHA,
                                                    color=colourList[groupCount])
                    LABEL = '_nolegend_'
            data.index = data['syncTime']
            r = data.resample(RESAMPLE).mean()
            sem = 1.253*(data.resample(RESAMPLE).std()/np.sqrt(N)) #http://davidmlane.com/hyperstat/A106993.html and
            #    https://influentialpoints.com/Training/standard_error_of_median.htm
            xvals = [i.total_seconds()  for i in r.index]
            prestimIdx = (np.array(xvals) < 0) * (np.array(xvals)>-1)
            if NORMALIZE_PRESTIM:
                a = r.loc[prestimIdx, col].mean() #normalization value for variation in prestim
            else:
                a=0
            ax.plot(xvals, r[col]-a, label=LABEL, linewidth=2, color=colourList[groupCount], zorder=100)
            ax.fill_between(xvals,
                            r[col]-a+sem[col],
                            r[col]-a-sem[col],
                            color=colourList[groupCount],
                            alpha=0.1,
                            linewidth=0,
                            zorder=10)
            groupCount+=1

    #fig.autofmt_xdate()
    ax.set_xlabel('Time since direction change (s)')#, fontsize='xx-small')

    if NORMALIZE_PRESTIM:
        ax.set_ylabel('Normalized ' + YLABEL)#, fontsize='xx-small')
    else:
        ax.set_ylabel(YLABEL)#, fontsize='xx-small')
    ax.set_ylim(YLIM[0], YLIM[1])
    #ax.set_yticks([-1.0, 0, 1.0])
    ax.set_xlim(XLIM[0], XLIM[1])
    if grouping == 'coh':
        plt.legend(title='Coherence', fontsize='xx-small')
    else:
        plt.legend(title=grouping, fontsize='xx-small')
    #plotnice()
    return fig

groupsizes = [64,128,256,512,1024]
"""
allData = pd.DataFrame()

for groupsize in groupsizes:
    print groupsize
    groupData = pd.DataFrame()
    for fn in glob.glob('/media/recnodes/recnode_2mfish/reversals3m_' + str(groupsize) + '_dotbot_*/track/perframe_stats.pickle'):
        if '20181026' in fn:#day when fish were left in the tank overnight
            continue
        if '20190523' in fn:#on this day, timestamps were out of sync by ~25 seconds
            continue
        if '20190603' in fn:#on this day, timestamps were out of sync by ~25 seconds
            continue
        print fn
        try:
            pf = pd.read_pickle(fn)
            if not 'FrameNumber' in pf.columns:
                if not 'frame' in pf.columns:
                    pf['frame'] = pf.index.copy()
                ret, pf = stims.sync_reversals(pf, stims.get_logfile(fn.rsplit('/',2)[0]), imgstore.new_for_filename(fn.rsplit('/',2)[0] + '/metadata.yaml'))
        except Exception as e:
            print "failed for ", fn
            print e
            continue
        pf['dir'] = pd.to_numeric(pf['dir'], errors='coerce')

        pf = sync_by_reversal(pf)
        fileID = fn.split('/')[-3].split('.')[0].split('_',3)[-1]
        reversals = align_by_stim(pf, fileID)
        if len(reversals) == 0:
            continue
        reversals['groupsize'] = groupsize

        groupData = pd.concat([groupData, reversals], axis=0)
    groupData.to_pickle('/media/recnodes/Dan_storage/20200203_reversal_data_compiled_' + str(groupsize) + '.pickle')

    allData = pd.concat([allData, groupData], axis=0)
allData.to_pickle('/media/recnodes/Dan_storage/20200203_reversal_data_compiled_full.pickle')
"""
allData = pd.read_pickle('/media/recnodes/Dan_storage/20200203_reversal_data_compiled_full.pickle')

colourList = create_colourlist(len(groupsizes), cmap='viridis')
for COL in ['median_dRotation_cArea','median_dRotation_cMass']:
    plot_many_trials(allData, col=COL, grouping='groupsize', plotTrials=False)
    plt.savefig('/media/recnodes/Dan_storage/20200312_reversals_'+COL+'_vs_time_by_groupsize.svg')
    plt.close('all')
    plot_many_trials(allData, col=COL, grouping='groupsize', plotTrials=False, XLIM=(-5,15), RESAMPLE='25ms', NORMALIZE_PRESTIM=False)
    plt.savefig('/media/recnodes/Dan_storage/20200312_reversals_'+COL+'_vs_time_by_groupsize_onset.svg')
    plt.close('all')

#colourList = ['#EA4335', '#E16D13', '#FBBC05', '#34A853', '#4285F4', '#891185', '#4285F4', '#FBBC05', '#34A853', '#EA4335', '#891185', '#E16D13', '#0B1C2E','#347598']
cols = ['mean_polarization',
         'mean_radius',
         'median_radius',
         'median_swimSpeed',
         'entropy_Ra',
         'std_dRotation_cArea']

allData['rA_rounded'] = np.around(allData['median_dRotation_cMass']/10.0, 2)*10.0
rev = allData[allData.syncTime.between(np.timedelta64(-10, 's'), np.timedelta64(30,'s'))]

g = rev.groupby('groupsize')
#PLOT BY GROUPING BY ROTATION ORDER
ymax, xmax = (4000,6000)
my_dpi = 300
my_figsize = [xmax/my_dpi, ymax/my_dpi]
fig  = plt.figure(figsize=my_figsize, dpi=my_dpi)
p = 0
for col in cols:
    p+=1
    i=1
    for gs, data in g:
        axi = fig.add_subplot(2,3,p)
        data.index=data['syncTime']
        N = len(set(data.trialID))
        foo = data.groupby('rA_rounded')
        m = foo.mean()
        s = foo.std()/np.sqrt(N)
        plt.fill_between(m.median_dRotation_cMass, m[col]+s[col], m[col]-s[col], color=colourList[i-1], alpha=0.1, linewidth=0)
        plt.plot(m.median_dRotation_cMass, m[col], color=colourList[i-1],label=gs)
        plt.legend()
        axi.set_ylabel(col)
        i+=1
plt.savefig('/media/recnodes/Dan_storage/20200312_reversals_misc_vs_dRotationMass_binsize_0.1.svg')

#FIRST GROUP BY TIME, THEN PLOT VS ROTATION ORDER

fig  = plt.figure(figsize=my_figsize, dpi=my_dpi)
p = 0
for col in cols:
    p+=1
    i=1
    for gs, data in g:
        axi = fig.add_subplot(2,3,p)
        data.index=data['syncTime']
        N = len(set(data.trialID))
        foo = data.resample('250ms')
        m = foo.mean()
        s = foo.std()/np.sqrt(N)
        plt.fill_between(m.median_dRotation_cMass, m[col]+s[col], m[col]-s[col], color=colourList[i-1], alpha=0.1, linewidth=0)
        plt.plot(m.median_dRotation_cMass, m[col], color=colourList[i-1],label=gs)
        plt.legend()
        axi.set_ylabel(col)
        i+=1
plt.savefig('/media/recnodes/Dan_storage/20200312_reversals_misc_vs_dRotationMass_250msBins.svg')


import datetime

class Human(object):
    name = None
    gender = None
    bday = None

    def __getattr__(self, name):
        if name == 'age':
            print (datetime.datetime.now() - self.bday)
        else:
            return None
    def __getattribute__(self, name):
        return object.__getattribute__(self, name)

h = Human()
h.bday = datetime.datetime(1984,8,20)
h.age = 25
print (h.age)


# Write a python code to take some string input. Now in the string find out how the
#frequency of each letter and print them along the letter alphabetically.

def main():
    freq = dict()
    s=input("Enter the string: ")
    for i in s:
        if i in freq:
            freq[i]+=1
        else:
            freq[i]=1

    sort=sorted(freq.items(), key=lambda x:x[0])
    print("The output is:")
    print(sort)

main()


from django.db import models
from django.contrib.auth.models import AbstractUser

AbstractUser._meta.get_field('email')._unique = True
AbstractUser._meta.get_field('email').blank = False
AbstractUser._meta.get_field('username').blank = True
AbstractUser._meta.get_field('username')._unique = False

class User(AbstractUser):
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    class SEX:
        M = 'М'
        F = 'Ж'

        LIST_DISPLAY = (
            (M, 'Мужской'),
            (F, 'Женский')
        )

    sex = models.CharField('Пол', max_length=1, choices=SEX.LIST_DISPLAY, blank=False)
    photo = models.ImageField(upload_to='profiles', verbose_name='Фото')
    liked = models.ManyToManyField('self', blank=True, symmetrical=False)
    lat = models.FloatField(null=True, verbose_name='Широта')
    lon = models.FloatField(null=True, verbose_name='Долгота')

    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

    class Meta:
        verbose_name = 'User'
        verbose_name_plural = 'Users'


"""Registered models of post app"""

from django.contrib import admin

from post.models import Post, LikeDate, DislikeDate

class LikeDateAdmin(admin.ModelAdmin):
    """Admin panel for LikeDate model"""

    list_display = (
        'date', 'user', 'post',
    )

class DislikeDateAdmin(admin.ModelAdmin):
    """Admin panel for DislikeDate model"""

    list_display = (
        'date', 'user', 'post',
    )

class PostAdmin(admin.ModelAdmin):
    """Admin panel for Post model"""

    list_display = (
        'title', 'author', 'status',
        'published_time', 'updated_time',
    )

    list_filter = (
        'published_time', 'status',
    )

MODEL_WITH_ADMIN_MODEL = {
    LikeDate: LikeDateAdmin,
    DislikeDate: DislikeDateAdmin,
    Post: PostAdmin,
}

for model, admin_model in MODEL_WITH_ADMIN_MODEL.items():
    admin.site.register(model, admin_model)


from kol.manager import PatternManager
from kol.util import Report
from kol.util import StringUtils

CHAT_CHANNELS = [
    "clan",
    "dev",
    "foodcourt",
    "games",
    "haiku",
    "hardcore",
    "harem",
    "hobopolis",
    "kwe",
    "lounge",
    "mod",
    "newbie",
    "normal"
    "pvp",
    "radio",
    "slimetube",
    "trade",
    "valhalla",
    "veteran",
    "villa",
]

def parseIncomingChatMessage(text):
    return parseChatMessages(text, True)

def parseOutgoingChatMessages(text):
    return parseChatMessages(text, False)

def parseChatMessages(text, isIncoming):
    """
    This function parses chats passed to it. The chats are assumed to come from a GetChatMessagesRequest.
    Returns a list of chats, each of which is a dictionary possibly containing the following keys:
        "type" : What kind of chat this is.  Current possible values are
            "channel"
            "listen"
            "listen:start"
            "listen:stop"
            "normal"
            "emote"
            "private"
            "system message"
            "mod warning"
            "mod announcement"
            "notification:kmail"
            "unknown"
        "currentChannel" : The current channel as indicated when sending a /c, /s, or /l request
        "otherChannels" : The other channels being listened to as indicated by a /l request
        "description" : The description of the current channel as indicated when sending a /c or /s request
        "channel" : The channel this chat message was posted from
        "userId" : The user id number of the user sending this chat message
        "userName" : The user name of the user sending this chat message
        "text" : The text of the current chat message
        "isMultiline" : A flag indicating whether this is a multiline message such as a haiku or a message from the Gothy Effect
    """

    # Prepare the patterns.
    htmlCommentPattern = PatternManager.getOrCompilePattern("htmlComment")
    htmlTagPattern = PatternManager.getOrCompilePattern("htmlTag")
    channelPattern = PatternManager.getOrCompilePattern("chatChannel")
    chatPattern = PatternManager.getOrCompilePattern("chatMessage")
    emotePattern = PatternManager.getOrCompilePattern("chatEmote")
    privateChatPattern = PatternManager.getOrCompilePattern("privateChat")
    newKmailPattern = PatternManager.getOrCompilePattern("chatNewKmailNotification")
    linkPattern = PatternManager.getOrCompilePattern("chatLink")
    chatWhoPattern = PatternManager.getOrCompilePattern("chatWhoResponse")
    linkedPlayerPattern = PatternManager.getOrCompilePattern("chatLinkedPlayer")
    multiLinePattern = PatternManager.getOrCompilePattern("chatMultiLineStart")
    multiEmotePattern = PatternManager.getOrCompilePattern("chatMultiLineEmote")
    playerLoggedOnPattern = PatternManager.getOrCompilePattern("chatPlayerLoggedOn")
    playerLoggedOffPattern = PatternManager.getOrCompilePattern("chatPlayerLoggedOff")

    # Get the chat messages.
    chats = []

    # Check for responses to outgoing chat commands.
    if isIncoming == False:
        outPrivatePattern = PatternManager.getOrCompilePattern("outgoingPrivate")
        chatNewChannelPattern = PatternManager.getOrCompilePattern("newChatChannel")
        chatListenPattern = PatternManager.getOrCompilePattern("chatListenResponse")
        chatListenStartPattern = PatternManager.getOrCompilePattern("chatStartListen")
        chatListenStopPattern = PatternManager.getOrCompilePattern("chatStopListen")

        # See if it is an outgoing private message
        match = outPrivatePattern.search(text)
        if match:
            chat = {}
            chat["type"] = "private"
            chat["userName"] = match.group(2)
            chat["userId"] = int(match.group(1))
            chat["text"] = match.group(3).strip()
            text = text[:match.start()] + text[match.end():]
            chats.append(chat)

        # See if the user changed chat channels through /c or /s
        match = chatNewChannelPattern.search(text)
        if match:
            chat = {}
            chat["type"] = "channel"
            chat["currentChannel"] = match.group(1)
            chat["description"] = match.group(2).replace('
','')
            text = text[:match.start()] + text[match.end():]
            chats.append(chat)

        # See if it is a /l response
        match = chatListenPattern.search(text)
        if match:
            chat = {}
            listen = match.group()
            currentPattern = PatternManager.getOrCompilePattern("chatListenCurrent")
            otherPattern = PatternManager.getOrCompilePattern("chatListenOthers")
            chat["type"] = "listen"
            chat["currentChannel"] = currentPattern.search(listen).group(1)
            other = []
            for channel in otherPattern.finditer(listen):
                other.append(channel.group(1))
            chat["otherChannels"] = other
            text = text[:match.start()] + text[match.end():]
            chats.append(chat)

        # See if it is a /l <channel> response to start listening to a channel
        match = chatListenStartPattern.search(text)
        if match:
            chat = {}
            chat["type"] = "listen:start"
            chat["channel"] = match.group(1)
            text = text[:match.start()] + text[match.end():]
            chats.append(chat)

        # See if it is a /l <channel> response to stop listening to a channel
        match = chatListenStopPattern.search(text)
        if match:
            chat = {}
            chat["type"] = "listen:stop"
            chat["channel"] = match.group(1)
            text = text[:match.start()] + text[match.end():]
            chats.append(chat)

    lines = text.split("
")

    for line in lines:
        line = htmlCommentPattern.sub('', line)
        line = line.strip()
        if len(line) == 0:
            continue

        # Mod Announcements and Mod Warnings leave leading  tags at the beginning of the next message
        # This method will remove them and also skip the line if that is all there is
        if line[:7] == "":
            if len(line) == 7:
                continue
            else:
                line = line[7:].strip()

        # System Announcements leave leading  tags at the beginning of the next message
        # This method will remove them and also skip the line if that is all there is
        if line[:11] == "":
            if len(line) == 11:
                continue
            else:
                line = line[11:].strip()

        chat = {}
        parsedChat = False

        # See if this message was posted to a different channel.
        match = channelPattern.search(line)
        if match:
            chat["channel"] = match.group(1)
            line = line[len(match.group(0)):]

        # See if this was a normal chat message.
        if parsedChat == False:
            match = chatPattern.search(line)
            if match:
                chat["type"] = "normal"
                chat["userId"] = int(match.group(1))
                chat["userName"] = match.group(2)

                # Check for special announcements
                if chat["userId"] == -1 or chat["userName"] == "System Message":
                    chat["type"] = "system message"
                elif chat["userName"] == "Mod Warning":
                    chat["type"] = "mod warning"
                elif chat["userName"] == "Mod Announcement":
                    chat["type"] = "mod announcement"

                chat["text"] = match.group(3).strip()
                parsedChat = True

        # See if this was an emote.
        if parsedChat == False:
            match = emotePattern.search(line)
            if match:
                chat["type"] = "emote"
                chat["userId"] = int(match.group(1))
                chat["userName"] = match.group(2)
                chat["text"] = match.group(3).strip()
                parsedChat = True

        if isIncoming == True:
            # See if a user logged in.
            if parsedChat == False:
                match = playerLoggedOnPattern.search(line)
                if match:
                    chat["type"] = "logonNotification"
                    chat["userId"] = int(match.group(1))
                    chat["userName"] = match.group(2)
                    parsedChat = True

            # See if a user logged out.
            if parsedChat == False:
                match = playerLoggedOffPattern.search(line)
                if match:
                    chat["type"] = "logoffNotification"
                    chat["userId"] = int(match.group(1))
                    chat["userName"] = match.group(2)
                    parsedChat = True

            # See if this was a private message.
            if parsedChat == False:
                match = privateChatPattern.search(line)
                if match:
                    chat["type"] = "private"
                    chat["userId"] = int(match.group(1))
                    chat["userName"] = match.group(2)
                    chat["text"] = match.group(3).strip()
                    parsedChat = True

            # See if this is a new kmail notification.
            if parsedChat == False:
                match = newKmailPattern.search(line)
                if match:
                    chat["type"] = "notification:kmail"
                    chat["userId"] = int(match.group(1))
                    chat["userName"] = match.group(2)
                    parsedChat = True

            # See if this is the start of a multi-line message (Gothy or Haiku)
            if parsedChat == False:
                match = multiLinePattern.search(line)
                if match:
                    chat["type"] = "normal"
                    chat["userId"] = int(match.group(1))
                    chat["userName"] = match.group(2)
                    chat["isMultiline"] = True
                    chat["text"] = ""
                    parsedChat = True

            # See if this is the start of a multi-line emote (Gothy or Haiku)
            # I've seen a Haiku emote, don't know if Gothy will trigger for it
            if parsedChat == False:
                match = multiEmotePattern.search(line)
                if match:
                    chat["type"] = "emote"
                    chat["userId"] = int(match.group(1))
                    chat["userName"] = match.group(2)
                    chat["isMultiline"] = True
                    chat["text"] = ""
                    parsedChat = True

        else:
            # See if this is a /who response.
            if parsedChat == False:
                if chatWhoPattern.search(line):
                    chat["type"] = "who"
                    chat["users"] = []
                    chatWhoPersonPattern = PatternManager.getOrCompilePattern("chatWhoPerson")
                    for match in chatWhoPersonPattern.finditer(line):
                        userClass = match.group(1)
                        userId = match.group(2)
                        userName = match.group(3)
                        userInfo = {"userId" : userId, "userName" : userName}
                        if userClass == "afk":
                            userInfo["isAway"] = True
                        chat["users"].append(userInfo)
                    parsedChat = True

        if parsedChat and "text" in chat:
            chat["text"] = cleanChatText(chat["text"])

        # Handle unrecognized chat messages.
        if parsedChat == False:
            # If the last chat was flagged as starting a multiline
            if len(chats) > 0 and "isMultiline" in chats[-1]:
                if chats[-1]["isMultiline"] == True:
                    if len(chats[-1]["text"]) > 0:
                        chats[-1]["text"] += "\n"
                    line = line.replace('
','\n')
                    cleanLine = cleanChatText(line)
                    cleanLine = cleanLine.replace(' ','').strip()

                    chats[-1]["text"] += cleanLine

                    continue

            # If the last chat was flagged as a System or Mod Announcement, skip past the trailing tags
            elif len(chats) > 0:
                if "type" in chats[-1] and chats[-1]["type"] in ["system message", "mod warning", "mod announcement"]:
                    if line == "":
                        continue

            # Any other case we aren't prepared to handle
            Report.error("bot", "Unknown message.  ResponseText = %s" % text)
            chat["type"] = "unknown"
            chat["text"] = StringUtils.htmlEntityDecode(line)

        chats.append(chat)

    return chats

def cleanChatMessageToSend(text):
    "Cleans a chat message by removing extra whitespace."
    text = text.strip()
    whitespacePattern = PatternManager.getOrCompilePattern('whitespace')
    text = whitespacePattern.sub(' ', text)
    return text

def parseChatMessageToSend(text):
    "This function assumes that the text has already been cleaned using cleanChatMessageToSend()."

    # We need to break up the chat message into chunks.
    arr = text.split(' ')
    lowerText = text.lower()
    chatInfo = {}

    if arr[0].find('/') == 0:
        if arr[0] in ["/msg", "/whisper", "/w", "/tell"] and len(arr) > 2:
            chatInfo["type"] = "private"
            chatInfo["recipient"] = arr[1]
        elif arr[0][1:] in CHAT_CHANNELS:
            chatInfo["type"] = "channel"
            chatInfo["channel"] = arr[0][1:]
            if len(arr) > 2 and arr[0] in ["/me", "/em"]:
                chatInfo["isEmote"] = True
        elif arr[0] in ["/me", "/em"]:
            chatInfo["type"] = "channel"
            chatInfo["isEmote"] = True
    else:
        chatInfo["type"] = "channel"

    return chatInfo

def cleanChatText(dirtyText):
    "This functions parses player links and external links in the body of the chat text, and cleans any html tags"

    linkPattern = PatternManager.getOrCompilePattern("chatLink")
    linkedPlayerPattern = PatternManager.getOrCompilePattern("chatLinkedPlayer")
    htmlTagPattern = PatternManager.getOrCompilePattern("htmlTag")

    text = dirtyText

    # Parse user links.
    text = linkedPlayerPattern.sub(r'\2', text)

    # Parse misc links.
    match = linkPattern.search(text)
    while match != None:
        url = match.group(1)
        urlIndex = 0
        textStart = match.end()
        textEnd = textStart
        found = False
        while found == False:
            if text[textEnd] == url[urlIndex]:
                textEnd += 1
                urlIndex += 1
            elif text[textEnd] == ' ':
                textEnd += 1

            if urlIndex == len(url):
                found = True

        newText = text[:match.start()] + url + text[textEnd:]
        text = newText
        match = linkPattern.search(text)

    # Decode HTML entities.
    text = StringUtils.htmlEntityDecode(text)

    # Clean up the text.
    text = htmlTagPattern.sub('', text)

    return text


"""
Реалізуйте підпрограми сортування масиву.
"""

def bubble_sort(array):
    """ Сортування "Бульбашкою"
    :param array: Масив (список однотипових елементів)
    """
    n = len(array)
    for i in range(n - 1, 0, -1):
        for j in range(i):
            if array[j] > array[j + 1]:
                array[j], array[j + 1] = array[j + 1], array[j]

def bubble_sort_optimized(array):
    """ Модификований алгоритм сортування "Бульбашкою"
    :param array: Вхідний масив даних, що треба відсортувати.
    """
    n = len(array)
    for i in range(n - 1, 0, -1):
        check = True
        for j in range(i):
            if array[j] > array[j + 1]:
                check = False
                array[j], array[j + 1] = array[j + 1], array[j]
        if check:
            break

def selection_sort(array):
    """ Сортування вибором
    :param array: Масив (список однотипових елементів)
    :return: None
    """
    n = len(array)
    for i in range(n - 1, 0, -1):
        m = 0
        for j in range(1, i + 1):
            if array[m] < array[j]:
                m = j
        array[i], array[m] = array[m], array[i]

def insertion_sort(array):
    """ Сортування вставкою
    :param array: Масив (список однотипових елементів)
    :return: None
    """
    n = len(array)
    for i in range(1, n):
        cur = array[i]
        pos = i
        while pos > 0:
            if array[pos - 1] > cur:
                array[pos] = array[pos - 1]
            else:
                break
            pos -= 1
        array[pos] = cur