import itertools
import math
import re
import textwrap
from typing import Iterable, Tuple

from antlir.btrfs_diff.extent import Extent
from antlir.btrfs_diff.extents_to_chunks import extents_to_chunks_with_clones
from antlir.btrfs_diff.inode import Chunk
from antlir.btrfs_diff.inode_id import InodeID, InodeIDMap

from antlir.tests.common import AntlirTestCase

def _gen_ranges_from_figure(figure: str):
    for s in textwrap.dedent(figure.strip("\n")).split("\n"):
        s = s.rstrip()
        # Number lines should aid reading off positions. Check they're right.
        if re.match("[0-9]*$", s):
            assert ("0123456789" * math.ceil(len(s) / 10))[
                : len(s)
            ] == s, f"Bad number line {s} in {figure}"
            continue
        offset = 0
        for m in re.finditer(r"(.)\1*", s):
            v = m.group(0)
            if v[0] != " ":
                yield v[0], offset, len(v)
            offset += len(v)

def _repr_ids_and_chunks(ids_and_chunks: Iterable[Tuple[InodeID, Chunk]]):
    return {
        repr(id): [
            (
                f"{c.kind.name}/{c.length}",
                # I don't want to think about clone ordering within a chunk,
                # since it's largely arbitrary and not very important.
                {repr(cc) for cc in c.chunk_clones},
            )
            for c in chunks
        ]
        for id, chunks in ids_and_chunks
    }

