from shutil import rmtree
from datetime import datetime
from tempfile import mkdtemp
from functools import partial
from itertools import count
import pathlib
import io
import os
import os.path
import re
import sys
import textwrap

from unittest.mock import Mock, patch, call
import freezegun
import pytest
import tms

from yoyo import read_migrations
from yoyo.config import get_configparser
from yoyo.tests import dburi_sqlite3
from yoyo.tests import migrations_dir
from yoyo.tests import get_backend
from yoyo.scripts.main import main, parse_args, LEGACY_CONFIG_FILENAME
from yoyo.scripts import newmigration

def is_tmpfile(p, directory=None):
    return (p.startswith(directory) if directory else True) and os.path.basename(
        p
    ).startswith(newmigration.tempfile_prefix)

class TestInteractiveScript(object):
    def setup_method(self):
        self.stdout_tty_patch = patch("sys.stdout.isatty", return_value=True)
        self.stdout_tty_patch.start()
        self.confirm_patch = patch("yoyo.utils.confirm", return_value=False)
        self.confirm = self.confirm_patch.start()
        self.prompt_patch = patch("yoyo.utils.prompt", return_value="n")
        self.prompt = self.prompt_patch.start()
        self.tmpdir = mkdtemp()
        self.dburi = "sqlite:///" + self.tmpdir + "db.sqlite"
        self.saved_cwd = os.getcwd()
        os.chdir(self.tmpdir)

    def teardown_method(self):
        self.prompt_patch.stop()
        self.confirm_patch.stop()
        self.stdout_tty_patch.stop()
        os.chdir(self.saved_cwd)
        rmtree(self.tmpdir)

    def writeconfig(self, **defaults):
        cp = get_configparser()
        for item in defaults:
            cp.set("DEFAULT", item, defaults[item])

        if sys.version_info < (3, 0):
            with open("yoyo.ini", "w") as f:
                cp.write(f)
        else:
            with open("yoyo.ini", "w", encoding="UTF-8") as f:
                cp.write(f)

    def get_migration_log(self):
        return (
            get_backend(self.dburi)
            .execute("SELECT migration_id, operation FROM _yoyo_log")
            .fetchall()
        )

class TestYoyoScript(TestInteractiveScript):
    def test_it_sets_verbosity_level(self, tmpdir):
        with patch("yoyo.scripts.main.configure_logging") as m:
            main(["apply", str(tmpdir), "--database", dburi_sqlite3])
            assert m.call_args == call(0)
            main(["-vvv", "apply", str(tmpdir), "--database", dburi_sqlite3])
            assert m.call_args == call(3)

    def test_it_prompts_to_create_config_file(self, tmpdir):
        main(["apply", str(tmpdir), "--database", dburi_sqlite3])
        assert "save migration config" in self.confirm.call_args[0][0].lower()

    def test_it_creates_config_file(self, tmpdir):
        self.confirm.return_value = True
        main(["apply", str(tmpdir), "--database", dburi_sqlite3])
        assert os.path.exists("yoyo.ini")
        with open("yoyo.ini") as f:
            assert "database = {0}".format(dburi_sqlite3) in f.read()

    def test_it_uses_config_file(self, tmpdir):
        self.writeconfig(batch_mode="on")
        with patch("yoyo.scripts.migrate.apply") as apply:
            main(["apply", str(tmpdir), "--database", dburi_sqlite3])
            args_used = apply.call_args[0][0]
            assert args_used.batch_mode is True

    def test_it_ignores_config_file(self, tmpdir):
        self.writeconfig(batch_mode="on")
        with patch("yoyo.scripts.migrate.apply") as apply:
            main(
                [
                    "apply",
                    "--no-config-file",
                    str(tmpdir),
                    "--database",
                    dburi_sqlite3,
                ]
            )
            args_used = apply.call_args[0][0]
            assert args_used.batch_mode is False

    def test_it_prompts_password(self, tmpdir):
        dburi = "sqlite://user@/:memory"
        with patch("yoyo.scripts.main.getpass", return_value="fish") as getpass, patch(
            "yoyo.connections.get_backend"
        ) as get_backend:
            main(["apply", str(tmpdir), "--database", dburi, "--prompt-password"])
            assert getpass.call_count == 1
            assert get_backend.call_args == call(
                "sqlite://user:fish@/:memory", "_yoyo_migration"
            )

    def test_it_prompts_migrations(self, tmpdir):
        with patch(
            "yoyo.scripts.migrate.prompt_migrations"
        ) as prompt_migrations, patch(
            "yoyo.scripts.migrate.get_backend"
        ) as get_backend:
            main(["apply", str(tmpdir), "--database", dburi_sqlite3])
            migrations = get_backend().to_apply()
            assert migrations in prompt_migrations.call_args[0]

    def test_it_applies_migrations(self, tmpdir):
        with patch("yoyo.scripts.migrate.get_backend") as get_backend:
            main(["-b", "apply", str(tmpdir), "--database", dburi_sqlite3])
            assert get_backend().rollback_migrations.call_count == 0
            assert get_backend().apply_migrations.call_count == 1

    def test_it_rollsback_migrations(self, tmpdir):
        with patch("yoyo.scripts.migrate.get_backend") as get_backend:
            main(["-b", "rollback", str(tmpdir), "--database", dburi_sqlite3])
            assert get_backend().rollback_migrations.call_count == 1
            assert get_backend().apply_migrations.call_count == 0

    def test_it_reapplies_migrations(self, tmpdir):
        with patch("yoyo.scripts.migrate.get_backend") as get_backend:
            main(["-b", "reapply", str(tmpdir), "--database", dburi_sqlite3])
            assert get_backend().rollback_migrations.call_count == 1
            assert get_backend().apply_migrations.call_count == 1

    def test_it_applies_from_multiple_sources(self):
        with migrations_dir(
            m1='step("CREATE TABLE yoyo_test1 (id INT)")'
        ) as t1, migrations_dir(
            m2='step("CREATE TABLE yoyo_test2 (id INT)")'
        ) as t2, patch(
            "yoyo.backends.DatabaseBackend.apply_migrations"
        ) as apply:
            main(["-b", "apply", t1, t2, "--database", dburi_sqlite3])
            call_posargs, call_kwargs = apply.call_args
            migrations, _ = call_posargs
            assert [m.path for m in migrations] == [
                os.path.join(t1, "m1.py"),
                os.path.join(t2, "m2.py"),
            ]

    def test_it_offers_to_upgrade(self, tmpdir):
        legacy_config_path = os.path.join(str(tmpdir), LEGACY_CONFIG_FILENAME)
        with open(legacy_config_path, "w", encoding="utf-8") as f:
            f.write("[DEFAULT]\n")
            f.write("migration_table=_yoyo_migration\n")
            f.write("dburi=sqlite:///\n")

        self.confirm.return_value = True
        main(["apply", str(tmpdir)])
        prompts = [args[0].lower() for args, kwargs in self.confirm.call_args_list]
        assert len(prompts) == 2
        assert prompts[0].startswith("move legacy configuration")
        assert prompts[1].startswith("delete legacy configuration")
        assert not os.path.exists(legacy_config_path)

        with open("yoyo.ini", "r") as f:
            config = f.read()
            assert "database = sqlite:///\n" in config
            assert "migration_table = _yoyo_migration\n" in config
            assert "batch_mode = off\n" in config
            assert "verbosity = 0\n" in config

    def test_it_upgrades_migration_table_None(self, tmpdir):
        legacy_config_path = os.path.join(str(tmpdir), LEGACY_CONFIG_FILENAME)
        with open(legacy_config_path, "w", encoding="utf-8") as f:
            f.write("[DEFAULT]\n")
            f.write("migration_table=None\n")
            f.write("dburi=sqlite:///\n")
        self.confirm.return_value = True
        main(["apply", str(tmpdir)])

        with open("yoyo.ini", "r") as f:
            config = f.read()
        assert "migration_table = _yoyo_migration\n" in config

    def test_it_forces_batch_mode_if_not_running_in_a_tty(self, tmpdir):
        with patch("sys.stdout", isatty=lambda: False):
            main(["apply", str(tmpdir), "--database", dburi_sqlite3])
            assert self.prompt.call_count == 0
            assert self.confirm.call_count == 0

    def test_concurrent_instances_do_not_conflict(self, backend):
        import threading
        from functools import partial

        if backend.uri.scheme == "sqlite":
            pytest.skip("Concurrency tests not supported for sqlite databases")

        with migrations_dir(
            m1=(
                "import time\n"
                "step(lambda conn: time.sleep(0.1))\n"
                "step(\"INSERT INTO yoyo_t VALUES ('A')\")"
            )
        ) as tmpdir:
            assert "yoyo_t" in backend.list_tables()
            backend.rollback()
            backend.execute("SELECT * FROM yoyo_t")
            run_migrations = partial(
                main, ["apply", "-b", tmpdir, "--database", str(backend.uri)]
            )
            threads = [threading.Thread(target=run_migrations) for ix in range(20)]
            for t in threads:
                t.start()
            for t in threads:
                t.join()

            # Exactly one instance of the migration script should have succeeded
            backend.rollback()
            cursor = backend.execute("SELECT COUNT(1) from yoyo_t")
            assert cursor.fetchone()[0] == 1

    def test_it_breaks_lock(self, dburi):
        if dburi.startswith("sqlite"):
            pytest.skip("Test not supported for sqlite databases")
        backend = get_backend(dburi)
        backend.execute(
            "INSERT INTO yoyo_lock (locked, ctime, pid) " "VALUES (1, :now, 1)",
            {"now": datetime.utcnow()},
        )
        backend.commit()
        main(["break-lock", "--database", dburi])
        lock_count = backend.execute("SELECT COUNT(1) FROM yoyo_lock").fetchone()[0]
        assert lock_count == 0

    def test_it_prompts_password_on_break_lock(self):
        dburi = "sqlite://user@/:memory"
        with patch("yoyo.scripts.main.getpass", return_value="fish") as getpass, patch(
            "yoyo.connections.get_backend"
        ) as get_backend:
            main(["break-lock", "--database", dburi, "--prompt-password"])
            assert getpass.call_count == 1
            assert get_backend.call_args == call(
                "sqlite://user:fish@/:memory", "_yoyo_migration"
            )

class TestArgParsing(TestInteractiveScript):
    def test_it_uses_config_file_defaults(self):
        self.writeconfig(
            sources="/tmp/migrations",
            database="postgresql:///foo",
            migration_table="my_migrations",
        )
        _, _, args = parse_args(["apply"])
        assert args.database == "postgresql:///foo"
        assert args.sources == ["/tmp/migrations"]
        assert args.migration_table == "my_migrations"

    def test_it_uses_interpolated_values_from_config(self):
        self.writeconfig(sources="%(here)s/migrations")
        _, _, args = parse_args(["apply"])
        assert args.sources == [os.getcwd() + "/migrations"]

    def test_cli_args_take_precendence(self):
        self.writeconfig(sources="A")
        _, _, args = parse_args(["apply", "B", "--database", "C"])
        assert args.sources == ["B"]

    def test_global_args_can_appear_before_command(self):
        _, _, args = parse_args(["apply", "X", "--database", "Y"])
        assert args.verbosity == 0
        _, _, args = parse_args(["-v", "apply", "X", "--database", "Y"])
        assert args.verbosity == 1

    def test_global_args_can_appear_after_command(self):
        _, _, args = parse_args(["apply", "X", "Y"])
        assert args.verbosity == 0
        _, _, args = parse_args(["apply", "-v", "X", "Y"])
        assert args.verbosity == 1