class ExtentsToChunksTestCase(AntlirTestCase):
    """
    This test has one main focus, plus a few additional checks.

    Primarily: Given a "backing" DATA `Extent` whose slices are cloned into
    a few `Extent`s representing files, we must compute a correct
    presentation of those files' clone structure.

    Additionally, I exercise the case of multiple extents in a "kitchen
    sink" fashion in `test_multi_extent`.  I did not go for more
    reductionistic tests because -- with the exception of chunk merging in
    the final pass -- the code in `extents_to_chunks.py` does not allow
    interactions between different leaf extents.  Instead, it loops over
    each extent separately.  The main risk with multiple-extent scenarios is
    incorrect keying / aggregation of the clones.  This is covered amply by
    the present test, and by in-code assertions that verify we only interact
    with one extent at a time.

    Besides exercising clone detection in extents, we need to check:
     - that chunk merging works correctly (including clones), and
     - that the extent-relative coordinates of `ChunkClones` are correctly
       trimmed to match the file extent's `gen_trimmed_leaves` output.
    Both of these are covered by `test_FIG1_*` -- look at the underlying
    trimmed leaf extents in `test_gen_ids_and_extents_from_figure_*` to
    confirm this.  Then, `test_multi_extent` gives even more coverage.

    ## How to read these tests

    Most tests use `_repr_ids_and_chunks` to compactly verify outputs. Read
    this to understand its notation.

    (1) An file's data fork is a sequence of chunks. The next example refers
        to a hole (unallocated space filled with 0s) of 10 bytes, followed
        by actual data-on-disk of 20 bytes.  The two ...  sets refer to
        `clone` links to other files.  Cloning is a mechanism by which btrfs
        shares the same blocks on disk to represent identical data in
        different file locations -- in particular, this makes `cp` nearly
        instant, and allows filesystem deduplication.

        [('HOLE/10'x, {...}), ('DATA/20', {...})]

    (1) The notation for ChunkClone objects is as follows:

        <file paths>:<file offset>+<length>@<offset in the current chunk>

        So, the first clone of `('DATA/30', {'A:123+17@5', ...})` states
        that the 17 bytes of this data chunk starting from offset 5 are
        cloned (share storage with) the 123rd byte of file "A".

    ## How to read the "figure" notation

    Many tests use the "figure" notation to concisely describe how many
    different files clone their chunks from a single underlying Extent.
    Reading a figure pretty much as expected, but I will spell out a simple
    example for explicitness -- also look at `test_docstring_example`:

        AAA  AAA
          BBBBCCCCCC
         CCC
        012345678901

    The numbered bottom line serves solely to help the reader quickly
    identify lengths and offsets. The letter lines say that:
     - File A concatenates a clone of length 3 at offset 0 with a second
       clone of length 3 at offset 5.  The file's total length is thus 6.
     - File B consists of a single clone at offset 2, length 4.
     - C concatenates two clones: offset 1, length 3 with offset 6, length 6.
     - Thus, offset 2 of the portrayed Extent is cloned into all 3 files.
     - Review the ChunkClones for this figure in `test_docstring_example`.

    IMPORTANT: Figures must NOT specify two overlapping ranges for the same
    filename -- that does not make sense for `clone` operations.  The
    warning is here because `_gen_ranges_from_figure` does not detect this,
    and the downstream failure will be hard to debug.
    """

    def setUp(self) -> None:
        super().setUp()
        self.id_map = InodeIDMap.new()

    def _gen_ids_and_extents_from_figure(
        self,
        s: str,
        extent_left: int = 0,
        extent_right: int = 0,
        slice_spacing: int = 0,
    ):
        """
        Given a figure,

         - Create a backing DATA Extent, from which every file will clone.

         - Parse from the figure the sequential slices of the Extent that are
           identified by contiguous sequences of letters (aka filenames). A
           slice is an offset & length.

         - Create an `InodeID` + file `Extent` for each mentioned filename.

         - Pack, from left-to-right, each filename's slices of the backing
           Extent into the corresponding file Extent.  Optionally, we will
           separate the slices by HOLEs of `slice_spacing` bytes.  Note that
           a `slice_spacing` hole is also inserted at the beginning of each
           file.  The intent of this option is simply to check that we
           don't compute the right file offsets by accident.

         - Similarly, `extent_{left,right}` add uncloned bytes on the left &
           right of the backing extent.
        """
        ranges = sorted(_gen_ranges_from_figure(s))
        source_extent = Extent.empty().write(  # A single DATA extent
            offset=0,
            length=extent_left
            + max(offset + length for _, offset, length in ranges)
            + extent_right,
        )
        for name, group in itertools.groupby(ranges, key=lambda x: x[0]):
            file_extent = Extent.empty()
            file_offset = slice_spacing
            for _, offset, length in group:
                file_extent = file_extent.clone(
                    to_offset=file_offset,
                    from_extent=source_extent,
                    from_offset=extent_left + offset,
                    length=length,
                )
                file_offset += slice_spacing + length
            yield (
                self.id_map.add_file(self.id_map.next(), name.encode()),
                file_extent,
            )

    def _repr_chunks_from_figure(self, s: str, **kwargs):
        return _repr_ids_and_chunks(
            # pyre-fixme[6]: For 1st param expected `Iterable[Tuple[InodeID,
            #  Chunk]]` but got `Iterable[Tuple[InodeID, Sequence[Chunk]]]`.
            extents_to_chunks_with_clones(
                list(self._gen_ids_and_extents_from_figure(s, **kwargs))
            )
        )

    def test_gen_ranges_from_figure(self) -> None:
        self.assertEqual(
            [
                ("A", 0, 9),
                ("A", 16, 3),
                ("B", 9, 5),
                ("C", 5, 9),
                ("D", 3, 7),
                ("E", 10, 7),
                ("F", 11, 2),
            ],
            sorted(_gen_ranges_from_figure(self.FIG1)),
        )

    def test_gen_ranges_from_figure_empty(self) -> None:
        self.assertEqual([], list(_gen_ranges_from_figure("01234")))

    def test_gen_ranges_from_figure_bad_number_line(self) -> None:
        with self.assertRaises(AssertionError):
            list(_gen_ranges_from_figure("10234"))

    def test_gen_ids_and_extents_from_figure(self) -> None:
        # The numeric line has 21 chars, but it is not counted.
        e = Extent(Extent.Kind.DATA, 0, 19)
        self.assertEqual(
            {
                "A": [(0, 9, e), (16, 3, e)],
                "B": [(9, 5, e)],
                "C": [(5, 9, e)],
                "D": [(3, 7, e)],
                "E": [(10, 7, e)],
                "F": [(11, 2, e)],
            },
            {
                repr(id): list(extent.gen_trimmed_leaves())
                for id, extent in self._gen_ids_and_extents_from_figure(self.FIG1)
            },
        )

    def test_gen_ids_and_extents_from_figure_with_extent_left_and_right(
        self,
    ) -> None:
        # Check extent_left & extent_right
        e = Extent(Extent.Kind.DATA, 0, 2119)
        self.assertEqual(
            {
                "A": [(100, 9, e), (116, 3, e)],
                "B": [(109, 5, e)],
                "C": [(105, 9, e)],
                "D": [(103, 7, e)],
                "E": [(110, 7, e)],
                "F": [(111, 2, e)],
            },
            {
                repr(id): list(extent.gen_trimmed_leaves())
                for id, extent in self._gen_ids_and_extents_from_figure(
                    self.FIG1, extent_left=100, extent_right=2000
                )
            },
        )

    def test_gen_ids_and_extents_from_figure_with_all_kwargs(self) -> None:
        # Check slice_spacing -- the leaf trimming offsets & lengths do not
        # change, but we do get HOLEs in the expected places.
        e = Extent(Extent.Kind.DATA, 0, 2119)
        hole = (0, 17, Extent(Extent.Kind.HOLE, 0, 17))
        self.assertEqual(
            {
                "A": [hole, (100, 9, e), hole, (116, 3, e)],
                "B": [hole, (109, 5, e)],
                "C": [hole, (105, 9, e)],
                "D": [hole, (103, 7, e)],
                "E": [hole, (110, 7, e)],
                "F": [hole, (111, 2, e)],
            },
            {
                repr(id): list(extent.gen_trimmed_leaves())
                for id, extent in self._gen_ids_and_extents_from_figure(
                    self.FIG1,
                    extent_left=100,
                    extent_right=2000,
                    slice_spacing=17,
                )
            },
        )

    FIG1 = """
               FF   AAA
         CCCCCCCCC
       DDDDDDDEEEEEEE
    AAAAAAAAABBBBB
    012345678901234567890
    """

    # The result without `slice_spacing` is the same even if we vary
    # `extent_left` & `extent_right` -- that internal representation
    # detail should not be captured in our file-offset clone list.
    FIG1_repr_chunks_no_spacing = {
        # This shows successful merging of chunks -- the preceding test
        # demonstrated that we emit 2 trimmed leaves of size 9 & 3 for
        # A, but here they became one chunk of 12:
        "A": [("DATA/12", {"C:0+4@5", "D:0+6@3", "E:6+1@9"})],
        "B": [("DATA/5", {"C:4+5@0", "D:6+1@0", "E:0+4@1", "F:0+2@2"})],
        "C": [("DATA/9", {"A:5+4@0", "B:0+5@4", "D:2+5@0", "E:0+4@5", "F:0+2@6"})],
        "D": [("DATA/7", {"A:3+6@0", "B:0+1@6", "C:0+5@2"})],
        "E": [("DATA/7", {"A:9+1@6", "B:1+4@0", "C:5+4@0", "F:0+2@1"})],
        "F": [("DATA/2", {"B:2+2@0", "C:6+2@0", "E:1+2@0"})],
    }

    # The next three tests are the only tests that checks that
    # `extent_left`, `extent_right`, and `slice_spacing` do not break
    # `extents_to_chunks_with_clones`.  The single example in FIG1 is rich
    # enough that using it to exercise these offset variations gives us a
    # confidence in our position arithmetic.

    def test_finalize_FIG1(self) -> None:
        self.assertEqual(
            self.FIG1_repr_chunks_no_spacing,
            self._repr_chunks_from_figure(self.FIG1),
        )

    def test_finalize_FIG1_with_extent_left_and_right(self) -> None:
        self.assertEqual(
            self.FIG1_repr_chunks_no_spacing,
            self._repr_chunks_from_figure(
                self.FIG1, extent_left=100, extent_right=2000
            ),
        )

    def test_finalize_FIG1_with_spacing_extent_left_and_right(self) -> None:
        # Adding slice_spacing shifts all the file offsets, and adds holes
        hole = ("HOLE/100", set())
        self.assertEqual(
            {
                # This time, chunk merging is prevented by the hole.
                "A": [
                    hole,
                    ("DATA/9", {"C:100+4@5", "D:100+6@3"}),
                    hole,
                    ("DATA/3", {"E:106+1@0"}),
                ],
                "B": [
                    hole,
                    (
                        "DATA/5",
                        {"C:104+5@0", "D:106+1@0", "E:100+4@1", "F:100+2@2"},
                    ),
                ],
                "C": [
                    hole,
                    (
                        "DATA/9",
                        {
                            "A:105+4@0",
                            "B:100+5@4",
                            "D:102+5@0",
                            "E:100+4@5",
                            "F:100+2@6",
                        },
                    ),
                ],
                "D": [
                    hole,
                    ("DATA/7", {"A:103+6@0", "B:100+1@6", "C:100+5@2"}),
                ],
                "E": [
                    hole,
                    (
                        "DATA/7",
                        {"A:209+1@6", "B:101+4@0", "C:105+4@0", "F:100+2@1"},
                    ),
                ],
                "F": [
                    hole,
                    ("DATA/2", {"B:102+2@0", "C:106+2@0", "E:101+2@0"}),
                ],
            },
            self._repr_chunks_from_figure(
                self.FIG1, extent_left=17, extent_right=23, slice_spacing=100
            ),
        )

    def test_nothing_cloned(self) -> None:
        self.assertEqual(
            {"a": [("DATA/4", set())], "b": [("DATA/6", set())]},
            self._repr_chunks_from_figure("aabbbaabbb"),
        )

    def test_docstring_example(self) -> None:
        # Note that A's and C's disjoint chunks are merged before output.
        self.assertEqual(
            {
                "A": [("DATA/6", {"B:0+1@2", "B:3+1@3", "C:0+2@1", "C:3+2@4"})],
                "B": [("DATA/4", {"A:2+1@0", "A:3+1@3", "C:1+2@0"})],
                "C": [("DATA/9", {"A:1+2@0", "A:4+2@3", "B:0+2@1"})],
            },
            self._repr_chunks_from_figure(
                """
            AAA  AAA
              BBBBCCCCCC
             CCC
            012345678901
        """
            ),
        )

    def test_a_and_b_are_subsequences_of_c(self) -> None:
        self.assertEqual(
            {
                "a": [("DATA/6", {"c:0+3@0", "c:5+2@3", "c:12+1@5"})],
                "b": [("DATA/4", {"c:3+2@0", "c:7+2@2"})],
                "c": [
                    (
                        "DATA/14",
                        {
                            "a:0+3@0",
                            "a:3+2@5",
                            "a:5+1@12",
                            "b:0+2@3",
                            "b:2+2@7",
                        },
                    )
                ],
            },
            self._repr_chunks_from_figure(
                """
              cccccccccccccc
              aaabbaabb   a
            01234567890123456789
        """
            ),
        )

    def test_four_identical_clones(self) -> None:
        self.assertEqual(
            {
                "a": [("DATA/1", {"b:0+1@0", "c:0+1@0", "d:0+1@0"})],
                "b": [("DATA/1", {"a:0+1@0", "c:0+1@0", "d:0+1@0"})],
                "c": [("DATA/1", {"a:0+1@0", "b:0+1@0", "d:0+1@0"})],
                "d": [("DATA/1", {"a:0+1@0", "b:0+1@0", "c:0+1@0"})],
            },
            self._repr_chunks_from_figure("d\nc\na\nb"),
        )

    def test_ladder_of_clones(self) -> None:
        self.assertEqual(
            {
                "a": [("DATA/3", {"b:0+2@1", "c:0+1@2"})],
                "b": [("DATA/3", {"a:1+2@0", "c:0+2@1", "d:0+1@2"})],
                "c": [("DATA/3", {"a:2+1@0", "b:1+2@0", "d:0+2@1", "e:0+1@2"})],
                "d": [("DATA/3", {"b:2+1@0", "c:1+2@0", "e:0+2@1", "f:0+1@2"})],
                "e": [("DATA/3", {"c:2+1@0", "d:1+2@0", "f:0+2@1"})],
                "f": [("DATA/3", {"d:2+1@0", "e:1+2@0"})],
            },
            self._repr_chunks_from_figure(
                """
               ddd
              ccc
             bbbeee
            aaa  fff
            01234567890123456789
        """
            ),
        )

    def test_cannot_merge_adjacent_clones(self) -> None:
        # Shows that we don't currently have merging of adjacent clones
        self.assertEqual(
            {
                "a": [("DATA/4", {"b:0+2@0", "b:2+2@2"})],
                "b": [("DATA/4", {"a:0+2@0", "a:2+2@2"})],
            },
            self._repr_chunks_from_figure(
                """
            bbaa
            aabb
            01234567890123456789
        """
            ),
        )

    def test_multi_extent(self) -> None:
        # There are 3 `write` commands below, one for each of `a`, `b`, and
        # `c`.  We also create a few HOLE leaf extents along the way.  All
        # of these extents are spread around by a few clone operations, so
        # this is at true multi-extent test.
        a = Extent.empty().write(offset=3, length=5)

        b = (
            Extent.empty()
            .truncate(length=10)
            .write(offset=7, length=10)
            .clone(to_offset=5, from_extent=a, from_offset=2, length=4)
        )

        # Besides verifying `b`'s content, this gives us a way to refer to it.
        # pyre-fixme[23]: Unable to unpack single value, 4 were expected.
        _, _, (b_trunc, a_hole, a_wr, b_wr) = zip(*b.gen_trimmed_leaves())
        self.assertEqual(b_trunc, Extent(Extent.Kind.HOLE, 0, 10))
        self.assertEqual(a_hole, Extent(Extent.Kind.HOLE, 0, 3))
        self.assertEqual(a_wr, Extent(Extent.Kind.DATA, 0, 5))
        self.assertEqual(b_wr, Extent(Extent.Kind.DATA, 0, 10))
        b_trimmed_leaves = [
            (0, 5, b_trunc),
            (2, 1, a_hole),
            (0, 3, a_wr),
            (2, 8, b_wr),
        ]
        self.assertEqual(b_trimmed_leaves, list(b.gen_trimmed_leaves()))

        c = Extent.empty().write(offset=0, length=30)
        c_wr = c  # Save this Extent for later
        self.assertEqual(c_wr, Extent(Extent.Kind.DATA, 0, 30))
        c = c.clone(to_offset=10, from_extent=a, from_offset=1, length=3).clone(
            to_offset=25, from_extent=b, from_offset=0, length=b.length
        )

        # Here is what `c` looks like on disk.
        c_trimmed_leaves = [
            (0, 10, c_wr),
            (1, 2, a_hole),
            (0, 1, a_wr),
            (13, 12, c_wr),
            *b_trimmed_leaves,
        ]
        self.assertEqual(c_trimmed_leaves, list(c.gen_trimmed_leaves()))

        a = a.clone(to_offset=4, from_extent=c, from_offset=0, length=c.length)

        # The final form of `a`
        a_trimmed_leaves = [(0, 3, a_hole), (0, 1, a_wr), *c_trimmed_leaves]
        self.assertEqual(a_trimmed_leaves, list(a.gen_trimmed_leaves()))

        # Now that we have these messy, multi-extent, self-referential
        # files, let's make sure the clone detection does the right thing.
        # Also add an empty file to make sure that corner case works.

        ids_and_chunks = list(
            extents_to_chunks_with_clones(
                [
                    (self.id_map.add_file(self.id_map.next(), p), e)
                    for p, e in [
                        (b"a", a),
                        (b"b", b),
                        (b"c", c),
                        (b"e", Extent.empty()),
                    ]
                ]
            )
        )

        expected_dict = {
            "b": [
                (
                    "HOLE/6",
                    {  # merged: `b_trunc` (5) + `a_hole` (1) = 6
                        "a:2+1@5",  # `a_hole` original location in `a`
                        "c:11+1@5",  # `a_hole` from `c.clone(10, a)`
                        "c:30+1@5",  # `a_hole` from `c.clone(25, b)`
                        "a:15+1@5",  # `a_hole` from `a.clone(4,c.clone(10,a))`
                        "a:34+1@5",  # `a_hole` from `a.clone(4,c.clone(25,b))`
                        "c:25+5@0",  # `b_trunc` from `c.clone(25, b)`
                        "a:29+5@0",  # `b_trunc` from `a.clone(4,c.clone(25,b))`
                    },
                ),
                (
                    "DATA/11",
                    {  # merged: `a_wr` (3) + `b_wr` (8) = 11
                        "a:3+1@0",  # `a_wr` original location in `a`
                        "c:12+1@0",  # `a_wr` from `c.clone(10, a)`
                        "a:16+1@0",  # `a_wr` from `a.clone(4, c.clone(10, a))`
                        "c:31+3@0",  # `a_wr` from `c.clone(25, b)`
                        "a:35+3@0",  # `a_wr` from `a.clone(4, c.clone(25, b))`
                        "c:34+8@3",  # `b_wr` from `c.clone(25, b)`
                        "a:38+8@3",  # `b_wr` from `a.clone(4, c.clone(25, b))`
                    },
                ),
            ],
            "c": [
                ("DATA/10", {"a:4+10@0"}),  # `c_wr`, via `a.clone(4, c)`
                (
                    "HOLE/2",
                    {  # `a_hole`
                        # `b` instance & its copy in `c`, and the next one in
                        # `a`
                        "b:5+1@1",
                        "c:30+1@1",
                        "a:34+1@1",
                        "a:1+2@0",  # original instance in `a`
                        "a:14+2@0",  # copy of this `c` instance into `a`
                    },
                ),
                (
                    "DATA/13",
                    {  # merged `a_wr` (1) + `c_wr` (12) = 13
                        # `a_wr`: `b` instance, copy in `c`, and it copy in `a`
                        "b:6+1@0",
                        "c:31+1@0",
                        "a:35+1@0",
                        "a:3+1@0",  # `a_wr`: original instance in `a`
                        "a:16+1@0",  # `a_wr`: copy of this `c` instance in `a`
                        "a:17+12@1",  # `c_wr` copy in `a`
                    },
                ),
                # These two are just the copy of `b` inside `c`, so this is
                # a copy-paste, replacing references into `c`'s copy of `b`
                # with references into the original `b`.
                (
                    "HOLE/6",
                    {
                        # `a` clones are unchanged
                        "a:2+1@5",
                        "a:15+1@5",
                        "a:34+1@5",
                        "a:29+5@0",
                        # `c` but unchanged, is outside of this copy of `b` in
                        # `c`
                        "c:11+1@5",
                        # In `Inode: b`, these linked to us. We return the
                        # favor.
                        "b:0+5@0",
                        "b:5+1@5",
                    },
                ),
                (
                    "DATA/11",
                    {
                        # unchanged
                        "a:3+1@0",
                        "a:16+1@0",
                        "a:35+3@0",
                        "a:38+8@3",
                        "c:12+1@0",
                        # links to this `c` chunk replaced by `b` counterparts
                        "b:6+3@0",
                        "b:9+8@3",
                    },
                ),
            ],
            "a": [
                (
                    "HOLE/3",
                    {  # `a_hole`
                        "b:5+1@2",  # `a_hole` copy from `b`
                        # These are copied with minor changes from b/17, HOLE/6
                        "c:10+2@1",  # `a_hole` from `c.clone(10, a)`
                        "c:30+1@2",  # `a_hole` from `c.clone(25, b)`
                        "a:14+2@1",  # `a_hole` from `a.clone(4,c.clone(10,a))`
                        "a:34+1@2",  # `a_hole` from `a.clone(4,c.clone(25,b))`
                    },
                ),
                (
                    "DATA/11",
                    {  # merged: `a_wr` + `c_wr` from `c` copy
                        "c:0+10@1",  # original `c_wr`
                        "b:6+1@0",  # `a_wr` copy from `b`
                        # These are copied with trivial changes from b/17,
                        # DATA/11
                        "c:12+1@0",  # `a_wr` from `c.clone(10, a)`
                        "a:16+1@0",  # `a_wr` from `a.clone(4, c.clone(10, a))`
                        "c:31+1@0",  # `a_wr` from `c.clone(25, b)`
                        "a:35+1@0",  # `a_wr` from `a.clone(4, c.clone(25, b))`
                    },
                ),
                # The rest are copy-pasted from `c`, with the appropriate
                # references to the current chunk of `a` replaced by the
                # corresponding link to `c` (subtract 4 from offset).
                (
                    "HOLE/2",
                    {  # `a_hole`
                        "b:5+1@1",
                        "c:30+1@1",
                        "a:34+1@1",
                        "a:1+2@0",  # copy-pasta
                        "c:10+2@0",  # `c` counterpart
                    },
                ),
                (
                    "DATA/13",
                    {  # merged `a_wr` (1) + `c_wr` (12) = 13
                        "b:6+1@0",
                        "c:31+1@0",
                        "a:35+1@0",
                        "a:3+1@0",  # copy-pasta
                        "c:12+1@0",
                        "c:13+12@1",  # `c` counterparts
                    },
                ),
                (
                    "HOLE/6",
                    {
                        "a:2+1@5",
                        "a:15+1@5",
                        "c:11+1@5",
                        "b:0+5@0",
                        "b:5+1@5",
                        "c:30+1@5",
                        "c:25+5@0",  # `c` counterparts (via `b` copy)
                    },
                ),
                (
                    "DATA/11",
                    {
                        "a:3+1@0",
                        "a:16+1@0",
                        "c:12+1@0",
                        "b:6+3@0",
                        "b:9+8@3",
                        "c:31+3@0",
                        "c:34+8@3",  # `c` counterparts (via `b` copy)
                    },
                ),
            ],
            "e": [],
        }
        # I iteratively built this up from the "trimmed leaves" data above,
        # and checked against the real output, one file at a time.  So, this
        # is not just "blind gold data", but a real worked example.
        # pyre-fixme[6]: For 1st param expected `Iterable[Tuple[InodeID, Chunk]]`
        #  but got `List[Tuple[InodeID, Sequence[Chunk]]]`.
        self.assertEqual(expected_dict, _repr_ids_and_chunks(ids_and_chunks))


from enum import IntEnum, auto

from aiogram.fsm.state import State, StatesGroup
from aiogram.filters.callback_data import CallbackData
from aiogram.types import InlineKeyboardButton

from ..states import MainState

class ProductsDecision(CallbackData, prefix='prde'):
    class S(IntEnum):
        back = auto()
        search = auto()
        all_products = auto()
        cart_products = auto()

    _parent = MainState
    _STATE_FUNCTION = None
    _BUTTONS: dict
    function: S

ProductsDecision._BUTTONS = {
    ProductsDecision.S.back: InlineKeyboardButton(
        text='Назад',
        callback_data=ProductsDecision(function=ProductsDecision.S.back).pack()
    ),
    ProductsDecision.S.search: InlineKeyboardButton(
        text='Поиск',
        callback_data=ProductsDecision(function=ProductsDecision.S.search).pack()
    ),
    ProductsDecision.S.all_products: InlineKeyboardButton(
        text='Все продукты',
        callback_data=ProductsDecision(function=ProductsDecision.S.all_products).pack()
    ),
    ProductsDecision.S.cart_products: InlineKeyboardButton(
        text='Товары в корзине',
        callback_data=ProductsDecision(function=ProductsDecision.S.cart_products).pack()
    ),
}

class ProductsNamePrompt(CallbackData, prefix='prnapr'):
    class S(IntEnum):
        back = auto()
        yes = auto()
        no = auto()

    _parent = MainState
    _STATE_FUNCTION = None
    _BUTTONS: dict
    function: S

ProductsNamePrompt._BUTTONS = {
    ProductsNamePrompt.S.back: InlineKeyboardButton(
        text='Назад',
        callback_data=ProductsNamePrompt(function=ProductsNamePrompt.S.back).pack()
    ),
    ProductsNamePrompt.S.yes: InlineKeyboardButton(
        text='Да',
        callback_data=ProductsNamePrompt(function=ProductsNamePrompt.S.yes).pack()
    ),
    ProductsNamePrompt.S.no: InlineKeyboardButton(
        text='Нет',
        callback_data=ProductsNamePrompt(function=ProductsNamePrompt.S.no).pack()
    ),
}

class ProductNamePrompt(StatesGroup):
    main = State()

class ProductsListPrompt(StatesGroup):
    main = State()

class ProductsList(CallbackData, prefix='prli'):
    class S(IntEnum):
        back = auto()
        next = auto()
        previous = auto()

    _parent = MainState
    _STATE_FUNCTION = None
    function: S

ProductsList._BUTTONS = {
    ProductsList.S.back: InlineKeyboardButton(
        text='Назад',
        callback_data=ProductsList(function=ProductsList.S.back).pack()
    ),
    ProductsList.S.next: InlineKeyboardButton(
        text='Cледующая',
        callback_data=ProductsList(function=ProductsList.S.next).pack()
    ),
    ProductsList.S.previous: InlineKeyboardButton(
        text='Предыдущая',
        callback_data=ProductsList(function=ProductsList.S.previous).pack()
    ),
}


# crie um programa que leia o ano de nascimento de sete pessoas, no final, mostre quantas pessoas ainda não atingiram a maioridade e quantos ja são maiores
from datetime import date
maior = 0
menor = 0
print(f'ano é: {date.today().year}')
for c in range(0,7):
    nascimento = int(input('Digite o ano do seu nascimento: '))
    if  date.today().year - nascimento >= 18:
        maior += 1
    else:
        menor += 1
print(f'O número de maiores é: {maior}')
print(f'O número de menores é: {menor}')


"""
:Author: Stefan Feuz; http://www.laboratoridenvol.com
:License: General Public License GNU GPL 3.0
"""

from PyQt6.QtCore import Qt
from PyQt6.QtSql import QSqlQuery, QSqlTableModel

from data.SqlTableModel import SqlTableModel
from Singleton.Singleton import Singleton

class NoseMylarsModel(SqlTableModel, metaclass=Singleton):
    """
    :class: Provides a SqlTableModel holding the DXF layer names
    """
    OrderNumCol = 0
    ''':attr: num of column for ordering the individual lines of a config'''
    FirstRibCol = 1
    ''':attr: Number of the col holding the first rib'''
    LastRibCol = 2
    ''':attr: Number of the col holding the last rib'''
    xOneCol = 3
    ''':attr: Number of the col holding the 1st param of 2nd row'''
    uOneCol = 4
    ''':attr: Number of the col holding the 2nd param of 2nd row'''
    uTwoCol = 5
    ''':attr: Number of the col holding the 3rd param of 2nd row'''
    xTwoCol = 6
    ''':attr: Number of the col holding the 4th param of 2nd row'''
    vOneCol = 7
    ''':attr: Number of the col holding the 5th param of 2nd row'''
    vTwoCol = 8
    ''':attr: Number of the col holding the 1st param of 3rd row'''
    ConfigNumCol = 9
    ''':attr: num of column for config number (always 1)'''

    def create_table(self):
        """
        :method: Creates initially the empty table
        """
        query = QSqlQuery()

        query.exec("DROP TABLE if exists NoseMylars;")
        query.exec("create table if not exists NoseMylars ("
                   "OrderNum INTEGER, "
                   "FirstRib INTEGER, "
                   "LastRib INTEGER, "
                   "x_one REAL, "
                   "u_one REAL, "
                   "u_two REAL, "
                   "x_two REAL, "
                   "v_one REAL, "
                   "v_two REAL, "
                   "ConfigNum INTEGER,"
                   "ID INTEGER PRIMARY KEY);")

    def __init__(self):
        """
        :method: Class initialization
        """
        super().__init__()
        self.create_table()
        self.setTable("NoseMylars")
        self.select()
        self.setEditStrategy(QSqlTableModel.EditStrategy.OnFieldChange)

        self.setHeaderData(0, Qt.Orientation.Horizontal, _("Order num"))
        self.setHeaderData(1, Qt.Orientation.Horizontal, _("First Rib"))
        self.setHeaderData(2, Qt.Orientation.Horizontal, _("Last Rib"))
        self.setHeaderData(3, Qt.Orientation.Horizontal, _("X1"))
        self.setHeaderData(4, Qt.Orientation.Horizontal, _("U1"))
        self.setHeaderData(5, Qt.Orientation.Horizontal, _("U2"))
        self.setHeaderData(6, Qt.Orientation.Horizontal, _("X2"))
        self.setHeaderData(7, Qt.Orientation.Horizontal, _("V1"))
        self.setHeaderData(8, Qt.Orientation.Horizontal, _("V2"))

    def update_row(self, config_num, order_num, first_rib, last_rib, x_one, u_one, u_two, x_two, v_one, v_two):
        """
        :method: Updates a specific row in the database with the values passed. Parameters are not explicitly
                 explained here as they should be well known.
        """
        query = QSqlQuery()
        query.prepare("UPDATE NoseMylars SET "
                      "FirstRib= :first_rib, "
                      "LastRib= :last_rib, "
                      "x_one= :x_one, "
                      "u_one= :u_one, "
                      "u_two= :u_two, "
                      "x_two= :x_two, "
                      "v_one= :v_one, "
                      "v_two= :v_two "
                      "WHERE (ConfigNum = :config AND OrderNum = :order);")
        query.bindValue(":first_rib", first_rib)
        query.bindValue(":last_rib", last_rib)
        query.bindValue(":x_one", x_one)
        query.bindValue(":u_one", u_one)
        query.bindValue(":u_two", u_two)
        query.bindValue(":x_two", x_two)
        query.bindValue(":v_one", v_one)
        query.bindValue(":v_two", v_two)
        query.bindValue(":config", config_num)
        query.bindValue(":order", order_num)
        query.exec()
        self.select()  # to a select() to assure the model is updated properly

    def get_row(self, config_num, order_num):
        """
        :method: reads values back from the internal database for a specific config and order number
        :param config_num: Configuration number. Starting with 1
        :param order_num: Order number. Starting with 1
        :return: specific values read from internal database
        """
        query = QSqlQuery()
        query.prepare("Select "
                      "OrderNum, "
                      "FirstRib, "
                      "LastRib, "
                      "x_one, "
                      "u_one, "
                      "u_two, "
                      "x_two, "
                      "v_one, "
                      "v_two "
                      "FROM NoseMylars WHERE (ConfigNum = :config) ORDER BY OrderNum")
        query.bindValue(":config", config_num)
        query.exec()
        query.next()
        # now we are at the first row
        i = 1
        while i < order_num:
            query.next()
            i += 1
        return query.value