class TestMarkCommand(TestInteractiveScript):
    def test_it_prompts_only_unapplied(self):
        with migrations_dir(
            m1='step("CREATE TABLE test1 (id INT)")',
            m2='step("CREATE TABLE test2 (id INT)")',
            m3='step("CREATE TABLE test3 (id INT)")',
        ) as tmpdir:
            from yoyo.connections import get_backend

            migrations = read_migrations(tmpdir)
            backend = get_backend(self.dburi)
            backend.apply_migrations(migrations[:1])

            with patch("yoyo.scripts.migrate.prompt_migrations") as prompt_migrations:
                main(["mark", tmpdir, "--database", self.dburi])
                _, prompted, _ = prompt_migrations.call_args[0]
                prompted = [m.id for m in prompted]
                assert prompted == ["m2", "m3"]

    def test_it_marks_at_selected_version(self):
        with migrations_dir(
            m1='step("INSERT INTO t VALUES (1)")',
            m2='__depends__=["m1"]; step("INSERT INTO t VALUES (2)")',
            m3='step("INSERT INTO t VALUES (2)")',
        ) as tmpdir:
            from yoyo.connections import get_backend

            self.confirm.return_value = True
            migrations = read_migrations(tmpdir)
            backend = get_backend(self.dburi)
            with backend.transaction():
                backend.execute("CREATE TABLE t (id INT)")

            main(["mark", "-r", "m2", tmpdir, "--database", self.dburi])
            assert backend.is_applied(migrations[0])
            assert backend.is_applied(migrations[1])
            assert not backend.is_applied(migrations[2])

            # Check that migration steps have not been applied
            c = backend.execute("SELECT * FROM t")
            assert len(c.fetchall()) == 0

class TestUnmarkCommand(TestInteractiveScript):
    def test_it_prompts_only_applied(self):
        with migrations_dir(m1="", m2="", m3="") as tmpdir:
            from yoyo.connections import get_backend

            migrations = read_migrations(tmpdir)
            backend = get_backend(self.dburi)
            backend.apply_migrations(migrations[:2])
            assert len(backend.get_applied_migration_hashes()) == 2

            with patch("yoyo.scripts.migrate.prompt_migrations") as prompt_migrations:
                main(["unmark", tmpdir, "--database", self.dburi])
                _, prompted, _ = prompt_migrations.call_args[0]
                prompted = [m.id for m in prompted]
                assert prompted == ["m2", "m1"]

    def test_it_unmarks_to_selected_revision(self):
        with migrations_dir(
            m1="", m2='__depends__=["m1"]', m3='__depends__=["m2"]'
        ) as tmpdir:
            from yoyo.connections import get_backend

            self.confirm.return_value = True
            migrations = read_migrations(tmpdir)
            backend = get_backend(self.dburi)
            backend.apply_migrations(migrations)

            main(["unmark", "-r", "m2", tmpdir, "--database", self.dburi])
            assert backend.is_applied(migrations[0])
            assert not backend.is_applied(migrations[1])
            assert not backend.is_applied(migrations[2])

class TestNewMigration(TestInteractiveScript):
    def setup_method(self):
        def mockstat(f, c=count()):
            return Mock(st_mtime=next(c))

        super(TestNewMigration, self).setup_method()
        self.subprocess_patch = patch("yoyo.scripts.newmigration.subprocess")
        self.subprocess = self.subprocess_patch.start()
        self.subprocess.call.return_value = 0
        self.stat_patch = patch("yoyo.scripts.newmigration.stat", mockstat)
        self.stat_patch.start()

    def teardown_method(self):
        super(TestNewMigration, self).teardown_method()
        self.subprocess_patch.stop()
        self.stat_patch.stop()

    def test_it_creates_an_empty_migration(self, tmpdir):
        main(["new", "-b", "-m", "foo", str(tmpdir), "--database", dburi_sqlite3])
        assert any("-foo.py" in f for f in os.listdir(str(tmpdir)))

    def test_it_depends_on_all_current_heads(self):
        with migrations_dir(
            m1="", m2='__depends__=["m1"]; step("INSERT INTO t VALUES (2)")', m3=""
        ) as tmpdir:
            main(["new", "-b", "-m", "foo", tmpdir, "--database", dburi_sqlite3])
            m = next(f for f in os.listdir(tmpdir) if "-foo.py" in f)
            with open(os.path.join(tmpdir, m), encoding="utf-8") as f:
                assert "__depends__ = {'m2', 'm3'}" in f.read()

    def test_it_names_file_by_date_and_sequence(self, tmpdir):
        with freezegun.freeze_time("2001-1-1"):
            main(["new", "-b", "-m", "foo", str(tmpdir), "--database", dburi_sqlite3])
            main(["new", "-b", "-m", "bar", str(tmpdir), "--database", dburi_sqlite3])
        names = [n for n in sorted(os.listdir(str(tmpdir))) if n.endswith(".py")]
        assert names[0].startswith("20010101_01_")
        assert names[0].endswith("-foo.py")
        assert names[1].startswith("20010101_02_")
        assert names[1].endswith("-bar.py")

    def test_it_invokes_correct_editor_binary_from_config(self, tmpdir):
        self.writeconfig(editor="vim {} -c +10")
        main(["new", str(tmpdir), "--database", dburi_sqlite3])
        assert self.subprocess.call.call_args == call(
            [
                "vim",
                tms.Matcher(partial(is_tmpfile, directory=str(tmpdir))),
                "-c",
                "+10",
            ]
        )

    def test_it_invokes_correct_editor_binary_from_env(self, tmpdir):
        # default to $VISUAL
        with patch("os.environ", {"EDITOR": "ed", "VISUAL": "visualed"}):
            main(["new", str(tmpdir), "--database", dburi_sqlite3])
            assert self.subprocess.call.call_args == call(["visualed", tms.Unicode()])

        # fallback to $EDITOR
        with patch("os.environ", {"EDITOR": "ed"}):
            main(["new", str(tmpdir), "--database", dburi_sqlite3])
            assert self.subprocess.call.call_args == call(["ed", tms.Unicode()])

        # Otherwise, vi
        with patch("os.environ", {}):
            main(["new", str(tmpdir), "--database", dburi_sqlite3]) == call(
                ["vi", tms.Unicode()]
            )

        # Prompts should only appear if there is an error reading the migration
        # file, which should not be the case.
        assert self.prompt.call_args_list == []

    def test_it_pulls_message_from_docstring(self, tmpdir):
        def write_migration(argv):
            with open(argv[-1], "w", encoding="utf8") as f:
                f.write('"""\ntest docstring\nsplit over\n\nlines\n"""\n')

        self.subprocess.call = write_migration
        main(["new", str(tmpdir), "--database", dburi_sqlite3])
        names = [n for n in sorted(os.listdir(str(tmpdir))) if n.endswith(".py")]
        assert "test-docstring" in names[0]

    def test_it_prompts_to_reedit_bad_migration(self, tmpdir):
        def write_migration(argv):
            with open(argv[-1], "w", encoding="utf8") as f:
                f.write("this is not valid python!")

        self.subprocess.call = write_migration
        main(["new", str(tmpdir), "--database", dburi_sqlite3])
        prompts = [args[0].lower() for args, kwargs in self.prompt.call_args_list]
        assert "retry editing?" in prompts[0]

    def test_it_defaults_docstring_to_message(self, tmpdir):
        main(
            [
                "new",
                "-b",
                "-m",
                "your ad here",
                str(tmpdir),
                "--database",
                dburi_sqlite3,
            ]
        )
        names = [n for n in sorted(os.listdir(tmpdir)) if n.endswith(".py")]
        with open(os.path.join(str(tmpdir), names[0]), "r", encoding="utf-8") as f:
            assert "your ad here" in f.read()

    def test_it_calls_post_create_command(self, tmpdir):
        self.writeconfig(post_create_command="/bin/ls -l {} {}")
        with freezegun.freeze_time("2001-1-1"):
            main(["new", "-b", str(tmpdir), "--database", dburi_sqlite3])
        is_filename = tms.Str(lambda s: os.path.basename(s).startswith("20010101_01_"))
        assert self.subprocess.call.call_args == call(
            ["/bin/ls", "-l", is_filename, is_filename]
        )

    def test_it_uses_configured_prefix(self, tmpdir):
        self.writeconfig(prefix="foo_")
        main(["new", "-b", "-m", "bar", str(tmpdir), "--database", dburi_sqlite3])
        names = [n for n in sorted(os.listdir(str(tmpdir))) if n.endswith(".py")]
        assert re.match("foo_.*-bar", names[0]) is not None

    def test_it_creates_sql_file(self):
        with migrations_dir(m1="") as tmpdir:
            main(
                [
                    "new",
                    "-b",
                    "-m",
                    "comment",
                    "--sql",
                    tmpdir,
                    "--database",
                    dburi_sqlite3,
                ]
            )
            name = next(n for n in sorted(os.listdir(tmpdir)) if n.endswith(".sql"))
            with open(os.path.join(tmpdir, name), "r", encoding="utf-8") as f:
                assert f.read() == textwrap.dedent(
                    """\
                    -- comment
                    -- depends: m1

                    """
                )

class TestList(TestInteractiveScript):
    def get_output(self, migrations):
        with migrations_dir(**migrations) as tmpdir:
            with patch("sys.stdout", io.StringIO()) as captured:
                main(["list", tmpdir, "--database", dburi_sqlite3])
            return captured.getvalue()

    def test_it_lists_migrations(self):
        output = self.get_output({"m1": "", "m2": ""})
        assert re.search(r"^U\s+m1", output, re.M)
        assert re.search(r"^U\s+m2", output, re.M)

class TestDevelopCommand(TestInteractiveScript):
    def test_it_applies_outstanding_migrations(self):
        with migrations_dir(m1="", m2="") as tmpdir:
            main(["develop", tmpdir, "--database", self.dburi])
            assert self.get_migration_log() == [
                ("m1", "apply"),
                ("m2", "apply"),
            ]

    def test_it_reapplies_last_migration(self):
        with migrations_dir(
            m1='step("CREATE TABLE yoyo_test1 (id INT)", "DROP TABLE yoyo_test1")',
            m2='step("CREATE TABLE yoyo_test2 (id INT)", "DROP TABLE yoyo_test2")',
        ) as tmpdir:
            main(["-b", "apply", tmpdir, "--database", self.dburi])
            assert self.get_migration_log() == [
                ("m1", "apply"),
                ("m2", "apply"),
            ]
            main(["develop", tmpdir, "--database", self.dburi])
            assert self.get_migration_log() == [
                ("m1", "apply"),
                ("m2", "apply"),
                ("m2", "rollback"),
                ("m2", "apply"),
            ]

    def test_it_reapplies_last_n_migrations(self):
        with migrations_dir(
            m1='step("CREATE TABLE yoyo_test1 (id INT)", "DROP TABLE yoyo_test1")',
            m2='step("CREATE TABLE yoyo_test2 (id INT)", "DROP TABLE yoyo_test2")',
        ) as tmpdir:
            main(["-b", "apply", tmpdir, "--database", self.dburi])
            assert self.get_migration_log() == [
                ("m1", "apply"),
                ("m2", "apply"),
            ]
            main(["develop", tmpdir, "-n", "2", "--database", self.dburi])
            assert self.get_migration_log() == [
                ("m1", "apply"),
                ("m2", "apply"),
                ("m2", "rollback"),
                ("m1", "rollback"),
                ("m1", "apply"),
                ("m2", "apply"),
            ]