"""
3. Написать функцию thesaurus(), принимающую в качестве аргументов имена сотрудников и возвращающую словарь,
в котором ключи — первые буквы имен, а значения — списки, содержащие имена, начинающиеся с соответствующей буквы.
Например:
>>> >>> thesaurus("Иван", "Мария", "Петр", "Илья")
{
    "И": ["Иван", "Илья"],
    "М": ["Мария"], "П": ["Петр"]
}
"""

"""
def thesaurus_adv(lst):
    dct = {}
    for name in lst:
        first_letter = name[0]
        if first_letter in dct:
            dct[first_letter].append(name)
        else:
            dct[first_letter] = [name]
    return dct

print(thesaurus_adv(["Иван", "Мария", "Марина", "Петр", "Павел", "Илья", "Марат"]))
"""

def thesaurus_adv(lst):
    dct = {}
    for name in lst:
        first_letter_surname = ""
        first_letter_name = name[0]
        for i in range(len(name)):
            if name[i] == " ":
                first_letter_surname = name[i + 1]
                break
        if first_letter_surname not in dct:
            dct[first_letter_surname] = [{first_letter_name: [name]}]
        else:
            if first_letter_name in dct[first_letter_surname][0]:
                dct[first_letter_surname][0][first_letter_name].append(name)
            else:
                dct[first_letter_surname].append({first_letter_name: [name]})
    return dct

names = thesaurus_adv(["Иван Петров", "Мария Иванова", "Марина Сидорова", "Петр Наумов",
                       "Павел Местников", "Паша Местников", "Илья Нестеров", "Марат Местников"])
for keys, values in names.items():
    print(keys)
    print(values)


import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import os
from services.exporter import Exporter as exporter

def fileLogger(txt):
    print(txt)
    exporter.printToFile(txt, "log")

def logger(txt=""):
    print(txt)

lines_cache = {}

def get_lines(file):
    if file in lines_cache:
        return lines_cache[file]

    fileObject1 = open(file, 'rb')
    file_lines = fileObject1.readlines()
    fileObject1.close()

    lines_cache[file] = file_lines

    return file_lines

def is_erro(erro_array):
    if len(erro_array) >= 2:
        return True

    return False

def find_constrat(file1, file2, showdifferences=False):
    contrast_list = []
    file1_lines = get_lines(file1)
    file2_lines = get_lines(file2)
    error_total = 0
    error_percent_total = 0
    biggest_delta = 0

    for index, file1_line in enumerate(file1_lines):
        if index == 0:
            continue

        file2_line = file2_lines[index]

        striped_file1_line = str(file1_line).strip(r"b|\\n|\n|'|\"").split(',')
        striped_file2_line = str(file2_line).strip(r"b|\\n|\n|'|\"").split(',')

        source1 = striped_file1_line[0]
        source2 = striped_file2_line[0]

        target1 = striped_file1_line[1]
        target2 = striped_file2_line[1]

        erro1 = striped_file1_line[2].split(':')
        erro2 = striped_file2_line[2].split(':')

        distance1 = striped_file1_line[3]
        distance2 = striped_file2_line[3]

        # Caso de erro
        if is_erro(erro1) or is_erro(erro2):
            if not (is_erro(erro1) and is_erro(erro2)):
                if is_erro(erro1):
                    raise Exception("file1 apresentou o erro: "+str(erro1))

                elif is_erro(erro2):
                    raise Exception("file2 apresentou o erro: "+str(erro2))

        elif source1 != source2:
            raise Exception("O source "+source1+" do file1 é diferente do source "+source2+" do file2")

        elif target1 != target2:
            raise Exception("O target "+target1+" do file1 é diferente do target "+target2+" do file2")

        else:
            d1 = float(distance1)
            d2 = float(distance2)

            delta = abs(d2 - d1)

            if delta > 00.0001:
                if biggest_delta < delta:
                    biggest_delta = delta

                error_total += delta
                error_percent_total += d1/d2 if d1 < d2 else d2/d1
                contrast_list.append("A distância "+distance1+" do file1 é diferente da distância "+distance2+" do file2")

    if showdifferences:
        for difference in contrast_list:
            logger(difference)

    file_size = len(file1_lines) - 1 # - cabeçalho
    erro_medio = error_total/file_size
    erro_percent_medio = error_percent_total/file_size

    qtd_error = len(contrast_list)

    logger('\nTotal de diferenças entre o arquivo (file1) \n' + file1 + '\ne o arquivo (file2)\n' + file2 + '\né de ' + str(qtd_error))
    logger('Erro total: ' + str(error_total))
    logger('Erro máximo: ' + str(biggest_delta))
    logger('Erro médio: ' + str(erro_medio))
    logger('Erro percentual médio: ' + str(erro_percent_medio * 100))
    logger('Acurácia: ' + str((1 - (qtd_error / file_size)) * 100))

def compare_erros(file1, file2):
    file1_lines = get_lines(file1)
    error_list1 = []
    counter1 = 0

    file2_lines = get_lines(file2)
    error_list2 = []
    counter2 = 0

    def test_erro(erro_array1, erro_array2):
        erro1 = None
        erro2 = None

        if len(erro_array1) >= 2:
            erro1 = erro_array1[1]

        if len(erro_array2) >= 2:
            erro2 = erro_array2[1]

        if erro1 and erro2 is None:
            return True

    for index, file1_line in enumerate(file1_lines):
        file2_line = file2_lines[index]

        striped_file1_line = str(file1_line).strip(r"b|\\n|\n|'|\"").split(',')
        striped_file2_line = str(file2_line).strip(r"b|\\n|\n|'|\"").split(',')

        file1_erro_array = striped_file1_line[2].split(':')
        file2_erro_array = striped_file2_line[2].split(':')

        if test_erro(file1_erro_array, file2_erro_array):
            counter1 +=1
            error_list1.append((file1_line, file2_line))

        if test_erro(file2_erro_array, file1_erro_array):
            counter2 +=1
            error_list2.append((file2_line, file1_line))

    def show_result(file1, file2, counter, error_list):
        logger(file2+' \npossui ' + str(counter) + ' erros a mais que \n'+file1 + '\n')
        if counter:
            logger('Erros: ')
        for item in error_list:
            logger(item[0])
            logger(item[1])
            logger('')

    show_result(file2, file1, counter1, error_list1)
    logger('------------------------------------------------------------------------------------------------\n')
    show_result(file1, file2, counter2, error_list2)

def calcular_erros_percent(filename):
    with open(filename, 'rb') as fileObject:
        counter = 0
        error_count = 0
        error_dict = {}

        for line in fileObject.readlines():
            striped_line = str(line).strip(r"b|\\n|\n|'|\"").split(',')
            counter += 1

            if len(striped_line) >= 3:
                erro_array = striped_line[2].split(':')

                if len(erro_array) >= 2:
                    error_count += 1
                    error_number = erro_array[1]

                    if  error_number not in error_dict:
                        error_dict[error_number] = 0

                    error_dict[error_number] += 1

        logger(filename)
        logger('Total de linhas: ' + str(counter))
        logger('Total de erros: ' + str(error_count))
        logger('Normal execution: ' + str((counter-error_count)/counter*100) + '%')

        for error_code in error_dict:
            error_qtd = float(error_dict[error_code])
            error_percent = error_qtd/counter

            logger('Error: ' + str(error_code) + ' => '+str(error_qtd) +' * 100 / ' + str(counter) + ' = ' + str(error_percent * 100) + '%' )

def show_accuracy(file1, file2, show_graphic=True, not_save=False, logger=logger):
    contrast_list = []
    file1_lines = get_lines(file1)
    file_size = len(file1_lines) - 1 # - cabeçalho

    file2_lines = get_lines(file2)
    error_total = 0
    error_percent_total = 0
    biggest_delta = 0

    total_lines = 1

    progressive_data = []
    progress = 10

    for index, file1_line in enumerate(file1_lines):
        if index == 0:
            continue

        file2_line = file2_lines[index]

        striped_file1_line = str(file1_line).strip(r"b|\\n|\n|'|\"").split(',')
        striped_file2_line = str(file2_line).strip(r"b|\\n|\n|'|\"").split(',')

        source1 = striped_file1_line[0]
        source2 = striped_file2_line[0]

        target1 = striped_file1_line[1]
        target2 = striped_file2_line[1]

        erro1 = striped_file1_line[2].split(':')
        erro2 = striped_file2_line[2].split(':')

        distance1 = striped_file1_line[3]
        distance2 = striped_file2_line[3]

        # Caso de erro
        if is_erro(erro1) or is_erro(erro2):
            if not (is_erro(erro1) and is_erro(erro2)):
                if is_erro(erro1):
                    raise Exception("file1 apresentou o erro: "+str(erro1))

                elif is_erro(erro2):
                    raise Exception("file2 apresentou o erro: "+str(erro2))

        elif source1 != source2:
            raise Exception("O source "+source1+" do file1 é diferente do source "+source2+" do file2")

        elif target1 != target2:
            raise Exception("O target "+target1+" do file1 é diferente do target "+target2+" do file2")

        else:
            d1 = float(distance1)
            d2 = float(distance2)

            delta = abs(d2 - d1)

            if delta > 00.0001:
                if biggest_delta < delta:
                    biggest_delta = delta

                error_total += delta
                error_percent_total += d1/d2 if d1 < d2 else d2/d1
                contrast_list.append("A distância "+distance1+" do file1 é diferente da distância "+distance2+" do file2")

        if index % progress == 0:
            erro_medio = error_total/total_lines
            erro_percent_medio = error_percent_total/total_lines

            qtd_error = len(contrast_list)
            total_acc = (1 - (qtd_error / total_lines)) * 100

            progressive_data.append((erro_medio, erro_percent_medio, total_acc, total_lines))

        total_lines += 1

    file_size = len(file1_lines) - 1 # - cabeçalho
    erro_medio = error_total/file_size
    erro_percent_medio = error_percent_total/file_size

    qtd_error = len(contrast_list)
    total_acc = (1 - (qtd_error / file_size)) * 100

    logger('\nTotal de diferenças entre o arquivo (file1) \n' + file1 + '\ne o arquivo (file2)\n' + file2 + '\né de ' + str(qtd_error))
    logger('Acurácia Total: ' + str(total_acc))
    logger('Erro total: ' + str(error_total))
    logger('Erro máximo: ' + str(biggest_delta))
    logger('Erro médio: ' + str(erro_medio))
    logger('Erro percentual médio Total: ' + str(erro_percent_medio * 100))
    logger('Total de Linhas Percorridas:' + str(total_lines))

    erros_medios = []
    erros_percents_medios = []
    totais_acc = []
    totais_lines = []

    for erro_medio, erro_percent_medio, total_acc, total_lines in progressive_data:
        erros_medios.append(erro_medio)
        erros_percents_medios.append(erro_percent_medio * 100)
        totais_acc.append(total_acc)

        totais_lines.append(total_lines)

    plt.subplot(3, 1, 1)
    plt.plot(totais_lines, erros_medios)
    plt.ylabel('Erros Médios')

    plt.subplot(3, 1, 2)
    plt.plot(totais_lines, erros_percents_medios)
    plt.ylabel('Erros Percentuais Médios')

    plt.subplot(3, 1, 3)
    plt.plot(totais_lines, totais_acc)
    plt.ylabel('Acurácia')

    plt.xlabel('Total de Linhas Percorridas')

    file2_array_name = os.path.basename(file2).split('-network')

    if not not_save:
        plt.savefig('python_applications/output/img/' + file2_array_name[0] + ".png")

    if show_graphic:
        plt.show()

def accuracyInfo():
    raios = ["50", "100", "250", "500"]
    cidades = ["Fortaleza", "Paris", "Toquio", 'NY', 'Mumbai']

    for cidade in cidades:
        for raio in raios:
            file1 = 'caracterizacao-dados-reviews/saida/250/BruteForce-20000-'+raio+'.0-'+cidade+'-network-osm-2018-1.txt'
            file2 = 'caracterizacao-dados-reviews/saida/250/20000-'+raio+'.0-'+cidade+'-network-osm-2018-1.txt'

            fileLogger("======================Montar Gráficos======================")
            fileLogger("======================"+raio+"="+cidade+"======================")
            try:
                show_accuracy(file1, file2, show_graphic=False, not_save=True, logger=fileLogger)
            except Exception:
                fileLogger("Não iniciado")

def specificInfo():
    folder = "caracterizacao-dados-reviews/saida/250/"
    ods = "20000"
    radius = "100.0"
    city = "Paris"

    filename = folder + 'BruteForce-'+ods+'-'+radius+'-'+city+'-network-osm-2018-1.txt'
    filename2 = folder + ods+'-'+radius+'-'+city+'-network-osm-2018-1.txt'
    # filename2 = 'caracterizacao-dados-reviews/saida/Admissible-50000-50.0-Fortaleza-network-osm-2018-1.txt'

    try:
        # logger("=====================Calcular erros(%)=====================")
        # calcular_erros_percent(filename)
        # logger()
        # calcular_erros_percent(filename2)

        # logger()
        # logger("======================Comparar erros=======================")
        # compare_erros(filename, filename2)

        # logger()
        # logger("===================Localizar diferenças====================")
        # find_constrat(filename, filename2, True)

        logger()
        logger("======================Montar Gráficos======================")
        show_accuracy(filename, filename2, show_graphic=True, not_save=True)

    except ZeroDivisionError:
        logger("Não iniciado")

specificInfo()
# accuracyInfo()


from openerp import models, fields, api, _
from openerp.exceptions import ValidationError

class NpoObi(models.Model):
    _name = 'npo.obi'
    _description = 'Originator to Beneficiary Information'

    name = fields.Char(
        string='Name',
        size=256,
        required=True,
    )
    description = fields.Char(
        string='Description',
        size=256,
        required=False,
    )
    active = fields.Boolean(
        string='Active',
        default=True,
    )
    _sql_constraints = [
        ('uniq_name', 'unique(name)', "The name of this OBI must be unique !"),
    ]

class NpoProjectCateg(models.Model):
    _name = 'npo.project.categ'
    _description = 'Project Category'

    name = fields.Char(
        string='Name',
        size=256,
        required=True,
    )
    description = fields.Char(
        string='Description',
        size=256,
        required=False,
    )
    project_ids = fields.One2many(
        'npo.project',
        'project_categ_id',
        string='Projects under this Category',
        readonly=False,
    )
    active = fields.Boolean(
        string='Active',
        default=True,
    )
    _sql_constraints = [
        ('uniq_name', 'unique(name)',
         "The name of this project category must be unique !"),
    ]

class NpoProject(models.Model):
    _name = 'npo.project'
    _description = 'Project'

    name = fields.Char(
        string='Name',
        size=256,
        required=True,
    )
    description = fields.Char(
        string='Description',
        size=256,
        required=False,
    )
    project_categ_id = fields.Many2one(
        'npo.project.categ',
        string='Project Category',
        default=lambda self: self._context.get('active_id', False),
    )
    project_line_ids = fields.One2many(
        'npo.project.line',
        'project_id',
        string='Project Lines under this project',
        readonly=False,
    )
    start_period_id = fields.Many2one(
        'account.period',
        string='Start Period',
        required=True,
    )
    end_period_id = fields.Many2one(
        'account.period',
        string='End Period',
        required=True,
    )
    date_start = fields.Date(
        related='start_period_id.date_start',
        string='Start date',
        store=True,
        readonly=True,
    )
    date_stop = fields.Date(
        related='end_period_id.date_stop',
        string='End date',
        store=True,
        readonly=True,
    )
    active = fields.Boolean(
        string='Active',
        default=True,
    )
    _sql_constraints = [
        ('uniq_name', 'unique(name, project_categ_id, start_period_id)',
         "The project name and period duration must be unique!"),
    ]

    @api.multi
    def name_get(self):
        result = []
        for rec in self:
            result.append(
                (rec.id, "[%s] %s" % (rec.project_categ_id.name, rec.name)))
        return result

    @api.model
    def _estimate_next_period(self):
        project = self
        if not project.start_period_id or not project.end_period_id:
            raise ValidationError(
                _('There is no period duration define for this project'))
        Period = self.env['account.period']
        period_start = project.start_period_id
        period_end = project.end_period_id
        num_periods = \
            Period.search_count([('special', '=', False),
                                 ('date_start', '>=', period_start.date_start),
                                 ('date_stop', '<=', period_end.date_stop)])
        start_period_id = Period.next(project.end_period_id, 1)
        end_period_id = Period.next(project.end_period_id, num_periods)
        if start_period_id and Period.browse(start_period_id).special:
            start_period_id = Period.next(project.end_period_id, 2)
            end_period_id = Period.next(project.end_period_id, num_periods+1)
        return start_period_id, end_period_id

    @api.one
    def copy(self, vals):
        vals.update(name=_("%s (copy)") % (self.name or ''))
        # Set default period to be next period, plus number of period.
        start_period_id, end_period_id = self._estimate_next_period()
        vals.update({'start_period_id': start_period_id,
                     'end_period_id': end_period_id})
        return super(NpoProject,
                     self.with_context(start_period_id=start_period_id,
                                       end_period_id=end_period_id)).copy(vals)

    @api.onchange('start_period_id')
    def _onchange_start_period_id(self):
        Period = self.env['account.period']
        self.end_period_id = Period.next(self.start_period_id, 11)

class NpoProjectLine(models.Model):
    _name = 'npo.project.line'
    _description = 'Project Line'

    name = fields.Char(
        string='Name',
        size=256,
        required=True,
    )
    description = fields.Char(
        string='Description',
        size=256,
        required=False,
    )
    project_id = fields.Many2one(
        'npo.project',
        string='Project',
    )
    project_categ_id = fields.Many2one(
        'npo.project.categ',
        related='project_id.project_categ_id',
        string='Project Category',
        store=True,
        readonly=True,
    )
    budget_alloc_total = fields.Float(
        string='Total Budget Allocation',
        compute='_compute_sum_total',
        store=True,
        readonly=True,
    )
    budget_used_total = fields.Float(
        string='Total Budget Used',
        compute='_compute_sum_total',
        store=True,
        readonly=True,
    )
    budget_diff_total = fields.Float(
        string='Total Budget Difference',
        compute='_compute_sum_total',
        store=True,
        readonly=True,
    )
    budget_line = fields.One2many(
        'npo.project.line.budget.line',
        'project_line_id',
        string='Budget Lines',
        readonly=False,
    )
    statement_line = fields.One2many(
        'account.bank.statement.line',
        'project_line_id',
        string='Statement Lines',
        readonly=True,
    )
    activity_ids = fields.Many2many(
        'npo.activity',
        'project_line_activity_rel',
        'project_line_id', 'activity_id',
        string='Activity',
        required=False,
    )
    active = fields.Boolean(
        string='Active',
        default=True,
    )
    start_period_id = fields.Many2one(
        'account.period',
        related='project_id.start_period_id',
        string='Start Period',
        store=True,
        readonly=True,
    )
    end_period_id = fields.Many2one(
        'account.period',
        related='project_id.end_period_id',
        string='End Period',
        store=True,
        readonly=True,
    )
    date_start = fields.Date(
        related='start_period_id.date_start',
        string='Start date',
        store=True,
        readonly=True,
    )
    date_stop = fields.Date(
        related='start_period_id.date_stop',
        string='Stop date',
        store=True,
        readonly=True,
    )
    account_id = fields.Many2one(
        'account.account',
        string='Account',
        domain=[('type', '!=', 'view')],
        required=True,
    )
    _sql_constraints = [
        ('uniq_name',
         'unique(name, project_id, project_categ_id, start_period_id)',
         "The name of this project line must be unique !"),
    ]

    @api.multi
    @api.depends('budget_line',
                 'budget_line.budget_alloc',
                 'budget_line.budget_used',
                 'budget_line.budget_diff',
                 'statement_line.amount',
                 'statement_line.project_line_id',
                 'statement_line.obi_id')
    def _compute_sum_total(self):
        for project_line in self:
            project_line.budget_alloc_total = 0.0
            project_line.budget_used_total = 0.0
            project_line.budget_diff_total = 0.0
            for budget_line in project_line.budget_line:
                project_line.budget_alloc_total += budget_line.budget_alloc
                project_line.budget_used_total += budget_line.budget_used
            project_line.budget_diff_total = (project_line.budget_alloc_total -
                                              project_line.budget_used_total)

    @api.multi
    def write(self, vals):
        if vals is None:
            vals = {}
        start_period_id = vals.get('start_period_id', False)
        end_period_id = vals.get('end_period_id', False)
        if start_period_id and end_period_id:
            for line in self:
                if start_period_id < line.project_id.start_period_id.id or \
                        end_period_id > line.project_id.end_period_id.id:
                    raise ValidationError(
                        _("Project line's period must lies "
                          "between its project's period"))
        return super(NpoProjectLine, self).write(vals)