class TestInitCommand(TestInteractiveScript):
    def test_it_creates_project(self):
        main(["-b", "init", "migrations", "--database", "sqlite://foo"])
        config = (pathlib.Path(self.tmpdir) / "yoyo.ini").read_text()
        assert "database = sqlite://foo" in config
        assert "sources = migrations" in config
        assert (pathlib.Path(self.tmpdir) / "migrations").is_dir()

    def test_it_doesnt_overwrite(self):
        configfile = pathlib.Path(self.tmpdir) / "yoyo.ini"
        configfile.write_text("[existing file]")
        main(["-b", "init", "migrations", "--database", "sqlite://foo"])
        assert "database = sqlite://foo" not in configfile.read_text()
        assert "existing file" in configfile.read_text()
        assert not (pathlib.Path(self.tmpdir) / "migrations").exists()

    def test_it_doesnt_prompt(self):
        with patch("yoyo.scripts.main.prompt_save_config") as prompt_save_config:
            main(["-b", "init", "migrations", "--database", "sqlite://foo"])
            assert prompt_save_config.called is False


#!/usr/bin/env python3
"""Pattoo classes querying the Favorite table."""

# PIP3 imports
from sqlalchemy import and_

# Import project libraries
from pattoo_shared import log
from pattoo.db import db
from pattoo.db.models import Favorite
from pattoo.constants import DbRowFavorite

def idx_exists(idx):
    """Determine whether primary key exists.

    Args:
        idx: idx_favorite

    Returns:
        result: True if exists

    """
    # Initialize key variables
    result = False
    rows = []

    # Get the result
    with db.db_query(20095) as session:
        rows = session.query(Favorite.idx_favorite).filter(
            Favorite.idx_favorite == idx)

    # Return
    for _ in rows:
        result = True
        break
    return bool(result)

def exists(idx_chart, idx_user):
    """Determine whether name exists in the Favorite table.

    Args:
        idx_chart: Chart table index
        idx_user: User table index

    Returns:
        result: Favorite.idx_favorite value

    """
    # Initialize key variables
    result = False
    rows = []

    # Get name from database
    with db.db_query(20010) as session:
        rows = session.query(Favorite.idx_favorite).filter(
            and_(Favorite.idx_chart == idx_chart,
                 Favorite.idx_user == idx_user))

    # Return
    for row in rows:
        result = row.idx_favorite
        break
    return result

def insert_row(row):
    """Create a Favorite table entry.

    Args:
        row: DbRowFavorite object

    Returns:
        None

    """
    # Verify values
    if bool(row) is False or isinstance(row, DbRowFavorite) is False:
        log_message = 'Invalid favorite type being inserted'
        log.log2die(20058, log_message)

    # Insert
    row = Favorite(
        idx_chart=row.idx_chart,
        idx_user=row.idx_user,
        order=row.order,
        enabled=int(bool(row.enabled)),
        )
    with db.db_modify(20056, die=True) as session:
        session.add(row)


import json
from pathlib import Path
from moviepy.editor import VideoFileClip
from utils import read_text

def main():
    vid_path = Path(
        "/Users/orrav/Documents/Data/human-feedback/raw/video/10_most_common_YOGA_MISTAKES_new_students_make_and_how_to_fix_them.mp4"
    )
    text_path = Path(
        "/Users/orrav/Documents/Data/human-feedback/raw/sentiment_text/10_most_common_YOGA_MISTAKES_new_students_make_and_how_to_fix_them.json"
    )
    segments_dir = Path("/Users/orrav/Documents/Data/human-feedback/sentiment_segments")

    vid_segments_dir = segments_dir / "video"
    text_segments_dir = segments_dir / "text"
    vid_segments_dir.mkdir(parents=True, exist_ok=True)
    text_segments_dir.mkdir(parents=True, exist_ok=True)

    narrations = read_text(text_path)

    with VideoFileClip(str(vid_path)) as vid:
        for sentence in narrations:
            start, end = sentence["timestamp"]
            text = sentence["text"]
            sentiment = sentence["sentiment"]
            sub_vid = vid.subclip(start, end)
            segment_name = f"{vid_path.stem}_{start}_{end}"
            vid_segment_path = vid_segments_dir / f"{segment_name}.mp4"
            text_segment_path = text_segments_dir / f"{segment_name}.json"

            sub_vid.write_videofile(str(vid_segment_path))
            with open(text_segment_path, "w") as fp:
                json.dump({"text": text, "sentiment": sentiment}, fp)
        sub_vid.close()
    vid.close()

if __name__ == "__main__":
    main()


import base64
import datetime
import functools
import io
from pathlib import Path
import re
from typing import AbstractSet, Callable, Optional, Tuple

from aiohttp import web
from attr import dataclass
from google.protobuf.message import Message
import jinja2
from PIL import Image, ImageDraw, ImageFont  # type: ignore
import structlog

from .core import AuthorizingUsername, Servicer, TokenMint, Username, token_owner
from .http_glue import HttpTokenGlue
from .protobuf import mvp_pb2

logger = structlog.get_logger()

_HERE = Path(__file__).parent

ARIAL_PATH = _HERE / 'arial.ttf'
WARNOCK_PRO_PATH = _HERE / 'warnock-pro.otf'

@dataclass(frozen=True)
class Style:
    color: Tuple[int, int, int]
    fontpath: Path
    fontsize: int
    underline: bool

    @staticmethod
    def parse(s: str) -> 'Style':
        s = s.lstrip('-')
        m = re.search(r'(?:-|^)([0-9]{1,2})pt\b', s)
        if m:
            fontsize = min(30, int(m.group(1)))
            s = s.replace(m.group(), '', 1)
        else:
            fontsize = 12

        if s == 'lesswrong':
            return Style(color=(0x5f, 0x9b, 0x65), fontpath=WARNOCK_PRO_PATH, fontsize=fontsize, underline=False)

        m = re.search('(?:-|^)(' + '|'.join(COLOR_NAME_TO_COLOR.keys()) + r')\b', s)
        if m:
            color = COLOR_NAME_TO_COLOR[m.group(1)]
            s = s.replace(m.group(), '', 1)
        else:
            color = COLOR_NAME_TO_COLOR['darkgreen']

        return Style(color=color, fontpath=ARIAL_PATH, fontsize=fontsize, underline=True)

COLOR_NAME_TO_COLOR = {
    'red'      : (255, 0  , 0  ),
    'darkgreen': (0  , 128, 0  ),
    'darkblue' : (0  , 0  , 128),
    'black'    : (0  , 0  , 0  ),
    'white'    : (255, 255, 255),
    'plainlink' : (0x0a, 0x58, 0xca),
    'lwlinkgreen': (0x5f, 0x9b, 0x65),
}

@functools.lru_cache(maxsize=256)
def render_text(text: str, style: Style, file_format: str = 'png') -> bytes:
    font = ImageFont.truetype(str(style.fontpath.resolve()), style.fontsize)
    w, h = font.getsize(text)
    if style.underline:
        h += 2
    img = Image.new('RGBA', (w, h), color=(255,255,255,0))
    draw = ImageDraw.Draw(img)
    draw.text((0,0), text, fill=style.color, font=font)
    if style.underline:
        draw.line([(0, h-1), (w, h-1)], fill=style.color)
    buf = io.BytesIO()
    img.save(buf, format=file_format)
    return buf.getvalue()

def stupid_file_response(path: Path) -> web.Response:
    '''For some reason, web.FileResponse sometimes results in a 404 even when the file exists. Dodge that.'''
    if path.is_file():
        return web.Response(status=200, body=path.read_bytes(), content_type='text/html' if path.name.endswith('.html') else 'text/css' if path.name.endswith('.css') else 'text/javascript' if path.name.endswith('.js') else 'application/octet-stream')
    return web.Response(status=404)