class NpoProjectLineBudgetLine(models.Model):
    _name = 'npo.project.line.budget.line'
    _description = 'Project Line Budget Line'

    @api.multi
    @api.depends('project_line_id.statement_line',
                 'project_line_id.statement_line.amount',
                 'project_line_id.statement_line.project_line_id',
                 'project_line_id.statement_line.obi_id',
                 'project_line_id.budget_line',
                 'project_line_id.budget_line.budget_alloc')
    def _compute_budget_used(self):
        for line in self:
            line.budget_used = 0.0
            line.budget_diff = 0.0
            if isinstance(line.project_line_id.id, int) and \
                    isinstance(line.obi_id.id, int):
                self._cr.execute("""
                    select sum(amount) from account_bank_statement_line sl
                    join account_bank_statement s on s.id = sl.statement_id
                    where project_line_id = %s
                    and obi_id = %s""", (line.project_line_id.id,
                                         line.obi_id.id))
                result = dict(self._cr.dictfetchone())
                line.budget_used = result['sum'] and - result['sum'] or 0.0
                line.budget_diff = line.budget_alloc - line.budget_used

    project_line_id = fields.Many2one(
        'npo.project.line',
        string='Project Line',
        ondelete="cascade",
    )
    budget_alloc = fields.Float(
        string='Budget Allocation',
    )
    budget_used = fields.Float(
        compute='_compute_budget_used',
        string='Budget Used',
        store=True,
        readonly=True,
    )
    budget_diff = fields.Float(
        compute='_compute_budget_used',
        string='Budget Difference',
        store=True,
        readonly=True,
    )
    obi_id = fields.Many2one(
        'npo.obi',
        string='Donor',
        required=True,
    )
    _sql_constraints = [
        ('uniq_donor', 'unique(obi_id, project_line_id)',
         "Donor must be unique!"),
    ]

    @api.model
    def create(self, vals):
        if vals is None:
            vals = {}
        vals.update({'budget_alloc': False,
                     'end_period_id': self._context.get('end_period_id')})
        return super(NpoProjectLineBudgetLine, self).create(vals)

class NpoActivity(models.Model):
    _name = 'npo.activity'
    _description = 'Activity'

    name = fields.Char(
        string='Name',
        size=256,
        required=True,
    )
    active = fields.Boolean(
        string='Active',
        default=True,
    )
    project_line_ids = fields.Many2many(
        'npo.project.line',
        'project_line_activity_rel',
        'activity_id', 'project_line_id',
        string='Project Lines',
        required=False,
    )
    _sql_constraints = [
        ('uniq_name', 'unique(name)', "The name of this OBI must be unique !"),
    ]


from django.urls import path,include
from .views import *

urlpatterns = [
path('inicio/', inicio, name='inicio-lab'),
path('insertar/', insertar_lab, name='insertar-lab'),
path('mostrar/', mostrar_lab, name='mostrar-lab'),
path('editar/<int:pk>', editar_lab, name='editar-lab'),
path('actualizar/<int:pk>', actualizar_lab, name='actualizar-lab'),
path('eliminar/<int:pk>', eliminar_lab, name='eliminar-lab'),
]


# import math as maths
# import random
# import numpy as np
# import pygame
# import cv2 as cv

# def main():
#     X = [] #

# if __name__ == "__main__":
#     main()
import numpy as np

dist = [50,100,200,100,10]

dist = np.array(dist).reshape(len(dist),1)
print(dist.shape)

# n_inputs = 5
# n_neurons = 6
# print(0.1*np.random.randn(n_neurons,n_inputs))


import collections

import time
import datetime
import csv

import json
import requests
import pika

def runAPICall(event, context):

    api_obs = []
    file_data = []
    rain_rates = []
    averages = []
    average = 0
    prev_rain_total = 0
    current_rain_total = 0
    rain_last10mins = 0
    rain_rate_average = 0.0
    rain_rate_av = ""

    # flag to indicate whether we want to send to rabbitmq
    send_message = 1
    # flag to indciate whether to write to file
    write_to_file = 0

    # run with static file data
    with open('sample_rain_data.json') as f:
        file_data = json.load(f)

    response = file_data
    #print(response)

    print('\nDateTime|Rain last 10 mins|Total rainfall|Rain rate|Rain rate last 10 mins|Rainfall duration\n')

    # if write to file is required, open the file handle and write the header
    if(write_to_file ==1):
        with open('rain_data_out.csv', mode='w',  newline='') as csv_file:
            fieldnames = ['last recorded time', 'rain last 10 mins', 'current rain total', 'rain rate last 10 mins', 'rain rate average', 'rain duration in mins' ]
            writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
            writer.writeheader()

    # loop through the data set using the total number of observations as a limit
    for i in range(len(response['observations'])):

        # get each rain rate measurement
        rain_rate_average =response['observations'][i]['metric']['precipRate']

        # check for invalid values: if it's not the first reading use previous value instead, otherwise reset to zero
        if(rain_rate_average > 300) or (rain_rate_average < 0):
            if(i != 0):
                rain_rate_average = response['observations'][i-1]['metric']['precipRate']
            else:
                rain_rate_average = 0.0

        # if its not the first reading use the previous value
        if(i != 0):
            prev_rain_total=(response['observations'][i-1]['metric']['precipTotal'] )

        current_rain_total=(response['observations'][i]['metric']['precipTotal'] )
        rain_last10mins = (current_rain_total-prev_rain_total)
        rain_last10mins = round(rain_last10mins, 2)
        last_recorded_timeUTC=(response['observations'][i]['obsTimeUtc'] )
        ## format last recorded time
        last_recorded_time = datetime.datetime.strptime(last_recorded_timeUTC, "%Y-%m-%dT%H:%M:%SZ")

        print(last_recorded_time ,'|', rain_last10mins,'|', current_rain_total, '|',  rain_rate_average, '\n')

        # write each row to file
        if (write_to_file == 1):
            writer.writerow({'last recorded time': last_recorded_time, 'rain last 10 mins': rain_last10mins, 'current rain total': current_rain_total, 'rain rate average': rain_rate_average })

        if(send_message == 1):

            # set up connection to  rabbitmq
            connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
            if(connection):
                channel = connection.channel() # start a channel

                # not needed now as set up in rabbit config
                # channel.exchange_declare(exchange='rabt-rainfall-exchange', exchange_type='direct')
                # channel.queue_declare(queue='rabt-rainfall-queue') # declare a queue, then bind the queue to the exchange
                # channel.queue_bind(queue='rabt-rainfall-queue', exchange='rabt-rainfall-exchange', routing_key='rabt-rainfall-queue')

                # send a message
                # routing key must match the queue name

                json_map = {}
                json_map["last-recorded-time"] = str(last_recorded_time)
                json_map["rain-last-10-mins"] = rain_last10mins
                json_map["current-rain-total"] = current_rain_total
                json_map["rain-rate-average"] = rain_rate_average
                message = json.dumps(json_map)

                channel.basic_publish(exchange='rabt-rainfall-exchange', routing_key='rabt-rainfall-queue', body=message)
                print ("Message sent to consumer")
                connection.close()

            # this will send a a message to rabbitmq given that it connects correctly
            # if consumer.py is running at the same time, the messages will appear in the console, otherwise they will wait in the 'rainfall' queue

            # increment i
            i = i + 1

    return(event)

runAPICall(None,None)


import sys
from collections import deque

input = sys.stdin.readline

n = int(input())
a = list(map(int, input().split()))

s = [0]
ans = [0] * n

for i in range(1, n):
  while s and a[i] > a[s[-1]]:
    ans[s[-1]] = a[i]
    s.pop()
  s.append(i)

for i in range(n):
  if ans[i] == 0:
    ans[i] = -1

for el in ans:
  print(el, end=' ')
print()


import xml.etree.ElementTree as ET
import json

Dragon = {}
Illuminati = {}
Templar = {}

filelist = ["illuminati.json", "dragon.json", "templar.json"]

FactionData = {}
for file in filelist:
	with open(file) as json_file:
		jsonData = json.load(json_file)
		for key in jsonData:
			FactionData[key] = jsonData[key]

SharedData = {}
with open("all.json") as json_file:
	data = json.load(json_file)
	for key in data:
		SharedData[key] = data[key]

root = ET.Element('root')
for i in FactionData:
	ET.SubElement(root, "Q", t=FactionData[i], q=i)
tree = ET.ElementTree(root)
tree.write("Faction.xml")

root = ET.Element('root')
for i in SharedData:
	ET.SubElement(root, "Q", t=SharedData[i], q=i)
tree = ET.ElementTree(root)
tree.write("Shared.xml")


import struct
import random
from vector import *

#Write utils
def char(c):
	# 1 byte
	return struct.pack('=c', c.encode('ascii'))

def word(w):
	# 2 bytes
	return struct.pack('=h', w)

def dword(d):
	# 4 bytes
	return struct.pack('=l', d)

def color(r, g, b):
	return bytes([b, g, r])

BLACK = color(0, 0, 0)
WHITE = color(255, 255, 255)

#importar esta clase en gllib
class Render(object):
	def __init__(self, width, height):
		self.width = width
		self.height = height
		self.clear()

	def clear(self):
		self.framebuffer = [
			[BLACK for x in range(self.width)]
			for y in range(self.height)
		]

		self.zBuffer = [
			[-9999 for x in range(self.width)]
			for y in range(self.height)
		]
		print(len(self.zBuffer[0]), "len")

	#no se necesita nombre al escribir a memoria de video
	def write(self, filename):
		f = open(filename, 'bw')

		# pixel header
		f.write(char('B'))
		f.write(char('M'))

		# file size
		f.write(dword(14 + 40 + self.width * self.height * 3))

		f.write(word(0))
		f.write(word(0))

		f.write(dword(14 + 40))

		# info header
		f.write(dword(40))
		f.write(dword(self.width))
		f.write(dword(self.height))
		f.write(word(1))
		# true color
		f.write(word(24))
		f.write(dword(0))
		f.write(dword(self.width * self.height * 3))
		f.write(dword(0))
		f.write(dword(0))
		f.write(dword(0))
		f.write(dword(0))

		# pixel data

		for x in range (self.height):
			for y in range(self.width):
				f.write(self.framebuffer[x][y])

		f.close()

	def point(self, x, y):
		if (0 < x < self.width and 0 < y < self.height):
			self.framebuffer[x][y] = WHITE

	def set_current_color(self, c):
		self.current_color = c

# clase vector


import turtle

t = turtle.Turtle()

radius = 100
count = 10

while count > 1:
  if count % 2 == 0:
    t.color("blue")
  else:
    t.color("green")
  t.begin_fill()
  t.circle(radius)
  t.pu()
  t.left(90)
  t.forward(10)
  t.right(90)
  t.pd()
  radius -= 10
  t.end_fill()
  count -= 1


#Napisati program koji za svaki uneti celi broj određuje da li uneti broj sadrži
#više parnih ili neparnih cifara. Unos prekinuti nakon što korisnik unese broj
#čije su sve cifre parne ili sve cifre neparne. Napomena: Ne koristiti operacije
#nad stringovima, već brojevima
parni=0
neparni=0
prekid=0
while prekid!=1:
    uneti_broj=float(input("Unesite celi broj: "))   # Dozvoljavamo unos realnih brojeva ali korisniku ispisujemo poruku da unese celi broj
    if uneti_broj == 0:
        print("Broj ima vise parnih nego neparnih cifara")
        prekid = 1
    if uneti_broj<0:                                 # ako je korisnik uneo negativan broj pretvaramo ga u pozitvan
        uneti_broj=uneti_broj*(-1)                   # da bi naredni deo koda radio
    if ((uneti_broj*10)%10) != 0:                    # provera da li je broj ceo
        print("Niste uneli celi broj")
    else:
        while uneti_broj> 0:                        # brojanje parnih i neparnih cifara
            cifra =uneti_broj%10
            if (cifra%2 ==0 ):
                parni+=1
            else:
                neparni+=1
            uneti_broj=(uneti_broj-uneti_broj%10)/10
        if parni>neparni:                                                   #
            print("Broj ima vise parnih nego neparnih cifara")
        else:
            if parni<neparni:
                print("Broj ima vise neparnih cifara")
            else:
                if parni==neparni!=0:                                  # razlicito od nule jer smo na pocetku za nulu stavili poseban uslov
                    print("Broj parnih cifara jednak je broju neparnih cifara")
    if (parni==0 and neparni!=0) or (parni!=0 and neparni==0) or (parni==0 and neparni==0):    # uslov da samo broj parnih ili samo broj neparnih bude 0 ili ako su oba istovremeno 0
        prekid=1
        print("Prekid programa")
    else:
        prekid=0
        parni=0
        neparni=0


import pymongo

myclient = pymongo.MongoClient("mongodb://localhost:27017/")
mydb = myclient["projetoDestiny"]
mycol = mydb["guardian"]

myquery = { "name": "GuiCastro" }
newvalues = { "$set": { "name": "Gui" } }

mycol.update_one(myquery, newvalues)

for x in mycol.find():
  print(x)


from __future__ import print_function
import httplib2
import os
import re
from dateutil import parser
from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage
from datetime import datetime, timedelta, timezone
import pytz
import json

try:
    import argparse
    ap = argparse.ArgumentParser(parents=[tools.argparser])
    ap.add_argument('--file', type=argparse.FileType('r'), required=True)
    ap.add_argument('--yes', action='store_true', default=False)
    flags = ap.parse_args()
except ImportError:
    flags = None

import sys

def query_yes_no(question, default="yes"):
    """Ask a yes/no question via input() and return their answer.

    "question" is a string that is presented to the user.
    "default" is the presumed answer if the user just hits <Enter>.
        It must be "yes" (the default), "no" or None (meaning
        an answer is required of the user).

    The "answer" return value is True for "yes" or False for "no".
    """
    valid = {"yes": True, "y": True, "ye": True,
             "no": False, "n": False}
    if default is None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("invalid default answer: '%s'" % default)

    while True:
        sys.stdout.write(question + prompt)
        if flags.yes and default is not None:
            print(default)
            return valid[default]

        choice = input().lower()
        if default is not None and choice == '':
            return valid[default]
        elif choice in valid:
            return valid[choice]
        else:
            sys.stdout.write("Please respond with 'yes' or 'no' "
                             "(or 'y' or 'n').\n")

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/calendar-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/calendar'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Google Calendar API Python Quickstart'

def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir,
                                   'calendar-python-quickstart.json')

    store = Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else: # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials

def main():
    """Shows basic usage of the Google Calendar API.

    Creates a Google Calendar API service object and outputs a list of the next
    10 events on the user's calendar.
    """
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    service = discovery.build('calendar', 'v3', http=http)

    print('Getting the upcoming events')
    cals = service.calendarList().list().execute().get('items',[])
    kj = 'K & J'
    fl = 'Flight Lessons'
    kj_id = next(obj['id'] for obj in cals if obj['summary'] == kj)
    fl_id = next(obj['id'] for obj in cals if obj['summary'] == fl)
    print(kj + " :: " + kj_id)
    print(fl + " :: " + fl_id)
    deleteFromCalendar(service, kj, kj_id)
    deleteFromCalendar(service, fl, fl_id)

    events = parseEvents()
    if events:
        addEventsToCalendar(service,kj,kj_id,events,None)
        addEventsToCalendar(service,fl,fl_id,events,1)
        addEventsToCalendar(service,fl,fl_id,events,24)

def addEventsToCalendar(service, desc, calid,events, alarm):
    alarm_desc = "no alarm"
    alarms = []
    if alarm:
        alarm_desc = "a alarm " +str(alarm) + " hours before"
        alarms = [{'minutes':alarm*60,'method':'popup'}]
    if (query_yes_no("Add events to " + desc + " with " + alarm_desc + "?")):
        for event in events:
            body = {
                'summary': "AboveAll: " + event['instructor'] + " with " + event['plane'],
                'start':{'dateTime': event['start'].isoformat()},
                'end':{'dateTime': event['end'].isoformat()},
                'location': '1503 Cook Pl, Santa Barbara, CA 93117',
                'reminders':{
                    'useDefault': False,
                    'overrides': alarms
                    }
                }
            service.events().insert(
                calendarId=calid,
                body=body
            ).execute()

def getDateTime(string):
    time_format = "%m/%d/%y %H:%M%p"
    date = datetime.strptime(string + "M", time_format)
    date = pytz.timezone('US/Pacific').localize(date)
    return date