class WebServer:
    def __init__(self, servicer: Servicer, elm_dist: Path, token_glue: HttpTokenGlue, clock: Callable[[], datetime.datetime] = datetime.datetime.now) -> None:
        self._servicer = servicer
        self._elm_dist = elm_dist
        self._token_glue = token_glue
        self._clock = clock

        self._jinja = jinja2.Environment( # adapted from https://jinja.palletsprojects.com/en/2.11.x/api/#basics
            loader=jinja2.FileSystemLoader(searchpath=[_HERE/'templates'], encoding='utf-8'),
            autoescape=jinja2.select_autoescape(['html', 'xml']),
        )
        self._jinja.undefined = jinja2.StrictUndefined  # raise exception if a template uses an undefined variable; adapted from https://stackoverflow.com/a/39127941/8877656

    def _get_auth_success(self, auth: Optional[mvp_pb2.AuthToken], req: mvp_pb2.GetSettingsRequest = mvp_pb2.GetSettingsRequest()) -> Optional[mvp_pb2.AuthSuccess]:
        if auth is None:
            return None
        get_settings_response = self._servicer.GetSettings(token_owner(auth), req)
        if get_settings_response.WhichOneof('get_settings_result') != 'ok':
            logger.error('failed to get settings for valid-looking user', data_loss=True, auth=auth, get_settings_response=get_settings_response)
            return None
        return mvp_pb2.AuthSuccess(token=auth, user_info=get_settings_response.ok)

    async def get_static(self, req: web.Request) -> web.StreamResponse:
        filename = req.match_info['filename']
        static_dir = _HERE / 'static'
        return stupid_file_response(static_dir / (static_dir/filename).relative_to(static_dir))

    async def get_wellknown(self, req: web.Request) -> web.StreamResponse:
        path = Path(req.match_info['path'])
        root = Path('/home/public/.well-known')
        try:
            return stupid_file_response(root / ((root/path).absolute().relative_to(root)))
        except Exception:
            raise web.HTTPBadRequest()

    async def get_elm_module(self, req: web.Request) -> web.StreamResponse:
        module = req.match_info['module']
        elmdist = _HERE.parent/'elm'/'dist'
        return stupid_file_response(elmdist / (elmdist/f'{module}.js').relative_to(elmdist))

    async def get_index(self, req: web.Request) -> web.StreamResponse:
        auth = self._token_glue.parse_cookie(req)
        if auth is None:
            return web.HTTPTemporaryRedirect('/welcome')
        else:
            return await self.get_my_stakes(req)

    async def get_fast_bet(self, req: web.Request) -> web.Response:
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('FastBetPage.html').render()
        )

    async def get_welcome(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        auth_success = self._get_auth_success(auth)
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('Welcome.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
            ))

    async def get_create_prediction_page(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        auth_success = self._get_auth_success(auth)
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('CreatePredictionPage.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
            ))

    async def get_view_prediction_page(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        prediction_id = str(req.match_info['prediction_id'])
        get_prediction_resp = self._servicer.GetPrediction(token_owner(auth), mvp_pb2.GetPredictionRequest(prediction_id=prediction_id))
        if get_prediction_resp.WhichOneof('get_prediction_result') == 'error':
            return web.Response(status=404, body=str(get_prediction_resp.error))

        assert get_prediction_resp.WhichOneof('get_prediction_result') == 'prediction'
        prediction = get_prediction_resp.prediction
        auth_success = self._get_auth_success(auth, mvp_pb2.GetSettingsRequest(include_relationships_with_users=[prediction.creator]))
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('ViewPredictionPage.html').render(
                title=f'Prediction: by {datetime.datetime.fromtimestamp(prediction.resolves_at_unixtime).strftime("%Y-%m-%d")}, {prediction.prediction}',
                auth_success_pb_b64=pb_b64(auth_success),
                predictions_pb_b64=pb_b64(mvp_pb2.PredictionsById(predictions={prediction_id: prediction})),
                prediction_id=prediction_id,
            ))

    async def get_prediction_img_embed(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        prediction_id = str(req.match_info['prediction_id'])
        get_prediction_resp = self._servicer.GetPrediction(token_owner(auth), mvp_pb2.GetPredictionRequest(prediction_id=prediction_id))
        if get_prediction_resp.WhichOneof('get_prediction_result') == 'error':
            return web.Response(status=404, body=str(get_prediction_resp.error))

        assert get_prediction_resp.WhichOneof('get_prediction_result') == 'prediction'
        def format_stake_concisely(n_cents: int) -> str:
            return f'${n_cents//100}'
        prediction = get_prediction_resp.prediction
        stake_text = format_stake_concisely(prediction.maximum_stake_cents)

        if prediction.certainty.high == 1:
            confidence_text = f'{round(prediction.certainty.low*100)}%+'
        else:
            confidence_text = f'{round(prediction.certainty.low*100)}-{round(prediction.certainty.high*100)}%'

        if prediction.resolution and prediction.resolution.resolution != mvp_pb2.RESOLUTION_NONE_YET:
            res = prediction.resolution.resolution
            res_text = "correct" if res == mvp_pb2.RESOLUTION_YES else "incorrect" if res == mvp_pb2.RESOLUTION_NO  else "n/a" if res == mvp_pb2.RESOLUTION_INVALID else "???"
            remaining_text = f" (resolved: {res_text})"
        elif prediction.closes_unixtime < self._clock().timestamp():
            remaining_text = " (closed)"
        elif not (prediction.remaining_stake_cents_vs_skeptics == prediction.remaining_stake_cents_vs_believers == prediction.maximum_stake_cents):
            remaining_text = (
                " ("
                + format_stake_concisely(prediction.remaining_stake_cents_vs_skeptics)
                + ("/" + format_stake_concisely(prediction.remaining_stake_cents_vs_believers) if prediction.remaining_stake_cents_vs_believers < prediction.maximum_stake_cents else "")
                + " remain)"
            )
        else:
            remaining_text = ""

        text = f'[bet: {stake_text} at {confidence_text}{remaining_text}]'
        style = Style.parse(req.match_info['style'])

        return web.Response(content_type='image/png', body=render_text(text=text, style=style, file_format='png'))

    async def get_my_stakes(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        auth_success = self._get_auth_success(auth)
        if auth is None:
            return web.HTTPTemporaryRedirect('/login?dest=/my_stakes')
        list_my_stakes_resp = self._servicer.ListMyStakes(token_owner(auth), mvp_pb2.ListMyStakesRequest())
        if list_my_stakes_resp.WhichOneof('list_my_stakes_result') == 'error':
            return web.Response(status=400, body=str(list_my_stakes_resp.error))
        assert list_my_stakes_resp.WhichOneof('list_my_stakes_result') == 'ok'
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('MyStakesPage.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
                predictions_pb_b64=pb_b64(list_my_stakes_resp.ok),
            ))

    async def get_username(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        username = Username(str(req.match_info['username']))
        auth_success = self._get_auth_success(auth, req=mvp_pb2.GetSettingsRequest(include_relationships_with_users=[username]))
        list_predictions_resp = self._servicer.ListPredictions(token_owner(auth), mvp_pb2.ListPredictionsRequest(creator=username))
        predictions = list_predictions_resp.ok
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('ViewUserPage.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
                who=username,
                predictions_pb_b64=pb_b64(predictions),
            ))

    async def get_settings(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        auth_success = self._get_auth_success(auth)
        if auth is None:
            return web.HTTPTemporaryRedirect('/login?dest=/settings')
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('SettingsPage.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
            ))

    async def get_login(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        auth_success = self._get_auth_success(auth)
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('LoginPage.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
            ))

    async def accept_invitation(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        auth_success = self._get_auth_success(auth)
        nonce = str(req.match_info['nonce'])
        check_invitation_resp = self._servicer.CheckInvitation(token_owner(auth), mvp_pb2.CheckInvitationRequest(nonce=nonce))
        if check_invitation_resp.WhichOneof('check_invitation_result') == 'error':
            return web.HTTPBadRequest(reason=str(check_invitation_resp.error))
        assert check_invitation_resp.WhichOneof('check_invitation_result') == 'ok'
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('AcceptInvitationPage.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
                recipient=check_invitation_resp.ok.recipient,
                inviter=check_invitation_resp.ok.inviter,
                nonce=nonce,
            ))

    async def signup(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        auth_success = self._get_auth_success(auth)
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('SignupPage.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
            ))

    async def init_user(self, req: web.Request) -> web.Response:
        auth = self._token_glue.parse_cookie(req)
        auth_success = self._get_auth_success(auth)
        code = str(req.match_info['code'])
        return web.Response(
            content_type='text/html',
            body=self._jinja.get_template('InitUserPage.html').render(
                auth_success_pb_b64=pb_b64(auth_success),
                proof_of_email_pb_b64=pb_b64(mvp_pb2.ProofOfEmail.FromString(base64.urlsafe_b64decode(code))),
            ))

    def add_to_app(self, app: web.Application) -> None:

        self._token_glue.add_to_app(app)

        app.router.add_get('/', self.get_index)
        app.router.add_get('/.well-known/{path:.*}', self.get_wellknown)
        app.router.add_get('/static/{filename}', self.get_static)
        app.router.add_get('/elm/{module}.js', self.get_elm_module)
        app.router.add_get('/fast', self.get_fast_bet)
        app.router.add_get('/welcome', self.get_welcome)
        app.router.add_get('/new', self.get_create_prediction_page)
        app.router.add_get('/p/{prediction_id:[0-9]+}', self.get_view_prediction_page)
        app.router.add_get('/p/{prediction_id:[0-9]+}/embed{style}.png', self.get_prediction_img_embed)
        app.router.add_get('/my_stakes', self.get_my_stakes)
        app.router.add_get('/settings', self.get_settings)
        app.router.add_get('/login', self.get_login)
        app.router.add_get('/invitation/{nonce}/accept', self.accept_invitation)
        app.router.add_get('/verify_email/{code}', self.init_user)
        app.router.add_get('/signup', self.signup)
        app.router.add_get('/username/{username:[a-zA-Z0-9_-]+}', self.get_username)
        app.router.add_get('/u/{username:[a-zA-Z0-9_-]+}', self.get_username)
        app.router.add_get('/{username:[a-zA-Z0-9_-]+}', self.get_username)

def _reserved_toplevel_path_segments() -> AbstractSet[str]:
    server = WebServer(servicer=None, elm_dist=None, token_glue=HttpTokenGlue(token_mint=TokenMint(secret_key=b'')))  # type: ignore
    app = web.Application()
    server.add_to_app(app)
    return {
        path.lstrip('/').split('/')[0]
        for path in (r.get_info().get('path') for r in app.router.routes())
        if path
    }
RESERVED_TOPLEVEL_PATH_SEGMENTS = _reserved_toplevel_path_segments()

def pb_b64(message: Optional[Message]) -> Optional[str]:
    if message is None:
        return None
    return base64.b64encode(message.SerializeToString()).decode('ascii')


# Detecting RedLeaves for Volatility
#
# LICENSE
# Please refer to the LICENSE.txt in the https://github.com/JPCERTCC/aa-tools/
#
# How to use:
# 1. cd "Volatility Folder"
# 2. mv redleavesscan.py volatility/plugins/malware
# 3. python vol.py [ redleavesscan | redleavesconfig ] -f images.mem --profile=Win7SP1x64

import volatility.plugins.taskmods as taskmods
import volatility.win32.tasks as tasks
import volatility.utils as utils
import volatility.debug as debug
import volatility.plugins.malware.malfind as malfind
import volatility.plugins.malware as malware
import re
from struct import pack, unpack, unpack_from, calcsize

try:
    import yara
    has_yara = True
except ImportError:
    has_yara = False

redleaves_sig = {
    'namespace1' : 'rule RedLeaves { \
                    strings: \
                       $v1 = "red_autumnal_leaves_dllmain.dll" \
                       $b1 = { FF FF 90 00 } \
                    condition: $v1 and $b1 at 0}',
    'namespace2' : 'rule Himawari { \
                    strings: \
                       $h1 = "himawariA" \
                       $h2 = "himawariB" \
                       $h3 = "HimawariDemo" \
                    condition: $h1 and $h2 and $h3}',
    'namespace3' : 'rule Lavender { \
                    strings: \
                       $l1 = {C7 ?? ?? 4C 41 56 45} \
                       $l2 = {C7 ?? ?? 4E 44 45 52} \
                    condition: $l1 and $l2}',
    'namespace4' : 'rule Armadill { \
                    strings: \
                       $a1 = {C7 ?? ?? 41 72 6D 61 } \
                       $a2 = {C7 ?? ?? 64 69 6C 6C } \
                    condition: $a1 and $a2}',
    'namespace5' : 'rule zark20rk { \
                    strings: \
                       $a1 = {C7 ?? ?? 7A 61 72 6B } \
                       $a2 = {C7 ?? ?? 32 30 72 6B } \
                    condition: $a1 and $a2}'
}

CONF_PATTERNS = [["RedLeaves", re.compile("\x68\x88\x13\x00\x00\xFF", re.DOTALL)],
                 ["Himawari", re.compile("\x68\x70\x03\x00\x00\xBF", re.DOTALL)],
                 ["Lavender", re.compile("\x68\x70\x03\x00\x00\xBF", re.DOTALL)],
                 ["Armadill", re.compile("\x68\x70\x03\x00\x00\xBF", re.DOTALL)],
                 ["zark20rk", re.compile("\x68\x70\x03\x00\x00\x8D", re.DOTALL)],
                 ]

CONNECT_MODE = {1 : 'TCP' , 2 : 'HTTP', 3 : 'HTTPS', 4 : 'TCP and HTTP'}

class patternCheck():
    def __init__(self, malname, data):
        for c_name, c_pt in CONF_PATTERNS:
            if str(malname) in c_name:
                if re.search(c_pt, data):
                    self.m_conf = re.search(c_pt, data)
                    break
            else:
                self.m_conf = None

class vad_ck():
    def get_vad_end(self, task, address):
        for vad in task.VadRoot.traverse():
            if address == vad.Start:
                return vad.End+1

        return None

class redleavesScan(taskmods.DllList):
    "Detect processes infected with redleaves malware"

    @staticmethod
    def is_valid_profile(profile):
        return (profile.metadata.get('os', 'unknown') == 'windows'), profile.metadata.get('memory_model', '32bit')

    def get_vad_base(self, task, address):
        for vad in task.VadRoot.traverse():
            if address >= vad.Start and address < vad.End:
                return vad.Start

        return None

    def calculate(self):

        if not has_yara:
            debug.error("Yara must be installed for this plugin")

        addr_space = utils.load_as(self._config)

        os , memory_model = self.is_valid_profile(addr_space.profile)
        if not os:
            debug.error("This command does not support the selected profile.")

        rules = yara.compile(sources = redleaves_sig)

        for task in self.filter_tasks(tasks.pslist(addr_space)):
            scanner = malfind.VadYaraScanner(task = task, rules = rules)

            for hit, address in scanner.scan():

                vad_base_addr = self.get_vad_base(task, address)

                yield task, vad_base_addr, hit, memory_model
                break

    def render_text(self, outfd, data):

        self.table_header(outfd, [("Name", "20"),
                                  ("PID", "8"),
                                  ("Data VA", "[addrpad]"),
                                  ("Malware Name", "13")])

        for task, start, malname, memory_model in data:
            self.table_row(outfd, task.ImageFileName, task.UniqueProcessId, start, malname)

class redleavesConfig(redleavesScan):
    "Parse the RedLeaves configuration"

    def parse_config(self, cfg_blob, cfg_sz, cfg_addr, outfd):
        server1 = unpack_from('<64s', cfg_blob, 0x0)[0]
        server2 = unpack_from('<64s', cfg_blob, 0x40)[0]
        server3 = unpack_from('<64s', cfg_blob, 0x80)[0]
        port = unpack_from('<I', cfg_blob, 0xC0)[0]
        mode = unpack_from('<I', cfg_blob, 0x1D0)[0]
        id = unpack_from('<64s', cfg_blob, 0x1E4)[0]
        mutex = unpack_from('<550s', cfg_blob, 0x500)[0]
        injection = unpack_from('<104s', cfg_blob, 0x726)[0]
        rc4key = unpack_from('<10s', cfg_blob, 0x82A)[0]

        ## config write
        outfd.write("[Config Info]\n")
        outfd.write("Server1\t\t\t: %s\n" % server1.split('\0')[0])
        outfd.write("Server2\t\t\t: %s\n" % server2.split('\0')[0])
        outfd.write("Server3\t\t\t: %s\n" % server3.split('\0')[0])
        outfd.write("Port\t\t\t: %i\n" % port)
        outfd.write("Mode\t\t\t: %i (%s)\n" % (mode, CONNECT_MODE[mode]))
        outfd.write("ID\t\t\t: %s\n" % id.split('\0')[0])
        outfd.write("Mutex\t\t\t: %s\n" % mutex.replace('\0',''))
        outfd.write("Injection Process\t: %s\n" % injection.replace('\0',''))
        outfd.write("RC4 Key\t\t\t: %s\n" % rc4key.split('\0')[0])

    def parse_config_himawari(self, cfg_blob, cfg_sz, cfg_addr, outfd):
        server1 = unpack_from('<64s', cfg_blob, 0x4)[0]
        server2 = unpack_from('<64s', cfg_blob, 0x44)[0]
        server3 = unpack_from('<64s', cfg_blob, 0x84)[0]
        server4 = unpack_from('<64s', cfg_blob, 0xC4)[0]
        port = unpack_from('<I', cfg_blob, 0x104)[0]
        proxy = unpack_from('<64s', cfg_blob, 0x10C)[0]
        puser = unpack_from('<64s', cfg_blob, 0x14C)[0]
        ppass = unpack_from('<64s', cfg_blob, 0x18C)[0]
        sleep1 = unpack_from('<I', cfg_blob, 0x1D0)[0]
        sleep2 = unpack_from('<I', cfg_blob, 0x1D4)[0]
        mode = unpack_from('<I', cfg_blob, 0x1D8)[0]
        id = unpack_from('<64s', cfg_blob, 0x1E0)[0]
        mutex = unpack_from('<62s', cfg_blob, 0x224)[0]
        sleep3 = unpack_from('<I', cfg_blob, 0x220)[0]
        ua = unpack_from('<260s', cfg_blob, 0x262)[0]
        key = unpack_from('<10s', cfg_blob, 0x366)[0]

        ## config write
        outfd.write("[Config Info]\n")
        outfd.write("Server1\t\t\t: %s\n" % server1.split('\0')[0])
        outfd.write("Server2\t\t\t: %s\n" % server2.split('\0')[0])
        outfd.write("Server3\t\t\t: %s\n" % server3.split('\0')[0])
        outfd.write("Server4\t\t\t: %s\n" % server4.split('\0')[0])
        outfd.write("Port\t\t\t: %i\n" % port)
        outfd.write("Mode\t\t\t: %i (%s)\n" % (mode, CONNECT_MODE[mode]))
        outfd.write("ID\t\t\t: %s\n" % id.split('\0')[0])
        outfd.write("Mutex\t\t\t: %s\n" % mutex.replace('\0',''))
        outfd.write("Key\t\t\t: %s\n" % key.split('\0')[0])
        outfd.write("UserAgent\t\t: %s\n" % ua.split('\0')[0])
        outfd.write("Proxy\t\t\t: %s\n" %  proxy.split('\0')[0])
        outfd.write("Username\t\t: %s\n" %  puser.split('\0')[0])
        outfd.write("Password\t\t: %s\n" %  ppass.split('\0')[0])

    def render_text(self, outfd, data):

        delim = '-' * 70

        for task, start, malname, memory_model in data:
            proc_addr_space = task.get_process_address_space()

            data = proc_addr_space.zread(start, vad_ck().get_vad_end(task, start)-start)

            loadp = patternCheck(malname ,data)
            if loadp.m_conf is None:
                continue

            offset_conf = loadp.m_conf.start()

            if str(malname) in "RedLeaves":
                config_size = 2100

                offset_conf -= 1
                while data[offset_conf]!="\xC7" and data[offset_conf]!="\xBE" and data[offset_conf]!="\xBF":
                    offset_conf -= 1
                if data[offset_conf]!="\xC7" and data[offset_conf]!="\xBE" and data[offset_conf]!="\xBF":
                    continue
                if data[offset_conf]=="\xC7" and data[offset_conf + 1]!="\x85" and data[offset_conf + 1]!="\x45":
                    offset_conf -= 6

                # get address
                if data[offset_conf]=="\xC7" and data[offset_conf + 1]!="\x85":
                    (config_addr, ) = unpack("=I", data[offset_conf+3:offset_conf+7])
                elif data[offset_conf]=="\xC7" and data[offset_conf + 1]=="\x85":
                    (config_addr, ) = unpack("=I", data[offset_conf+6:offset_conf+10])
                else:
                    (config_addr, ) = unpack("=I", data[offset_conf+1:offset_conf+5])
                outfd.write("Process: %8X\n\n" % offset_conf)

                if config_addr < start:
                    continue
                outfd.write("{0}\n".format(delim))
                outfd.write("RedLeaves Settings:\n\n")
                config_addr -= start
                config_data = data[config_addr:config_addr+config_size]
                outfd.write("Process: %s (%d)\n\n" % (task.ImageFileName, task.UniqueProcessId))
                if len(config_data) > 0:
                    self.parse_config(config_data, config_size, config_addr, outfd)

            if str(malname) in ["Himawari", "Lavender", "Armadill", "zark20rk"]:
                offset_conf += 6
                if str(malname) in ["zark20rk"]:
                    offset_conf += 6
                config_size = 880

                # get address
                (config_addr, ) = unpack("=I", data[offset_conf:offset_conf+4])
                outfd.write("config addr: %08X\n\n" % config_addr)

                if config_addr < start:
                    continue

                outfd.write("{0}\n".format(delim))
                outfd.write("%s Settings:\n\n" % str(malname))
                config_addr -= start
                config_data = data[config_addr:config_addr+config_size]
                outfd.write("Process: %s (%d)\n\n" % (task.ImageFileName, task.UniqueProcessId))
                if len(config_data) > 0:
                    self.parse_config_himawari(config_data, config_size, config_addr, outfd)


# -*- coding: utf-8 -*-
import json
import re
from typing import TYPE_CHECKING

from flask import request
from mcdreforged.api.event import LiteralEvent

if TYPE_CHECKING:
    from .factory import Factory

class Info:
    def __init__(self, factory: 'Factory', data: dict):
        self.__server = factory.mcdr_server
        self.__config = factory.config
        self.__bot = factory.bot
        self.__data = data

        # parse
        # global
        self.raw = None
        self.time = None
        self.post_type = None
        self.source_type = None
        self.source_id = None
        self.server = None

        # message
        self.message_id = None
        self.user_id = None
        self.source_sub_type = None
        self.raw_content = None
        self.content = None

        # notice
        self.notice_type = None

        # group_ban
        self.duration = None
        self.operator_id = None
        self.notice_sub_type = None

        # group_upload
        self.file_busid = None
        self.file_id = None
        self.file_name = None
        self.file_size = None

        self.__server.logger.debug(
            f'接收上报数据 {json.dumps(self.__data, indent=4, ensure_ascii=False)}'
        )

        # shutdown:
        if 'shutdown' in self.__data.keys() and self.__data['shutdown'] is True:
            self.__shutdown()
            return

        # global
        self.raw = self.__data
        self.time = self.__data['time']
        self.post_type = self.__data['post_type']
        if 'server' in self.__data.keys():
            self.server = self.__data['server']

        # message and notice
        if self.post_type == 'message':
            self.message_parse()
        elif self.post_type == 'notice':
            self.notice_parse()

    def message_parse(self):
        self.message_id = self.__data['message_id']
        self.user_id = self.__data['user_id']
        self.source_type = self.__data['message_type']
        self.source_sub_type = self.__data['sub_type']
        self.raw_content = self.__data['message']
        self.content_parse()
        if self.source_type == 'private':
            self.source_id = self.__data['user_id']
        elif self.source_type == 'group':
            self.source_id = self.__data['group_id']

        # call on_qq_info
        self.__server.dispatch_event(
            LiteralEvent('cool_q_api.on_qq_info'),
            (self, self.__bot)
        )
        # call on_qq_command
        if self.content.startswith(self.__config['command_prefix']):
            self.__server.dispatch_event(
                LiteralEvent('cool_q_api.on_qq_command'),
                (self, self.__bot)
            )

    def notice_parse(self):
        self.notice_type = self.__data['notice_type']
        # group_ban
        if self.notice_type == 'group_ban':
            self.source_type = 'group'
            self.source_id = self.__data['group_id']
            self.user_id = self.__data['user_id']
            self.duration = self.__data['duration']
            self.operator_id = self.__data['operator_id']
            self.notice_sub_type = self.__data['sub_type']

        # group_upload
        elif self.notice_type == 'group_upload':
            self.source_type = 'group'
            self.source_id = self.__data['group_id']
            self.user_id = self.__data['user_id']
            self.file_busid = self.__data['file']['busid']
            self.file_id = self.__data['file']['id']
            self.file_name = self.__data['file']['name']
            self.file_size = self.__data['file']['size']

        # group_increase
        elif self.notice_type == 'group_increase':
            self.source_type = 'group'
            self.source_id = self.__data['group_id']
            self.user_id = self.__data['user_id']
            self.operator_id = self.__data['operator_id']
            self.notice_sub_type = self.__data['sub_type']

        # group_decrease
        elif self.notice_type == 'group_decrease':
            self.source_type = 'group'
            self.source_id = self.__data['group_id']
            self.user_id = self.__data['user_id']
            self.operator_id = self.__data['operator_id']
            self.notice_sub_type = self.__data['sub_type']

        # call on_qq_notice
        self.__server.dispatch_event(
            LiteralEvent('cool_q_api.on_qq_notice'),
            (self, self.__bot,)
        )

    def content_parse(self):
        content = self.__data['raw_message']
        content = re.sub(r'\[CQ:image,file=.*?]', '[图片]', content)
        content = re.sub(r'\[CQ:share,file=.*?]', '[链接]', content)
        content = re.sub(r'\[CQ:face,id=.*?]', '[表情]', content)
        content = re.sub(r'\[CQ:record,file=.*?]', '[语音]', content)
        content = content.replace('CQ:at,qq=', '@')
        self.content = content

    @staticmethod
    def __shutdown():
        func = request.environ.get('werkzeug.server.shutdown')
        if func is None:
            raise RuntimeError
        func()

    def __getitem__(self, item):
        return self.raw[item]


import attr
import yaml
from icecream import ic

@attr.s(frozen=True)
class MLConfig:
    # Number of classes
    N_CLASSES = attr.ib()
    # Number of input channels (e.g. RGB)
    IN_CHANNELS = attr.ib()
    # Patch size
    WINDOW_SIZE = attr.ib()
    # Name of the architecture
    NET_NAME = attr.ib()
    # pretrain on ImageNet (UNet & LinkNet & SegNet only)
    PRETRAIN = attr.ib()
    # Normalize  data with RGB mean and std
    MEAN_STD = attr.ib()
    # Activate dropout in the network (for bayesian purpose)
    DROPOUT = attr.ib()
    # Train on a subset of data: 1 for full train set, 0 for none of it
    SUB_TRAIN = attr.ib()

    # Train parameters
    EPOCHS = attr.ib()
    OPTIM_BASELR = attr.ib()
    OPTIM_STEPS = attr.ib()
    # Default epoch size is 10 000 samples
    EPOCH_SIZE = attr.ib()
    # Number of threads to use during training
    WORKERS = attr.ib()
    # Number of samples in a mini-batch per GPU
    BATCH_SIZE = attr.ib()
    # Weight the loss for class balancing
    WEIGHTED_LOSS = attr.ib()
    # Data augmentation (flip vertically and horizontally)
    TRANSFORMATION = attr.ib()
    # Heavy augmentation (rotation, shifting,...)
    HEAVY_AUG = attr.ib()
    # Color jittering
    COL_JIT = attr.ib()
    # Keeps x data for validation. If the value is 1,
    # train and test on the full dataset (ie no validation set)
    test_size = attr.ib()
    # Only for interactivity: True enables distance transform to dilate annotations
    DISTANCE_TRANSFORM = attr.ib()
    GUIDED_FILTER = attr.ib()

    # Test parameters
    # stride at test time
    STRIDE = attr.ib()
    # Number of threads to use during testing. Has to be
    # lower than at training since we now load full images.
    TEST_WORKERS = attr.ib()

    # Interactivity
    SAVE_FOLDER = attr.ib()
    PATH_MODELS = attr.ib()

    DISIR = attr.ib()
    DISCA = attr.ib()
    CL_ONLY_NEW_POINTS = attr.ib()
    CL_LR = attr.ib()
    CL_STEPS = attr.ib()
    CL_REG = attr.ib()
    CL_LAMBDA = attr.ib()
    CL_DIST_TRANS = attr.ib()
    ENCODING_SIZE = attr.ib()
    N_CLICKS = attr.ib()  # number of simulated clicks
    CL_SEQUENTIAL_LEARNING = attr.ib()
    CL_FULL_PRED = attr.ib()  # learn on final full prediction once the annotation process is complete

    #### Active Learning
    BUDGET = attr.ib() # Number of annotations per patch
    AL_DISCA = attr.ib()
    """
    SAMPLING_STRATEGY: Strategy to sample the annotations during evaluation.
        - random (Sample randomly in the mistake areas.)
        - max (Sample the center of the largest mistake areas.)
        - entropy (Sample in the zone maximising the entropy of the prediction in the mistake area)
        - confidnet (Sample in the zone minimizing the confidence of confidnet on the prediction in the mistake area)
        - mcdropout
    """
    SAMPLING_STRATEGY = attr.ib()

def config_factory(config_file: str) -> MLConfig:
    """
    parse the config file and instanciate the config object
    """
    with open(config_file, "rb") as f:
        params = yaml.safe_load(f)
        ic(params)

    return MLConfig(**params)


from collections import defaultdict

from aoc_input import AOCInput

INP = AOCInput(9)
MARBLES = int(INP.value.split()[6])
PLAYERS = int(INP.value.split()[0])

def part_a():
    return solve(MARBLES)

# The Marble class is really a circular linked list. It gives us O(1) inserts
# and O(1) removes, which is something that Python's list, which is implemented
# using arrays, doesn't give us (instead, inserts and removes are both O(n)).
# This problem is minor for part A but huge for part B.
class Marble:
    def __init__(self, num, left=None, right=None):
        self.num = num
        self.left = left
        self.right = right

        if self.left is not None:
            self.left.right = self
        if self.right is not None:
            self.right.left = self

    def remove(self):
        assert self.right is not None
        assert self.left is not None
        self.right.left = self.left
        self.left.right = self.right

# Note: this algorithm isn't as fast as I'd like (part B takes several seconds). I feel like there
# must be some underlying pattern which I haven't noticed which eliminates the need for a list of any kind.
def solve(max_marble):
    current = Marble(0)
    current.left = current  # make it
    current.right = current  # a loop!

    player = 0
    score = defaultdict(int)
    for marble in range(1, max_marble + 1):
        if marble % 23 == 0:
            score[player] += marble
            for _ in range(7):
                current = current.left
            score[player] += current.num
            current = current.right
            current.left.remove()
        else:
            current = current.right
            current = Marble(marble, left=current, right=current.right)

        player = (player + 1) % PLAYERS

    return max(score.values())

def part_b():
    return solve(100 * MARBLES)

if __name__ == '__main__':
    print(part_a())
    print(part_b())


from __future__ import annotations

import math
import numpy as np

from typing import Union
import parselmouth

from ...pipeline.Node import Node
from parselmouth.praat import call
from .VoicelabNode import VoicelabNode

###########################################################################
#                                                                         #
#  Praat Script Syllable Nuclei                                           #
#  Copyright (C) 2008  Nivja de Jong and Ton Wempe                        #
#                                                                         #
#    This program is free software: you can redistribute it and/or modify #
#    it under the terms of the GNU General Public License as published by #
#    the Free Software Foundation, either version 3 of the License, or    #
#    (at your option) any later version.                                  #
#                                                                         #
#    This program is distributed in the hope that it will be useful,      #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of       #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
#    GNU General Public License for more details.                         #
#                                                                         #
#    You should have received a copy of the GNU General Public License    #
#    along with this program.  If not, see http://www.gnu.org/licenses/   #
#                                                                         #
###########################################################################
#
# modified 2010.09.17 by Hugo Quené, Ingrid Persoon, & Nivja de Jong
# Overview of changes:
# + change threshold-calculator: rather than using median, use the almost maximum
#     minus 25dB. (25 dB is in line with the standard setting to detect silence
#     in the "To TextGrid (silences)" function.
#     Almost maximum (.99 quantile) is used rather than maximum to avoid using
#     irrelevant non-speech sound-bursts.
# + add silence-information to calculate articulation rate and ASD (average syllable
#     duration.
#     NB: speech rate = number of syllables / total time
#         articulation rate = number of syllables / phonation time
# + remove max number of syllable nuclei
# + refer to objects by unique identifier, not by name
# + keep track of all created intermediate objects, select these explicitly,
#     then Remove
# + provide summary output in Info window
# + do not save TextGrid-file but leave it in Object-window for inspection
#     (if requested in startup-form)
# + allow Sound to have starting time different from zero
#      for Sound objects created with Extract (preserve times)
# + programming of checking loop for self.args['mindip'] adjusted
#      in the orig version, precedingtime was not modified if the peak was rejected !!
#      var precedingtime and precedingint renamed to currenttime and currentint
#
# + bug fixed concerning summing total pause, feb 28th 2011

# Translated to Python in 2019 by David Feinberg
# - Removed unused varaibles
# - Removed old comments

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

# counts syllables of all sound utterances in a directory
# NB unstressed syllables are sometimes overlooked
# NB filter sounds that are quite noisy beforehand
# NB use Silence threshold (dB) = -25 (or -20?)
# NB use Minimum dip between peaks (dB) = between 2-4 (you can first try;
#                                                      For clean and filtered: 4)

class MeasureSpeechRateNode(VoicelabNode):
    """Measure Speech Rate using the script found `here <https://sites.google.com/site/speechrate/Home/praat-script-syllable-nuclei-v2>`_.

    Arguments
    ---------

    self.args: dict
        Dictionary of values passed into the node.

        self.args['silencedb']: Union[float, int]
            The threshold for silence. Decibels are relative to 90.9691 dB, so we use negative dB values.
        self.args['mindib']: Union[float, int]
            The minimum dip between peaks
        self.args['minpause']: Union[float, int]
            The minimum pause duration

"""
    def __init__(self, *args, **kwargs):
        """
        Args:
            *args:
            **kwargs:
        """
        super().__init__(*args, **kwargs)

        self.args = {"silencedb": -25.0,  # negative integer
                     "mindip": 4.0,  # positive integer
                     "minpause": 0.3  # Positive number
                     }

    def process(self):
        """Measure speechrate.

        Returns
        -------
        A dictionary with the following keys (or an error message):

            - "Number of Syllables": voicedcount
                str | Union[float, int, str]
            - "Number of Pauses": npause
                str | Union[float, int, str]
            - "Duratrion(s)": originaldur
                str | Union[float, int, str]
            - "Phonation Time(s)": intensity_duration
                str | Union[float, int, str]
            - "speechrate(Number of Syllables / Duration)": speakingrate
                str | Union[float, int, str]
            - "Articulation Rate(Number of Syllables / phonationtime)": articulationrate,
                str | Union[float, int, str]
            - "ASD(Speaking Time / Number of Syllables)": asd
                str | Union[float, int, str]

        """
        try:
            file_path: str = self.args["file_path"]
            signal, sampling_rate = self.args['voice']
            sound: parselmouth.Sound = parselmouth.Sound(signal, sampling_rate)
            snr = call(sound.to_harmonicity(), "Get mean", 0, 0)
            if snr < 60:
                self.args["mindip"] = 2
            originaldur = sound.duration
            intensity = sound.to_intensity(50)
            start = call(intensity, "Get time from frame number", 1)
            nframes = call(intensity, "Get number of frames")
            end = call(intensity, "Get time from frame number", nframes)
            min_intensity = call(intensity, "Get minimum", 0, 0, "Parabolic")
            max_intensity = call(intensity, "Get maximum", 0, 0, "Parabolic")

            # get .99 quantile to get maximum (without influence of non-speech sound bursts)
            max_99_intensity = call(intensity, "Get quantile", 0, 0, 0.99)

            # estimate Intensity threshold
            threshold = max_99_intensity + self.args["silencedb"]
            threshold2 = max_intensity - max_99_intensity
            threshold3 = self.args["silencedb"] - threshold2
            if threshold < min_intensity:
                threshold = min_intensity

            # get pauses (silences) and speakingtime
            textgrid = call(
                intensity,
                "To TextGrid (silences)",
                threshold3,
                self.args["minpause"],
                0.1,
                "silent",
                "sounding",
            )
            silencetier = call(textgrid, "Extract tier", 1)
            silencetable = call(silencetier, "Down to TableOfReal", "sounding")
            npauses = call(silencetable, "Get number of rows")
            speakingtot = 0
            for ipause in range(npauses):
                pause = ipause + 1
                beginsound = call(silencetable, "Get value", pause, 1)
                endsound = call(silencetable, "Get value", pause, 2)
                speakingdur = endsound - beginsound
                speakingtot += speakingdur

            intensity_matrix = call(intensity, "Down to Matrix")
            sound_from_intensity_matrix = call(intensity_matrix, "To Sound (slice)", 1)
            # use total duration, not end time, to find out duration of intdur (intensity_duration)
            # in order to allow nonzero starting times.
            intensity_duration = call(sound_from_intensity_matrix, "Get total duration")
            intensity_max = call(
                sound_from_intensity_matrix, "Get maximum", 0, 0, "Parabolic"
            )
            point_process = call(
                sound_from_intensity_matrix,
                "To PointProcess (extrema)",
                "Left",
                "yes",
                "no",
                "Sinc70",
            )
            # estimate peak positions (all peaks)
            numpeaks = call(point_process, "Get number of points")
            t = [
                call(point_process, "Get time from index", i + 1)
                for i in range(numpeaks)
            ]

            # fill array with intensity values
            timepeaks = []
            peakcount = 0
            intensities = []
            for i in range(numpeaks):
                value = call(
                    sound_from_intensity_matrix, "Get value at time", t[i], "Cubic"
                )
                if value > threshold:
                    peakcount += 1
                    intensities.append(value)
                    timepeaks.append(t[i])

            # fill array with valid peaks: only intensity values if preceding
            # dip in intensity is greater than self.args['mindip']
            validpeakcount = 0
            currenttime = timepeaks[0]
            currentint = intensities[0]
            validtime = []

            for p in range(peakcount - 1):
                following = p + 1
                followingtime = timepeaks[p + 1]
                dip = call(
                    intensity, "Get minimum", currenttime, timepeaks[p + 1], "None"
                )
                diffint = abs(currentint - dip)
                if diffint > self.args["mindip"]:
                    validpeakcount += 1
                    validtime.append(timepeaks[p])
                currenttime = timepeaks[following]
                currentint = call(
                    intensity, "Get value at time", timepeaks[following], "Cubic"
                )

            # Look for only voiced parts
            pitch = sound.to_pitch_ac(
                0.02, 30, 4, False, 0.03, 0.25, 0.01, 0.35, 0.25, 450
            )
            voicedcount: int = 0
            voicedpeak = []

            for time in range(validpeakcount):
                querytime = validtime[time]
                whichinterval = call(textgrid, "Get interval at time", 1, querytime)
                whichlabel = call(textgrid, "Get label of interval", 1, whichinterval)
                value = pitch.get_value_at_time(querytime)
                if not math.isnan(value):
                    if whichlabel == "sounding":
                        voicedcount += 1
                        voicedpeak.append(validtime[time])

            # calculate time correction due to shift in time for Sound object versus
            # intensity object
            timecorrection = originaldur / intensity_duration

            # Insert voiced peaks in TextGrid
            call(textgrid, "Insert point tier", 1, "syllables")
            for i in range(len(voicedpeak)):
                position = voicedpeak[i] * timecorrection
                call(textgrid, "Insert point", 1, position, "")

            # return results
            speakingrate = voicedcount / originaldur
            articulationrate = voicedcount / speakingtot
            npause = npauses - 1
            if voicedcount != 0:
                asd = speakingtot / voicedcount
            else:
                asd = np.nan

            return {
                "Number of Syllables": voicedcount,
                "Number of Pauses": npause,
                "Duratrion(s)": originaldur,
                "Phonation Time(s)": intensity_duration,
                "speechrate(Number of Syllables / Duration)": speakingrate,
                "Articulation Rate(Number of Syllables / phonationtime)": articulationrate,
                "ASD(Speaking Time / Number of Syllables)": asd,
            }
        except Exception as e:
            return {
                "Number of Syllables": str(e),
                "Number of Pauses": str(e),
                "Duratrion(s)": str(e),
                "Phonation Time(s)": str(e),
                "speechrate(Number of Syllables / Duration)": str(e),
                "Articulation Rate(Number of Syllables / phonationtime)": str(e),
                "ASD(Speaking Time / Number of Syllables)": str(e),
            }


import math

import mujoco
import mujoco_viewer
import time

model = mujoco.MjModel.from_xml_path("/home/griffin/Documents/mujoco/doq_vis/doq.xml")
data = mujoco.MjData(model)

# create the viewer object
viewer = mujoco_viewer.MujocoViewer(model, data)

# simulate and render
mujoco.mj_step(model,data)
t_start = time.time()

data.qpos[2] = 0.5
for _ in range(100000):
    if viewer.is_alive:
        t = time.time()-t_start

        mujoco.mj_step(model,data)
        # for i in range(7, 14):
        #     data.qpos[i] = 0.5 * math.sin(0.5 * t)

        # data.qpos[13] = 0.5 * math.sin(0.5 * t)

        mujoco.mj_kinematics(model, data)
        viewer.render()
    else:
        break

mujoco.mj_deleteData(data)
mujoco.mj_deleteModel(model)

# close
# viewer.close()


import setuptools, sys, os

extra_compile_args = []
extra_link_args = []
library_dirs = []
libraries = []

if os.environ.get("PYODIDE"):
    library_dirs = ["freetype/build", "Chipmunk2D/build/src"]
    libraries = ["glfw3", "freetype", "chipmunk"]

elif sys.platform == "win32":
    libraries = [
        "glfw3", "opengl32", "kernel32", "user32", "gdi32", "winspool", "shell32",
        "ole32", "oleaut32", "uuid", "comdlg32", "advapi32", "freetype", "chipmunk"
    ]

    library_dirs = [
        "glfw/build/src/Release", "freetype/build/Release",
        "Chipmunk2D/build/src/Release"
    ]

elif sys.platform == "linux":
    libraries = [
        "glfw3", "GL", "m", "X11", "pthread", "Xi", "Xrandr", "dl", "rt", "png",
        "freetype", "z", "chipmunk"
    ]

    library_dirs = ["glfw/build/src", "freetype/build", "Chipmunk2D/build/src"]
    extra_compile_args = ["-Wextra", "-Wfloat-conversion"]

elif sys.platform == "darwin":
    extra_link_args = ["-framework", "OpenGL", "-framework", "IOKit", "-framework", "Cocoa"]
    library_dirs = ["glfw/build/src", "freetype/build", "Chipmunk2D/build/src"]
    libraries = ["glfw3", "freetype", "chipmunk", "bz2", "z"]

setuptools.setup(
    name = "JoBase",
    version = "2.8",
    author = "Reuben Ford",
    author_email = "hello@jobase.org",
    description = "Fast Python Game Library",
    long_description = open("README.md", "r").read(),
    long_description_content_type = "text/markdown",
    url = "https://jobase.org",
    python_requires = ">=3.6",
    license = "GPL-3.0-or-later",
    packages = ["JoBase"],
    include_package_data = True,

    package_data = {"JoBase": [
        "fonts/*.ttf", "images/*.png", "examples/*.py",
        "examples/*/__main__.py", "examples/*/README.md"
    ]},

    keywords = [
        "fast", "beginner", "extension",
        "library", "opengl", "glfw",
        "games", "c", "children", "freetype"
    ],

    project_urls = {
        "Source": "https://github.com/JoBase/JoBase",
        "Documentation": "https://jobase.org/reference"
    },

    classifiers = [
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
        "Operating System :: OS Independent",
    ],

    ext_modules = [
        setuptools.Extension(
            "JoBase.__init__",
            [os.path.join("src", e) for e in os.listdir("src")],
            ["include", "glfw/include", "freetype/include", "Chipmunk2D/include"],

            extra_compile_args = extra_compile_args,
            extra_link_args = extra_link_args,
            library_dirs = library_dirs,
            libraries = libraries)
    ])


import unittest
import os
import json

from utils.beam import Beam

TEST_PATH = os.path.dirname(os.path.realpath(__file__))
TEST_FILES_PATH = os.path.join(TEST_PATH, '../test_source')
TMP_PATH = os.path.join(TEST_PATH, '../tmp_test')

class BeamTestCase(unittest.TestCase):
    def setUp(self):
        self.test_files = ['d96ee006b62213506a07_segments.json']
        self.beam_width = 10
        self.t_min = 3 # currently useless
        self.t_max = 10

        if not os.path.exists(TMP_PATH):
            os.mkdir(TMP_PATH)

    def tearDown(self):
        pass
        #os.popen('rm %s/*.*'%TMP_PATH)

    def test_beam(self):
        for test_file in self.test_files:
            segment_file = os.path.join(TEST_FILES_PATH, test_file)
            with open(segment_file) as infile:
                segments = json.load(infile)

            # optimize segments
            beam = Beam(self.beam_width, self.t_min, self.t_max)
            for segment in segments:
                beam.add(segment)
            best_segments = beam.sequences[0]

            # check if there are unreasonably long segments
            max_unoptimized_length = max([segment['end']-segment['start']\
                                    for segment in segments])
            max_length = max([segment['end']-segment['start']\
                              for segment in best_segments])
            reference_length = max(30, max_unoptimized_length)
            self.assertTrue(max_length < reference_length,
                            msg="unreasonably long optimized segments are "\
                                "present in %s with %f seconds wrt %f"%(test_file,
                                                                 max_length,
                                                                 reference_length))


"""Module for functions related to saving and loading game."""
# Leon Lee
# BCIT
# A01062166

import character
import comp_1510_a1
import dialogue_and_scripts
import json
import os
import sys

def load_game(name: str):
    """Load a character file.

    Will load a character if there is a .json file with the same name string as the parameter.
    PARAM: A string with the player's username.
    PRECONDITION: The parameter must be a string.
    POSTCONDITION: Saves a character's name, health points, coordinates, and number of kills as an instance of the
    class "Character" and return it.
    RETURN: If there is an existing .json file, return that character instance. If file is not found,
    return boolean False.
    """
    file_name = name + ".json"
    try:
        with open(file_name) as file_object:
            saved_data = json.load(file_object)
            my_char = character.Character(saved_data['name'], saved_data['health'], saved_data['coordinates'],
                                          saved_data['kills'])
            # Creates an instance of the character class using the saved dictionary in the .json file
            dialogue_and_scripts.intro_script_load_game(my_char)
            # Prints the dialogue for a loaded game
            return my_char
    except FileNotFoundError:
        print('\nSorry, you do not have a saved file.')
        return False

def save_game(char: character.Character):
    """Save a character file.

    Will save a character into a .json file using the value of the 'name' attribute instance in the parameter.
    PARAM: An instance of the class "Character".
    PRECONDITION: The parameter must be an instance from the class "Character".
    POSTCONDITION: Saves a character's name, health points, coordinates, and number of kills into a .json file
    RETURN: N/A
    """
    file_name = char.get_name() + ".json"
    saved_data = {'name': char.get_name(),
                  'health': char.get_health(),
                  'coordinates': char.get_coordinate(),
                  'kills': char.get_kill_count()}
    with open(file_name, 'w') as file_object:
        json.dump(saved_data, file_object)

def choose_to_save(char):
    """Let player choose to save or not.

    The game will save if input is 'Y' and not save if input is 'N'.
    PARAM: An instance belonging to the class 'Character'.
    PRECONDITION: The parameter must be an instance belonging to the class 'Character'.
    POSTCONDITION: Either the game saves or does not save.
    RETURN: N/A.
    """
    print('\nQuitting so soon..? I am disappointed at your lack of bloodlust.\n'
          'You are not fit to wear this mask...\n')
    while True:
        save = input('Would you like to save your game? If you choose no, \nany older saved files under the same user '
                     'name will also be deleted. (Y / N)\n').title()
        if save == 'Y':
            save_game(char)
            return
        elif save == 'N':
            do_not_save(char)
            return
        else:
            print('That is not a valid command.')

def do_not_save(char: character.Character):
    """Do not save a character file.

    Will delete a .json file with the same 'name' value as the instance in the parameter.
    PARAM: An instance of the class "Character".
    PRECONDITION: The parameter must be an instance from the class "Character".
    POSTCONDITION: Removes the .json file associated with the name of a character.
    """
    file_name = char.get_name() + '.json'
    os.remove(file_name)
    # Removes .json file associated with current character

def restart_or_quit():
    """Let the player choose between playing again or exiting the game.

    PARAM: N/A
    PRECONDITION: This function is only called when an user dies or quits the game.
    POSTCONDITION: Either the main() game function is called again to restart the game or the game exits.
    RETURN: N/A.
    """
    choice_to_play_again = input("\nWould you like to play again? (Y / N)\n").title()
    if choice_to_play_again == 'Y':
        comp_1510_a1.start_game()
        # Restarts game
    elif choice_to_play_again == 'N':
        sys.exit()
        # Exits game
    else:
        print('That is not a valid command.')


# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:

import logging
import re
from logging import Filter

def set_level(record, levelno=logging.DEBUG):
    record.levelno = levelno
    record.levelname = logging.getLevelName(levelno)

dtype_pattern = re.compile(r"Changing (.+) dtype from (.+) to (.+)")

class DTypeWarningsFilter(Filter):
    def filter(self, record):
        message = record.getMessage()

        if dtype_pattern.search(message) is not None:
            set_level(record, levelno=logging.INFO)

        return True

pywarnings_to_ignore: list[str] = [
    "ParserWarning",
    "FutureWarning",
    "DeprecationWarning",
    "DependencyWarning",
    # numpy
    "RuntimeWarning: Mean of empty slice",
    "`np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.",
    "`np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.",
    "`np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.",
    # nipype
    "cmp not installed",
    "The trackvis interface has been deprecated and will be removed in v4.0; please use the 'nibabel.streamlines' interface.",
    "This has not been fully tested. Please report any failures.",
]

pywarnings_to_hide: list[str] = [
    # numpy
    "invalid value encountered in",
    "divide by zero encountered in",
    # nitransforms
    "Reference space not set",
    # statsmodels
    "overflow encountered in",
]

def compile_list(ww: list[str]) -> re.Pattern:
    escaped: list[str] = [str(re.escape(w)) for w in ww]
    regex_str = "|".join(escaped)
    return re.compile(f"(?:{regex_str})")

ignore_pattern = compile_list(pywarnings_to_ignore)
hide_pattern = compile_list(pywarnings_to_hide)

class PyWarningsFilter(Filter):
    def __init__(self, name: str = "pywarnings_filter") -> None:
        super().__init__(name=name)

    def filter(self, record):
        message = record.getMessage()

        if ignore_pattern.search(message):
            set_level(record, levelno=logging.DEBUG)

        if hide_pattern.search(message):
            set_level(record, levelno=logging.INFO)

        return True


# -*- coding: utf-8 -*-
"""
Created on Wed Aug 22 08:48:48 2018

@author: koshnick
"""

#import mnv14 as mnv
import os
import nbformat

from nbconvert.preprocessors import ExecutePreprocessor

"""
This file will populate a list of folders that all contain the same file
structure. Inside of this sturcture will be an ipynb that will have all of the
important information of the model prefilled in (or read to be filled in from
file). The purpose of this script is to run all of those notebooks with a
keyword argument for the "FINAL DATE" and "FINAL OUTPUT (?)" so the output
function in the notebook will drop all of the important MnV information in a
central folder in the FY folder

Find folders - func
verify integrity?

put notebook in nbconvert
 - remove create archive line?
 - append final archive line?
 -- lets prototype with current archive line - no alterations

Run notebook - drop some output file in central folder (archive input?)
Can the notebook return data into native python namespace?

"""
directory = 'FY 2018-2019'

def find_folders(directory='FY 2018-2019'):
    folders = next(os.walk(directory))[1]
    removals = ['REPORT', '.ipynb_checkpoints']

    for folder in folders:
        if folder in removals:
            folders.remove(folder)

    return folders

def load_notebook(fileName):

    nb = nbformat.read(open(fileName), as_version=4)

    return nb

def edit_lines(nb):

    lastCell = nb['cells'].pop()
    print(lastCell)
    # 'mnv.create_archive(dk, mc, saveFigs=True)
    code = lastCell['source']
    code = code.replace(')', ', centralize=True)')

    lastCell['source'] = code

    nb['cells'].append(lastCell)

    return nb

def run_notebook(ep, nb):
    ep.preprocess(nb, {'metadata': {'path': None}})

    print('process finished')

def main():
    folders = find_folders(directory=directory)

    fileName = 're-Model-MnV.ipynb'
    ep = ExecutePreprocessor(timeout=6000, kernel_name='python3')

    for folder in folders:
        print(folder)

        os.chdir(os.path.join(directory,folder))

        nb = load_notebook(fileName)
        nb = edit_lines(nb)
        run_notebook(ep, nb)

        os.chdir(os.path.join('..','..'))

if __name__ == "__main__":
    A =  main()


# -*- coding: utf-8 -*-

import logging
from odoo import api, fields, models, _
# this is for validation error exception
from odoo.exceptions import ValidationError

_logger = logging.getLogger(__name__)  # Show logs based on the module name
# if you want to log validation error, use Warning instead of any

# this won't be shown in menu if it's not listed in the security rules

class RegisClient(models.Model):
    _name = "regis.client"
    _description = "Client Registration Form"
    _inherit = [
        'mail.thread',
        'mail.activity.mixin'
    ]
    _rec_name = 'fullname_seq'
    # default order attribute, this will order by default of the field that specify here
    # you can use 1 or more field, eg: _order = "name, age desc" desc for desc, asc for asc or _order = "name desc, age asc"
    _order = "create_date desc, first_name asc"

    first_name = fields.Char(string='First Name', required=True, tracking=True)
    last_name = fields.Char(string='Last Name', required=True, tracking=True)
    age = fields.Integer(string='Age', tracking=True)
    gender = fields.Selection([
        ('male', 'Male'),
        ('female', 'Female')
    ], required=True, default='male', tracking=True)
    address = fields.Text(string='Address', tracking=True)
    bio = fields.Text(string='Bio', tracking=True)

    reference = fields.Char(string='Reference', required=True, readonly=True, tracking=True, copy=False,
                            default=lambda self: _('New'))

    @api.model
    def create(self, vals):
        if not vals.get('bio'):
            vals['bio'] = "This is default bio"

        if vals.get('reference', _('New')) == _('New'):
            vals['reference'] = self.env['ir.sequence'].next_by_code(
                'regis.client') or _('New')

        return super(RegisClient, self).create(vals)

    fullname_seq = fields.Char(
        string='Sequencing Fullname', compute='_compute_fields_combination_seq')

    @api.depends('first_name', 'last_name', 'reference')
    def _compute_fields_combination_seq(self):
        for com in self:
            if com.first_name:
                com.fullname_seq = f"{str(com.reference)} - {str(com.first_name).title()}"
                if com.last_name:
                    com.fullname_seq = f"{str(com.reference)} - {str(com.first_name).title()} {str(com.last_name).title()}"
            else:
                com.fullname_seq = f"{str(com.reference)} - "

    fullname = fields.Char(
        string='Fullname', compute='_compute_fields_combination')

    @api.depends('first_name', 'last_name')
    def _compute_fields_combination(self):
        for com in self:
            if com.first_name:
                com.fullname = f"{str(com.first_name).title()}"
                if com.last_name:
                    com.fullname = f"{str(com.first_name).title()} {str(com.last_name).title()}"
            else:
                com.fullname = ""

    # TODO: User count per client here

    @api.onchange('first_name')
    def onchange_first_name(self):
        for com in self:
            if com.first_name:
                val = str(com.first_name)
                com.first_name = val.title()
            else:
                com.first_name = ""

    @api.onchange('last_name')
    def onchange_last_name(self):
        for com in self:
            if com.last_name:
                val = str(com.last_name)
                com.last_name = val.title()
            else:
                com.last_name = ""

    @api.model
    def default_get(self, fields):
        res = super(RegisClient, self).default_get(fields)
        res['address'] = 'Jakarta, Indonesia'
        res['bio'] = _(
            'This is default bio, delete this line if you want to create other bio')
        return res

    # override copy method
    # this will override copy method if duplicate action is executed
    def copy(self, default=None):
        if default is None:
            default = {}
        default['first_name'] = self.first_name + _(' (copy)')
        default['last_name'] = self.last_name + _(' (copy)')
        default['bio'] = self.bio + _('. This is copy of bio')
        _logger.warning(f"{default['id']} is coppied")
        return super(RegisClient, self).copy(default)

    # not valid constraint
    @api.constrains('age')
    def check_age(self):
        for rec in self:
            if rec.age < 1 or rec.age > 150:
                _logger.warning('Age is not valid')
                raise ValidationError(_(f"{rec.age} is not a valid age"))

    # another example
    @api.constrains('first_name', 'last_name')
    def check_first_last_name(self):
        for rec in self:
            if rec.first_name == rec.last_name:
                _logger.warning('Not a valid name')
                raise ValidationError(_(f"Not a valid name"))

    # create archive/unarchive button
    # default is True, if default is False, so all of data will be archived
    active = fields.Boolean(string='Active', default=True)