def json_serial(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    raise TypeError ("Type %s not serializable" % type(obj))

def parseEvents():
    if(not flags.file):
        print("No file given to parse")
        return None
    regex = re.compile('(.*) beginning (.*) (?:until|through) (.*)')
    result = []
    lines = [line for line in flags.file]
    now = datetime.now(timezone.utc)
    for f in lines:
        f = f.strip()
        if f:
            match = regex.match(f)
            if match:
                start = getDateTime(match.group(2))
                if(start < now): continue

                event = next((e for e in result if e['start'] == start),None)
                if(not event):
                    event = {}
                    event['plane'] = "no plane"
                    result.append(event)

                event['start'] = start
                event['end'] = getDateTime(match.group(3))
                if ("172" in match.group(1) or "152" in match.group(1)):
                    event['plane'] = match.group(1)
                else:
                    event['instructor'] = match.group(1)
    bad_events = [e for e in result if not 'instructor' in e]
    if bad_events:
        print(json.dumps(bad_events, indent=4, sort_keys=True, default=str))
        raise Exception("Every lesson needs and instructor")
    return result

def deleteFromCalendar(service, desc, calid):
    now = datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
    print("Looking for future AboveAll notifications in " + desc + "...")
    eventsResult = service.events().list(
        calendarId=calid, timeMin=now, singleEvents=True,
        orderBy='startTime', q='AboveAll'
        ).execute()
    events = eventsResult.get('items', [])
    if not events:
        print('No upcoming events found.')
    if events:
        print("Found " + str(len(events)) + " events")
        if query_yes_no("Removing future lessons from " + desc + "?", "yes"):
            for event in events:
                sys.exit(1)
                service.events().delete(calendarId=calid, eventId=event['id']).execute()
            print("Done deleting from " + desc)

if __name__ == '__main__':
    main()


# https://school.programmers.co.kr/learn/courses/30/lessons/118666
# 성격 유형 검사하기

# 지표 번호	성격 유형
# 1번 지표	라이언형(R), 튜브형(T)
# 2번 지표	콘형(C), 프로도형(F)
# 3번 지표	제이지형(J), 무지형(M)
# 4번 지표	어피치형(A), 네오형(N)

# 1	매우 비동의 // 3
# 2	비동의      // 2
# 3	약간 비동의 // 1
# 4	모르겠음    // 0
# 5	약간 동의   // 1
# 6	동의        // 2
# 7	매우 동의   // 3

def solution(survey , choice):
    MyMBTI = ""

    MBTI = {
        "R" : 0, "T" : 0,

        "C" : 0, "F" : 0,

        "J" : 0, "M" : 0,

        "A" : 0, "N" : 0
    }
    for x in range(0,len(survey)):
        if choice[x] < 4:
            MBTI[survey[x][0]] += (4 - choice[x])
        elif choice[x] > 4:
            MBTI[survey[x][1]] += (choice[x] - 4) # 딕셔너리에 value 값 추가하기 위함

    if MBTI["R"] > MBTI["T"] :
        MyMBTI += "R"
    elif MBTI["R"] < MBTI["T"] :
        MyMBTI += "T"
    else:
        MyMBTI += "R"

    if MBTI["C"] > MBTI["F"] :
        MyMBTI += "C"
    elif MBTI["C"] < MBTI["F"] :
        MyMBTI += "F"
    else:
        MyMBTI += "C"

    if MBTI["J"] > MBTI["M"] :
        MyMBTI += "J"
    elif MBTI["J"] < MBTI["M"] :
        MyMBTI += "M"
    else:
        MyMBTI += "J"

    if MBTI["A"] > MBTI["N"] :
        MyMBTI += "A"
    elif MBTI["A"] < MBTI["N"] :
        MyMBTI += "N"
    else:
        MyMBTI += "A"

    return MyMBTI # 노가다식.. 정답 맘에 안듬

    # MyMBTI += 'R' if MBTI['R'] >= MBTI['T'] else 'T'  위 식 깔끔하게 정리...
    # MyMBTI += 'C' if MBTI['C'] >= MBTI['F'] else 'F'  if 절 이렇게 쓰는 연습..
    # MyMBTI += 'J' if MBTI['J'] >= MBTI['M'] else 'M'
    # MyMBTI += 'A' if MBTI['A'] >= MBTI['N'] else 'N'

survey = ["AN", "CF", "MJ", "RT", "NA"]

choice = [5, 3, 2, 7, 5]

print(solution(survey,choice))


import pandas as pd
from xgboost import XGBClassifier
from sklearn.metrics import brier_score_loss, accuracy_score, log_loss, f1_score
from sklearn.calibration import CalibratedClassifierCV
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization
from joblib import dump

TRAINING_COLS = ['SeedDiff', 'EloWinProbA', 'WinRatioA', 'WinRatioB', 'ClutchRatioA', 'ClutchRatioB']
TARGET_COL = ['Win']
DEFAULT_PARAMS = {'gamma' : 0.499538, 'learning_rate' : 0.104466, 'max_depth' : 2, 'n_estimators' : 113}

def load_training_data():
    """
    Load in training data with pre-calculated features from ETL.

    Returns:
        Pandas DataFrame with training set.
    """
    return pd.read_csv('./data/etl/training_set.csv')

def build_model(training_data, training_columns=TRAINING_COLS, params={},
                calibrate=False, calibrator_size=0.2, random_state=0):
    """
    Fit given data to an XGBoost model.

    Args:
        training_data: Data used to train model. Must contain training columns.
        training_columns: Columns in training_data used to train model.
        params: Dictionary of XGBClassifier hyperparameters.
        calibrate: If True, fits a probability calibrator after training model.
        calibrator_size: Proportion of training set to use for calibration.
        random_state: Random value for model and calibration.

    Returns:
        Fitted XGBClassifier model, or CalibratedClassifierCV fitted with
        XGBClassifier if calibrate is True.
    """

    # If calibrating, split training set
    if calibrate:
        training_data, calib_data = train_test_split(training_data,
                                                     test_size=calibrator_size,
                                                     random_state=random_state)

    # Train standard model
    model = XGBClassifier(**params)
    x = training_data[training_columns]
    y = training_data[TARGET_COL]
    model.fit(x, y)

    # Fit calibrator
    if calibrate:
        model = CalibratedClassifierCV(model, method='sigmoid', cv='prefit')
        cx = calib_data[training_columns]
        cy = calib_data[TARGET_COL]
        model.fit(cx, cy)

    return model

def cross_validate_model(training_data, training_columns=TRAINING_COLS,
                         start_year=2017, end_year=2022, verbose=True, params={},
                         calibrate=False, random_state=0, calibrator_size=0.2):
    """
    Cross validate model by building a new model per year.

    Args:
        training_data: Data used to train model. Must contain training columns.
        training_columns: Columns in training_data used to train model.
        start_year: First year for validation.
        end_year: Final year for validation.
        verbose: If True, output validation metrics.
        params: Dictionary of XGBClassifier hyperparameters.
        calibrate: If True, fits a probability calibrator after training model.
        random_state: Random value for model and calibration.
        calibrator_size: Proportion of training set to use for calibration.

    Returns:
        Tuple containing validation accuracy, Brier score, log loss,
        F1 score, and calibrated Brier score. If calibrate is False then
        calibrated Brier score will be 0.
    """

    # Store metrics for evaluation
    metrics = {'acc' : [],
               'brier' : [],
               'logloss' : [],
               'f1' : [],
               'calib-brier' : []}

    for year in range(start_year, end_year+1):
        # Split into training and validation set (data before year, data of year)
        df_train = training_data[training_data.Season < year]
        df_valid = training_data[training_data.Season == year]
        df_valid_res = df_valid[TARGET_COL]

        # If validation set, then get predictions and metrics
        if df_valid.shape[0] > 0:
            # Build uncalibrated model with training set
            model = build_model(df_train, training_columns=training_columns,
                                params=params, calibrate=False)

            # Get predictions for year
            preds_proba = model.predict_proba(df_valid[training_columns])[:,1]
            preds_cat = [1 if x >= 0.5 else 0 for x in preds_proba]

            # Get metrics
            metrics['acc'].append(accuracy_score(df_valid_res, preds_cat))
            metrics['brier'].append(brier_score_loss(df_valid_res, preds_proba))
            metrics['logloss'].append(log_loss(df_valid_res, preds_proba))
            metrics['f1'].append(f1_score(df_valid_res, preds_cat))

            # If testing calibration, repeat brier with a calibrator
            if calibrate:
                model = build_model(df_train,
                                    training_columns=training_columns,
                                    params=params,
                                    random_state=random_state,
                                    calibrator_size=calibrator_size,
                                    calibrate=True)
                # Get predictions for year
                preds_proba = model.predict_proba(df_valid[training_columns])[:,1]
                preds_cat = [1 if x >= 0.5 else 0 for x in preds_proba]
                metrics['calib-brier'].append(brier_score_loss(df_valid_res, preds_proba))

            if verbose:
                print(year)
                print('Accuracy   ', metrics['acc'][-1])
                print('Brier Loss ', metrics['brier'][-1])
                print('Log Loss   ', metrics['logloss'][-1])
                print('F1 Score   ', metrics['f1'][-1])
                if calibrate:
                    print('Calib Brier', metrics['calib-brier'][-1])
                print()

    # Calculate final metrics
    avg_acc = sum(metrics['acc']) / len(metrics['acc'])
    avg_brier = sum(metrics['brier']) / len(metrics['brier'])
    avg_logloss = sum(metrics['logloss']) / len(metrics['logloss'])
    avg_f1 = sum(metrics['f1']) / len(metrics['f1'])
    avg_calib = 0
    if calibrate:
        avg_calib = sum(metrics['calib-brier']) / len(metrics['calib-brier'])

    # Output final metrics
    if verbose:
        print('Final Metrics')
        print('Accuracy   ', avg_acc)
        print('Brier Loss ', avg_brier)
        print('Log Loss   ', avg_logloss)
        print('F1 Score   ', avg_f1)
        if calibrate:
            print('Calib Brier', avg_calib)
        print()

    return avg_acc, avg_brier, avg_logloss, avg_f1, avg_calib

def tune_model_bayesian_optimisation(training_data, training_columns=TRAINING_COLS,
                                     iterations=5, initial_points=8,
                                     random_state=0, calibrate=False,
                                     calibrator_size=0.2):
    """
    Perform Bayesian Optimisation to find best hyperparameters for model.

    Args:
        training_data: Data used to train model. Must contain training columns.
        training_columns: Columns in training_data used to train model.
        iterations: Bayesian optimisation rounds. Integer.
        initial_points: Bayesian optimisation starting points. Integer.
        random_state: Random value for model and calibration.
        calibrate: If True, fits a probability calibrator after training model.
        calibrator_size: Proportion of training set to use for calibration.

    Returns:
        Dictionary of best hyperparameters for model.
    """
    def bo_tune_function(max_depth, gamma, n_estimators, learning_rate):
        """Objective function for Bayesian optimisation."""
        params = {'max_depth' : int(max_depth),
                  'gamma' : gamma,
                  'n_estimators' : int(n_estimators),
                  'learning_rate' : learning_rate}
        acc, brier, loglogg, f1, calib = cross_validate_model(training_data,
                                                              training_columns=training_columns,
                                                              verbose=False,
                                                              params=params,
                                                              calibrate=calibrate,
                                                              random_state=random_state,
                                                              calibrator_size=calibrator_size)
        if not calibrate:
            return -1*brier
        else:
            return -1*calib

    # Search field for bayesian optimisation of hyperparameters
    search_params = {'max_depth' : (2,7),
                     'gamma' : (0,1),
                     'n_estimators' : (50,200),
                     'learning_rate' : (0,1)}

    # Find best hyperparameters
    xgb_bo = BayesianOptimization(bo_tune_function, search_params, random_state=random_state)
    xgb_bo.maximize(n_iter=iterations, init_points=initial_points)
    best_params = xgb_bo.max['params']

    # Fix type issues
    best_params['max_depth'] = int(best_params['max_depth'])
    best_params['n_estimators'] = int(best_params['n_estimators'])

    return best_params

def validate_and_build_model(model_name='default_model', training_columns_men=TRAINING_COLS,
                             training_columns_women=TRAINING_COLS, tune=True, random_state=0,
                             optimisation_iterations=50, optimisation_initial_points=10,
                             calibrate=False, calibrator_size=0.2):
    """
    Get metrics with cross validation, then save complete model.

    Args:
        model_name: File name for model. String.
        training_columns_men: Columns in training_data used to train mens
            model.
        training_columns_women: Columns in training_data used to train womens
            model.
        tune: If True, then find best hyperparameters for model.
        random_state: Random value for model and calibration.
        optimisation_iterations: Bayesian optimisation rounds. Integer.
        optimisation_initial_points: Bayesian optimisation starting points.
            Integer.
        calibrate: If True, fits a probability calibrator after training model.
        calibrator_size: Proportion of training set to use for calibration.

    Returns:
        Validation Brier score if calibrate is False. Calibrated validation
        Brier score if calibrate is True.
    """

    # Load training data
    training_data = load_training_data()

    # Get separate mens and womens data
    training_data = {'men' : training_data.loc[training_data['Gender'] == 0],
                     'women' : training_data.loc[training_data['Gender'] == 1]}

    for gen, data in training_data.items():
        print('Now Building Model For', gen.title())

        # Find training columns
        training_columns = training_columns_men if gen == 'men' else training_columns_women

        # Find best hyperparameters by tuning
        if tune:
            print()
            params = tune_model_bayesian_optimisation(data,
                                                      training_columns=training_columns,
                                                      iterations=optimisation_iterations,
                                                      initial_points=optimisation_initial_points,
                                                      random_state=random_state,
                                                      calibrate=calibrate,
                                                      calibrator_size=calibrator_size)
            print()
            print('Best Hyperparameters')
            print(params)
            print()
        else:
            params = DEFAULT_PARAMS

        # CV and output metrics
        _, brier, _, _, calib = cross_validate_model(data,
                                                     training_columns=training_columns,
                                                     params=params,
                                                     calibrate=calibrate,
                                                     random_state=random_state,
                                                     calibrator_size=calibrator_size)

        # Build final model and save
        model = build_model(data, training_columns=training_columns, params=params,
                            calibrate=calibrate, calibrator_size=calibrator_size)
        dump(model, f'./data/models/{model_name}_{gen}.mdl')

    # Return brier validation metric
    return calib if calibrate else brier

if __name__ == "__main__":
    validate_and_build_model()


from rouge_metric import PyRouge
from torch.nn import Module
from typing import List

class RougeL(Module):
	def __init__(self):
		"""
			Recall Oriented Understudy of Gisting Evaluation.
			Use 'rouge-metric' package as backend.
		"""
		super().__init__()
		self.rouge = PyRouge(rouge_l=True)

	def forward(self, hypothesis: List[List[str]], references: List[List[List[str]]]) -> float:
		if len(hypothesis) != len(references):
			raise ValueError(f'Batch size of hypothesis and references are different ({len(hypothesis)} != {len(references)}).')

		hypothesis = [' '.join(hyp) for hyp in hypothesis]
		references = [[' '.join(ref) for ref in refs] for refs in references]

		scores = self.rouge.evaluate(hypotheses=hypothesis, multi_references=references)
		rouge_l_scores = scores['rouge-l']
		# 3 scores = Recall r, Precision p, FScore f
		# {'r': ..., 'p': ..., 'f': ...}
		f_score = rouge_l_scores['f']

		return f_score