"""
Ported to Python 3.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from future.utils import PY2
if PY2:
    from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min  # noqa: F401

import json
import os.path, shutil

from bs4 import BeautifulSoup

from twisted.trial import unittest
from twisted.internet import defer

from zope.interface import implementer
from twisted.web.resource import (
    Resource,
)
from twisted.web.template import (
    renderElement,
)

from allmydata import check_results, uri
from allmydata import uri as tahoe_uri
from allmydata.interfaces import (
    IServer,
    ICheckResults,
    ICheckAndRepairResults,
)
from allmydata.util import base32
from allmydata.web import check_results as web_check_results
from allmydata.storage_client import StorageFarmBroker, NativeStorageServer
from allmydata.storage.server import storage_index_to_dir
from allmydata.monitor import Monitor
from allmydata.test.no_network import GridTestMixin
from allmydata.immutable.upload import Data
from allmydata.mutable.publish import MutableData

from .common import (
    EMPTY_CLIENT_CONFIG,
)
from .common_web import (
    render,
)

from .web.common import (
    assert_soup_has_favicon,
    assert_soup_has_tag_with_content,
)

class FakeClient(object):
    def get_storage_broker(self):
        return self.storage_broker

@implementer(IServer)
class FakeServer(object):  # type: ignore # incomplete implementation

    def get_name(self):
        return "fake name"

    def get_longname(self):
        return "fake longname"

    def get_nickname(self):
        return "fake nickname"

@implementer(ICheckResults)
class FakeCheckResults(object):  # type: ignore # incomplete implementation

    def __init__(self, si=None,
                 healthy=False, recoverable=False,
                 summary="fake summary"):
        self._storage_index = si
        self._is_healthy = healthy
        self._is_recoverable = recoverable
        self._summary = summary

    def get_storage_index(self):
        return self._storage_index

    def get_storage_index_string(self):
        return base32.b2a_or_none(self._storage_index)

    def is_healthy(self):
        return self._is_healthy

    def is_recoverable(self):
        return self._is_recoverable

    def get_summary(self):
        return self._summary

    def get_corrupt_shares(self):
        # returns a list of (IServer, storage_index, sharenum)
        return [(FakeServer(), b"<fake-si>", 0)]

@implementer(ICheckAndRepairResults)
class FakeCheckAndRepairResults(object):  # type: ignore # incomplete implementation

    def __init__(self, si=None,
                 repair_attempted=False,
                 repair_success=False):
        self._storage_index = si
        self._repair_attempted = repair_attempted
        self._repair_success = repair_success

    def get_storage_index(self):
        return self._storage_index

    def get_pre_repair_results(self):
        return FakeCheckResults()

    def get_post_repair_results(self):
        return FakeCheckResults()

    def get_repair_attempted(self):
        return self._repair_attempted

    def get_repair_successful(self):
        return self._repair_success

class ElementResource(Resource, object):
    def __init__(self, element):
        Resource.__init__(self)
        self.element = element

    def render(self, request):
        return renderElement(request, self.element)

class WebResultsRendering(unittest.TestCase):

    @staticmethod
    def remove_tags(html):
        return BeautifulSoup(html, 'html5lib').get_text(separator=" ")

    def create_fake_client(self):
        sb = StorageFarmBroker(True, None, EMPTY_CLIENT_CONFIG)
        # s.get_name() (the "short description") will be "v0-00000000".
        # s.get_longname() will include the -long suffix.
        servers = [(b"v0-00000000-long", b"\x00"*20, "peer-0"),
                   (b"v0-ffffffff-long", b"\xff"*20, "peer-f"),
                   (b"v0-11111111-long", b"\x11"*20, "peer-11")]
        for (key_s, binary_tubid, nickname) in servers:
            server_id = key_s
            tubid_b32 = base32.b2a(binary_tubid)
            furl = "pb://%s@nowhere/fake" % str(tubid_b32, "utf-8")
            ann = { "version": 0,
                    "service-name": "storage",
                    "anonymous-storage-FURL": furl,
                    "permutation-seed-base32": "",
                    "nickname": str(nickname),
                    "app-versions": {}, # need #466 and v2 introducer
                    "my-version": "ver",
                    "oldest-supported": "oldest",
                    }
            s = NativeStorageServer(server_id, ann, None, None, None)
            sb.test_add_server(server_id, s)
        c = FakeClient()
        c.storage_broker = sb
        return c

    def render_json(self, resource):
        return self.successResultOf(render(resource, {b"output": [b"json"]}))

    def render_element(self, element, args=None):
        if args is None:
            args = {}
        return self.successResultOf(render(ElementResource(element), args))

    def test_literal(self):
        lcr = web_check_results.LiteralCheckResultsRendererElement()

        html = self.render_element(lcr)
        self.failUnlessIn(b"Literal files are always healthy", html)

        html = self.render_element(lcr, args={b"return_to": [b"FOOURL"]})
        self.failUnlessIn(b"Literal files are always healthy", html)
        self.failUnlessIn(b'Return to file.', html)

        c = self.create_fake_client()
        lcr = web_check_results.LiteralCheckResultsRenderer(c)

        js = self.render_json(lcr)
        j = json.loads(js)
        self.failUnlessEqual(j["storage-index"], "")
        self.failUnlessEqual(j["results"]["healthy"], True)

    def test_check(self):
        c = self.create_fake_client()
        sb = c.storage_broker
        serverid_1 = b"\x00"*20
        serverid_f = b"\xff"*20
        server_1 = sb.get_stub_server(serverid_1)
        server_f = sb.get_stub_server(serverid_f)
        u = uri.CHKFileURI(b"\x00"*16, b"\x00"*32, 3, 10, 1234)
        data = { "count_happiness": 8,
                 "count_shares_needed": 3,
                 "count_shares_expected": 9,
                 "count_shares_good": 10,
                 "count_good_share_hosts": 11,
                 "count_recoverable_versions": 1,
                 "count_unrecoverable_versions": 0,
                 "servers_responding": [],
                 "sharemap": {"shareid1": [server_1, server_f]},
                 "count_wrong_shares": 0,
                 "list_corrupt_shares": [],
                 "count_corrupt_shares": 0,
                 "list_incompatible_shares": [],
                 "count_incompatible_shares": 0,
                 "report": [], "share_problems": [], "servermap": None,
                 }
        cr = check_results.CheckResults(u, u.get_storage_index(),
                                        healthy=True, recoverable=True,
                                        summary="groovy",
                                        **data)
        w = web_check_results.CheckResultsRendererElement(c, cr)
        html = self.render_element(w)
        s = self.remove_tags(html)
        self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
        self.failUnlessIn("Healthy : groovy", s)
        self.failUnlessIn("Share Counts: need 3-of-9, have 10", s)
        self.failUnlessIn("Happiness Level: 8", s)
        self.failUnlessIn("Hosts with good shares: 11", s)
        self.failUnlessIn("Corrupt shares: none", s)
        self.failUnlessIn("Wrong Shares: 0", s)
        self.failUnlessIn("Recoverable Versions: 1", s)
        self.failUnlessIn("Unrecoverable Versions: 0", s)
        self.failUnlessIn("Good Shares (sorted in share order):  Share ID Nickname Node ID shareid1 peer-0 00000000 peer-f ffffffff", s)

        cr = check_results.CheckResults(u, u.get_storage_index(),
                                        healthy=False, recoverable=True,
                                        summary="ungroovy",
                                        **data)
        w = web_check_results.CheckResultsRendererElement(c, cr)
        html = self.render_element(w)
        s = self.remove_tags(html)
        self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
        self.failUnlessIn("Not Healthy! : ungroovy", s)

        data["count_corrupt_shares"] = 1
        data["list_corrupt_shares"] = [(server_1, u.get_storage_index(), 2)]
        cr = check_results.CheckResults(u, u.get_storage_index(),
                                        healthy=False, recoverable=False,
                                        summary="rather dead",
                                        **data)
        w = web_check_results.CheckResultsRendererElement(c, cr)
        html = self.render_element(w)
        s = self.remove_tags(html)
        self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
        self.failUnlessIn("Not Recoverable! : rather dead", s)
        self.failUnlessIn("Corrupt shares:  Share ID Nickname Node ID sh#2 peer-0 00000000", s)

        html = self.render_element(w)
        s = self.remove_tags(html)
        self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
        self.failUnlessIn("Not Recoverable! : rather dead", s)

        html = self.render_element(w, args={b"return_to": [b"FOOURL"]})
        self.failUnlessIn(b'Return to file/directory.',
                          html)

        w = web_check_results.CheckResultsRenderer(c, cr)
        d = self.render_json(w)
        def _check_json(jdata):
            j = json.loads(jdata)
            self.failUnlessEqual(j["summary"], "rather dead")
            self.failUnlessEqual(j["storage-index"],
                                 "2k6avpjga3dho3zsjo6nnkt7n4")
            expected = {'count-happiness': 8,
                        'count-shares-expected': 9,
                        'healthy': False,
                        'count-unrecoverable-versions': 0,
                        'count-shares-needed': 3,
                        'sharemap': {"shareid1":
                                     ["v0-00000000-long", "v0-ffffffff-long"]},
                        'count-recoverable-versions': 1,
                        'list-corrupt-shares':
                        [["v0-00000000-long", "2k6avpjga3dho3zsjo6nnkt7n4", 2]],
                        'count-good-share-hosts': 11,
                        'count-wrong-shares': 0,
                        'count-shares-good': 10,
                        'count-corrupt-shares': 1,
                        'servers-responding': [],
                        'recoverable': False,
                        }
            self.failUnlessEqual(j["results"], expected)
        _check_json(d)

        w = web_check_results.CheckResultsRendererElement(c, cr)
        d = self.render_element(w)
        def _check(html):
            s = self.remove_tags(html)
            self.failUnlessIn("File Check Results for SI=2k6avp", s)
            self.failUnlessIn("Not Recoverable! : rather dead", s)
        _check(html)

    def test_check_and_repair(self):
        c = self.create_fake_client()
        sb = c.storage_broker
        serverid_1 = b"\x00"*20
        serverid_f = b"\xff"*20
        u = uri.CHKFileURI(b"\x00"*16, b"\x00"*32, 3, 10, 1234)

        data = { "count_happiness": 5,
                 "count_shares_needed": 3,
                 "count_shares_expected": 10,
                 "count_shares_good": 6,
                 "count_good_share_hosts": 7,
                 "count_recoverable_versions": 1,
                 "count_unrecoverable_versions": 0,
                 "servers_responding": [],
                 "sharemap": {"shareid1": [sb.get_stub_server(serverid_1),
                                           sb.get_stub_server(serverid_f)]},
                 "count_wrong_shares": 0,
                 "list_corrupt_shares": [],
                 "count_corrupt_shares": 0,
                 "list_incompatible_shares": [],
                 "count_incompatible_shares": 0,
                 "report": [], "share_problems": [], "servermap": None,
                 }
        pre_cr = check_results.CheckResults(u, u.get_storage_index(),
                                            healthy=False, recoverable=True,
                                            summary="illing",
                                            **data)

        data = { "count_happiness": 9,
                 "count_shares_needed": 3,
                 "count_shares_expected": 10,
                 "count_shares_good": 10,
                 "count_good_share_hosts": 11,
                 "count_recoverable_versions": 1,
                 "count_unrecoverable_versions": 0,
                 "servers_responding": [],
                 "sharemap": {"shareid1": [sb.get_stub_server(serverid_1),
                                           sb.get_stub_server(serverid_f)]},
                 "count_wrong_shares": 0,
                 "count_corrupt_shares": 0,
                 "list_corrupt_shares": [],
                 "list_incompatible_shares": [],
                 "count_incompatible_shares": 0,
                 "report": [], "share_problems": [], "servermap": None,
                 }
        post_cr = check_results.CheckResults(u, u.get_storage_index(),
                                             healthy=True, recoverable=True,
                                             summary="groovy",
                                             **data)

        crr = check_results.CheckAndRepairResults(u.get_storage_index())
        crr.pre_repair_results = pre_cr
        crr.post_repair_results = post_cr
        crr.repair_attempted = False

        w = web_check_results.CheckAndRepairResultsRendererElement(c, crr)
        html = self.render_element(w)
        s = self.remove_tags(html)

        self.failUnlessIn("File Check-And-Repair Results for SI=2k6avp", s)
        self.failUnlessIn("Healthy : groovy", s)
        self.failUnlessIn("No repair necessary", s)
        self.failUnlessIn("Post-Repair Checker Results:", s)
        self.failUnlessIn("Share Counts: need 3-of-10, have 10", s)

        crr.repair_attempted = True
        crr.repair_successful = True
        html = self.render_element(w)
        s = self.remove_tags(html)

        self.failUnlessIn("File Check-And-Repair Results for SI=2k6avp", s)
        self.failUnlessIn("Healthy : groovy", s)
        self.failUnlessIn("Repair successful", s)
        self.failUnlessIn("Post-Repair Checker Results:", s)

        crr.repair_attempted = True
        crr.repair_successful = False
        post_cr = check_results.CheckResults(u, u.get_storage_index(),
                                             healthy=False, recoverable=True,
                                             summary="better",
                                             **data)
        crr.post_repair_results = post_cr
        html = self.render_element(w)
        s = self.remove_tags(html)

        self.failUnlessIn("File Check-And-Repair Results for SI=2k6avp", s)
        self.failUnlessIn("Not Healthy! : better", s)
        self.failUnlessIn("Repair unsuccessful", s)
        self.failUnlessIn("Post-Repair Checker Results:", s)

        crr.repair_attempted = True
        crr.repair_successful = False
        post_cr = check_results.CheckResults(u, u.get_storage_index(),
                                             healthy=False, recoverable=False,
                                             summary="worse",
                                             **data)
        crr.post_repair_results = post_cr
        html = self.render_element(w)
        s = self.remove_tags(html)

        self.failUnlessIn("File Check-And-Repair Results for SI=2k6avp", s)
        self.failUnlessIn("Not Recoverable! : worse", s)
        self.failUnlessIn("Repair unsuccessful", s)
        self.failUnlessIn("Post-Repair Checker Results:", s)

        w = web_check_results.CheckAndRepairResultsRenderer(c, crr)
        j = json.loads(self.render_json(w))
        self.failUnlessEqual(j["repair-attempted"], True)
        self.failUnlessEqual(j["storage-index"],
                             "2k6avpjga3dho3zsjo6nnkt7n4")
        self.failUnlessEqual(j["pre-repair-results"]["summary"], "illing")
        self.failUnlessEqual(j["post-repair-results"]["summary"], "worse")

        w = web_check_results.CheckAndRepairResultsRenderer(c, None)
        j = json.loads(self.render_json(w))
        self.failUnlessEqual(j["repair-attempted"], False)
        self.failUnlessEqual(j["storage-index"], "")

    def test_deep_check_renderer(self):
        status = check_results.DeepCheckResults(b"fake-root-si")
        status.add_check(
            FakeCheckResults(b"<unhealthy/unrecoverable>", False, False),
            (u"fake", u"unhealthy", u"unrecoverable")
        )
        status.add_check(
            FakeCheckResults(b"<healthy/recoverable>", True, True),
            (u"fake", u"healthy", u"recoverable")
        )
        status.add_check(
            FakeCheckResults(b"<healthy/unrecoverable>", True, False),
            (u"fake", u"healthy", u"unrecoverable")
        )
        status.add_check(
            FakeCheckResults(b"<unhealthy/unrecoverable>", False, True),
            (u"fake", u"unhealthy", u"recoverable")
        )

        monitor = Monitor()
        monitor.set_status(status)

        elem = web_check_results.DeepCheckResultsRendererElement(monitor)
        doc = self.render_element(elem)
        soup = BeautifulSoup(doc, 'html5lib')

        assert_soup_has_favicon(self, soup)

        assert_soup_has_tag_with_content(
            self, soup, u"title",
            u"Tahoe-LAFS - Deep Check Results"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h1",
            "Deep-Check Results for root SI="
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Checked: 4"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Healthy: 2"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Unhealthy: 2"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Unrecoverable: 2"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Corrupt Shares: 4"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h2",
            u"Files/Directories That Had Problems:"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"fake/unhealthy/recoverable: fake summary"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"fake/unhealthy/unrecoverable: fake summary"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h2",
            u"Servers on which corrupt shares were found"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h2",
            u"Corrupt Shares"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h2",
            u"All Results"
        )

    def test_deep_check_and_repair_renderer(self):
        status = check_results.DeepCheckAndRepairResults(b"")

        status.add_check_and_repair(
            FakeCheckAndRepairResults(b"attempted/success", True, True),
            (u"attempted", u"success")
        )
        status.add_check_and_repair(
            FakeCheckAndRepairResults(b"attempted/failure", True, False),
            (u"attempted", u"failure")
        )
        status.add_check_and_repair(
            FakeCheckAndRepairResults(b"unattempted/failure", False, False),
            (u"unattempted", u"failure")
        )

        monitor = Monitor()
        monitor.set_status(status)

        elem = web_check_results.DeepCheckAndRepairResultsRendererElement(monitor)
        doc = self.render_element(elem)
        soup = BeautifulSoup(doc, 'html5lib')

        assert_soup_has_favicon(self, soup)

        assert_soup_has_tag_with_content(
            self, soup, u"title",
            u"Tahoe-LAFS - Deep Check Results"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h1",
            u"Deep-Check-And-Repair Results for root SI="
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Checked: 3"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Healthy (before repair): 0"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Unhealthy (before repair): 3"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Corrupt Shares (before repair): 3"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Repairs Attempted: 2"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Repairs Successful: 1"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            "Repairs Unsuccessful: 1"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Healthy (after repair): 0"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Objects Unhealthy (after repair): 3"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"li",
            u"Corrupt Shares (after repair): 3"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h2",
            u"Files/Directories That Had Problems:"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h2",
            u"Files/Directories That Still Have Problems:"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h2",
            u"Servers on which corrupt shares were found"
        )

        assert_soup_has_tag_with_content(
            self, soup, u"h2",
            u"Remaining Corrupt Shares"
        )

class BalancingAct(GridTestMixin, unittest.TestCase):
    # test for #1115 regarding the 'count-good-share-hosts' metric

    def add_server(self, server_number, readonly=False):
        assert self.g, "I tried to find a grid at self.g, but failed"
        ss = self.g.make_server(server_number, readonly)
        #log.msg("just created a server, number: %s => %s" % (server_number, ss,))
        self.g.add_server(server_number, ss)

    def add_server_with_share(self, server_number, uri, share_number=None,
                              readonly=False):
        self.add_server(server_number, readonly)
        if share_number is not None:
            self.copy_share_to_server(uri, share_number, server_number)

    def copy_share_to_server(self, uri, share_number, server_number):
        ss = self.g.servers_by_number[server_number]
        # Copy share i from the directory associated with the first
        # storage server to the directory associated with this one.
        assert self.g, "I tried to find a grid at self.g, but failed"
        assert self.shares, "I tried to find shares at self.shares, but failed"
        old_share_location = self.shares[share_number][2]
        new_share_location = os.path.join(ss.storedir, "shares")
        si = tahoe_uri.from_string(self.uri).get_storage_index()
        new_share_location = os.path.join(new_share_location,
                                          storage_index_to_dir(si))
        if not os.path.exists(new_share_location):
            os.makedirs(new_share_location)
        new_share_location = os.path.join(new_share_location,
                                          str(share_number))
        if old_share_location != new_share_location:
            shutil.copy(old_share_location, new_share_location)
        shares = self.find_uri_shares(uri)
        # Make sure that the storage server has the share.
        self.failUnless((share_number, ss.my_nodeid, new_share_location)
                        in shares)

    def _pretty_shares_chart(self, uri):
        # Servers are labeled A-Z, shares are labeled 0-9
        letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        assert len(self.g.servers_by_number) < len(letters), \
            "This little printing function is only meant for < 26 servers"
        shares_chart = {}
        names = dict(zip([ss.my_nodeid
                          for _,ss in self.g.servers_by_number.items()],
                         letters))
        for shnum, serverid, _ in self.find_uri_shares(uri):
            shares_chart.setdefault(shnum, []).append(names[serverid])
        return shares_chart

    def test_good_share_hosts(self):
        self.basedir = "checker/BalancingAct/1115"
        self.set_up_grid(num_servers=1)
        c0 = self.g.clients[0]
        c0.encoding_params['happy'] = 1
        c0.encoding_params['n'] = 4
        c0.encoding_params['k'] = 3

        DATA = b"data" * 100
        d = c0.upload(Data(DATA, convergence=b""))
        def _stash_immutable(ur):
            self.imm = c0.create_node_from_uri(ur.get_uri())
            self.uri = self.imm.get_uri()
        d.addCallback(_stash_immutable)
        d.addCallback(lambda ign:
            self.find_uri_shares(self.uri))
        def _store_shares(shares):
            self.shares = shares
        d.addCallback(_store_shares)

        def add_three(_, i):
            # Add a new server with just share 3
            self.add_server_with_share(i, self.uri, 3)
            #print(self._pretty_shares_chart(self.uri))
        for i in range(1,5):
            d.addCallback(add_three, i)

        def _check_and_repair(_):
            return self.imm.check_and_repair(Monitor())
        def _check_counts(crr, shares_good, good_share_hosts):
            prr = crr.get_post_repair_results()
            self.failUnlessEqual(prr.get_share_counter_good(), shares_good)
            self.failUnlessEqual(prr.get_host_counter_good_shares(),
                                 good_share_hosts)

        """
        Initial sharemap:
            0:[A] 1:[A] 2:[A] 3:[A,B,C,D,E]
          4 good shares, but 5 good hosts
        After deleting all instances of share #3 and repairing:
            0:[A], 1:[A,B], 2:[C,A], 3:[E]
# actually: {0: ['E', 'A'], 1: ['C', 'A'], 2: ['A', 'B'], 3: ['D']}
          Still 4 good shares but now 4 good hosts
            """
        d.addCallback(_check_and_repair)
        d.addCallback(_check_counts, 4, 5)
        d.addCallback(lambda _: self.delete_shares_numbered(self.uri, [3]))
        d.addCallback(_check_and_repair)

        # it can happen that our uploader will choose, e.g., to upload
        # to servers B, C, D, E .. which will mean that all 5 serves
        # now contain our shares (and thus "respond").

        def _check_happy(crr):
            prr = crr.get_post_repair_results()
            self.assertTrue(prr.get_host_counter_good_shares() >= 4)
            return crr
        d.addCallback(_check_happy)
        d.addCallback(lambda _: all([self.g.break_server(sid)
                                     for sid in self.g.get_all_serverids()]))
        d.addCallback(_check_and_repair)
        d.addCallback(_check_counts, 0, 0)
        return d

class AddLease(GridTestMixin, unittest.TestCase):
    # test for #875, in which failures in the add-lease call cause
    # false-negatives in the checker

    def test_875(self):
        self.basedir = "checker/AddLease/875"
        self.set_up_grid(num_servers=1)
        c0 = self.g.clients[0]
        c0.encoding_params['happy'] = 1
        self.uris = {}
        DATA = b"data" * 100
        d = c0.upload(Data(DATA, convergence=b""))
        def _stash_immutable(ur):
            self.imm = c0.create_node_from_uri(ur.get_uri())
        d.addCallback(_stash_immutable)
        d.addCallback(lambda ign:
            c0.create_mutable_file(MutableData(b"contents")))
        def _stash_mutable(node):
            self.mut = node
        d.addCallback(_stash_mutable)

        def _check_cr(cr, which):
            self.failUnless(cr.is_healthy(), which)

        # these two should work normally
        d.addCallback(lambda ign: self.imm.check(Monitor(), add_lease=True))
        d.addCallback(_check_cr, "immutable-normal")
        d.addCallback(lambda ign: self.mut.check(Monitor(), add_lease=True))
        d.addCallback(_check_cr, "mutable-normal")

        really_did_break = []
        # now break the server's add_lease call
        def _break_add_lease(ign):
            def broken_add_lease(*args, **kwargs):
                really_did_break.append(1)
                raise KeyError("intentional failure, should be ignored")
            assert self.g.servers_by_number[0].add_lease
            self.g.servers_by_number[0].add_lease = broken_add_lease
        d.addCallback(_break_add_lease)

        # and confirm that the files still look healthy
        d.addCallback(lambda ign: self.mut.check(Monitor(), add_lease=True))
        d.addCallback(_check_cr, "mutable-broken")
        d.addCallback(lambda ign: self.imm.check(Monitor(), add_lease=True))
        d.addCallback(_check_cr, "immutable-broken")

        d.addCallback(lambda ign: self.failUnless(really_did_break))
        return d

class CounterHolder(object):
    def __init__(self):
        self._num_active_block_fetches = 0
        self._max_active_block_fetches = 0

from allmydata.immutable.checker import ValidatedReadBucketProxy
class MockVRBP(ValidatedReadBucketProxy):
    def __init__(self, sharenum, bucket, share_hash_tree, num_blocks, block_size, share_size, counterholder):
        ValidatedReadBucketProxy.__init__(self, sharenum, bucket,
                                          share_hash_tree, num_blocks,
                                          block_size, share_size)
        self.counterholder = counterholder

    def get_block(self, blocknum):
        self.counterholder._num_active_block_fetches += 1
        if self.counterholder._num_active_block_fetches > self.counterholder._max_active_block_fetches:
            self.counterholder._max_active_block_fetches = self.counterholder._num_active_block_fetches
        d = ValidatedReadBucketProxy.get_block(self, blocknum)
        def _mark_no_longer_active(res):
            self.counterholder._num_active_block_fetches -= 1
            return res
        d.addBoth(_mark_no_longer_active)
        return d

class TooParallel(GridTestMixin, unittest.TestCase):
    # bug #1395: immutable verifier was aggressively parallized, checking all
    # blocks of all shares at the same time, blowing our memory budget and
    # crashing with MemoryErrors on >1GB files.

    def test_immutable(self):
        import allmydata.immutable.checker
        origVRBP = allmydata.immutable.checker.ValidatedReadBucketProxy

        self.basedir = "checker/TooParallel/immutable"

        # If any code asks to instantiate a ValidatedReadBucketProxy,
        # we give them a MockVRBP which is configured to use our
        # CounterHolder.
        counterholder = CounterHolder()
        def make_mock_VRBP(*args, **kwargs):
            return MockVRBP(counterholder=counterholder, *args, **kwargs)
        allmydata.immutable.checker.ValidatedReadBucketProxy = make_mock_VRBP

        d = defer.succeed(None)
        def _start(ign):
            self.set_up_grid(num_servers=4)
            self.c0 = self.g.clients[0]
            self.c0.encoding_params = { "k": 1,
                                        "happy": 4,
                                        "n": 4,
                                        "max_segment_size": 5,
                                      }
            self.uris = {}
            DATA = b"data" * 100 # 400/5 = 80 blocks
            return self.c0.upload(Data(DATA, convergence=b""))
        d.addCallback(_start)
        def _do_check(ur):
            n = self.c0.create_node_from_uri(ur.get_uri())
            return n.check(Monitor(), verify=True)
        d.addCallback(_do_check)
        def _check(cr):
            # the verifier works on all 4 shares in parallel, but only
            # fetches one block from each share at a time, so we expect to
            # see 4 parallel fetches
            self.failUnlessEqual(counterholder._max_active_block_fetches, 4)
        d.addCallback(_check)
        def _clean_up(res):
            allmydata.immutable.checker.ValidatedReadBucketProxy = origVRBP
            return res
        d.addBoth(_clean_up)
        return d


import streamlit as st

import fpl_opt.selection
import utils

def pick_team(points_data_path, playing_data_path, bucket_name, budget):
    player_df = utils.get_forecast_data(
        points_data_path, playing_data_path, bucket_name
    ).reset_index()
    player_df = player_df.groupby("element").agg(
        {
            "score_pred": "mean",
            "playing_chance": "mean",
            "value": "first",
            "position": "first",
            "team": "first",
            "name": "first",
        }
    )
    decisions, captain_decisions, sub_decisions = fpl_opt.selection.select_team(
        player_df["score_pred"].values,
        player_df["value"].values,
        player_df["position"]
        .replace({"GKP": "GK"})
        .map({"GK": 1, "DEF": 2, "MID": 3, "FWD": 4})
        .values,
        player_df["team"].values,
        playing_chance=player_df["playing_chance"].values,
        sub_factors=[0.15, 0.15, 0.15, 0.05],
        total_budget=budget,
    )
    selection_df = fpl_opt.selection.get_selection_df(
        decisions, captain_decisions, sub_decisions, player_df
    )

    return selection_df

settings = utils.get_settings()

def main():
    budget = float(st.text_input("Budget", "100"))
    selection_df = utils.pick_team(
        settings.points_data_path,
        settings.playing_data_path,
        settings.bucket_name,
        budget,
    )
    st.dataframe(
        selection_df,
        use_container_width=True,
    )

if __name__ == "__main__":
    main()


# Andromeda - an Asteroids remake!
# Sources for sounds, images, and font:
# http://wallarthd.com/imagepics/dark-galaxy-wallpaper-17017.html
# https://www.dafont.com/screenmatrix.font
# https://freesound.org/people/luckylittleraven/sounds/218346/
# https://freesound.org/people/Robinhood76/sounds/337788/
# https://freesound.org/people/ani_music/sounds/219622/
# https://freesound.org/people/benjaminharveydesign/sounds/315826/
# https://freesound.org/people/unadamlar/sounds/341984/
# https://freesound.org/people/josepharaoh99/sounds/364929/
# https://freesound.org/people/ProjectsU012/sounds/341626/

import pygame, random
pygame.init()

from pygame.locals import QUIT, KEYDOWN, KEYUP, K_DOWN, K_LEFT, K_UP, K_RIGHT, K_SPACE, K_RETURN

X_MAX, Y_MAX = 800, 600

speed1 = [3,3]
speed2 = [5,5]
speed3 = [7,7]
speed4 = [5,5]
speed5 = [4,4]
speed6 = [6,6]
speed7 = [10,10]
speed8 = [11,11]
speed9 = [9,9]

LEFT, RIGHT, UP, DOWN = 0, 1, 3, 4
START, STOP = 0, 1

class Start(pygame.sprite.Sprite):
    def __init__(self):
        super(Start, self).__init__()
        self.image = pygame.Surface((600, 200))
        self.rect = self.image.get_rect()
        self.rect.center = (X_MAX/2, Y_MAX/2)
        self.font = pygame.font.Font("fonts/ScreenMatrix.ttf", 75)
        self.font2 = pygame.font.Font("fonts/ScreenMatrix.ttf", 25)

    def update(self):
        self.logo = pygame.image.load("images/andromeda.png")
        self.logo = pygame.transform.smoothscale(self.logo, (100, 200))
        title = self.font.render("ANDROMEDA", True, (3, 168, 158))
        begin = self.font2.render("Press Enter To Begin", True, (255, 255, 255))
        self.image.fill((0, 0, 0))
        self.image.set_colorkey((0, 0, 0))
        self.image.blit(self.logo, (35, 0))
        self.image.blit(title, (160, 60))
        self.image.blit(begin, (210, 145))

class Asteroid(pygame.sprite.Sprite):
    def __init__(self):
        super(Asteroid, self).__init__()
        self.image = pygame.image.load("images/asteroid_lg_1.png")
        self.image = pygame.transform.smoothscale(self.image, (60, 60))
        self.rect = self.image.get_rect()
        self.x = random.randint(0+60, 800-60)
        self.y = random.randint(0+60, 600-60)
        self.rect.center = (self.x, self.y)

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed1[0] = -speed1[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed1[1] = -speed1[1]

        self.rect = self.rect.move(speed1)

class Asteroid2(Asteroid):
    def __init__(self):
        super(Asteroid2, self).__init__()
        self.image = pygame.image.load("images/asteroid_lg_2.png")
        self.image = pygame.transform.smoothscale(self.image, (60, 60))

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed2[0] = -speed2[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed2[1] = -speed2[1]

        self.rect = self.rect.move(speed2)

class Asteroid3(Asteroid):
    def __init__(self):
        super(Asteroid3, self).__init__()
        self.image = pygame.image.load("images/asteroid_lg_3.png")
        self.image = pygame.transform.smoothscale(self.image, (60, 60))

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed3[0] = -speed3[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed3[1] = -speed3[1]

        self.rect = self.rect.move(speed3)

class Asteroid4(pygame.sprite.Sprite):
    def __init__(self):
        super(Asteroid4, self).__init__()
        self.image = pygame.image.load("images/asteroid_md_1.png")
        self.image = pygame.transform.smoothscale(self.image, (45, 45))
        self.rect = self.image.get_rect()
        self.x = random.randint(0+45, 800-45)
        self.y = random.randint(0+45,600-45)
        self.rect.center = (self.x, self.y)

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed4[0] = -speed4[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed4[1] = -speed4[1]

        self.rect = self.rect.move(speed4)

class Asteroid5(Asteroid4):
    def __init__(self):
        super(Asteroid5, self).__init__()
        self.image = pygame.image.load("images/asteroid_md_2.png")
        self.image = pygame.transform.smoothscale(self.image, (45, 45))

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed5[0] = -speed5[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed5[1] = -speed5[1]

        self.rect = self.rect.move(speed5)

class Asteroid6(Asteroid4):
    def __init__(self):
        super(Asteroid6, self).__init__()
        self.image = pygame.image.load("images/asteroid_md_3.png")
        self.image = pygame.transform.smoothscale(self.image, (45, 45))

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed6[0] = -speed6[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed6[1] = -speed6[1]

        self.rect = self.rect.move(speed6)

class Spaceship(pygame.sprite.Sprite):
    def __init__(self, all_group, weapon_group):
        super(Spaceship, self).__init__()
        self.image = pygame.image.load("images/spaceship.png")
        self.image = pygame.transform.smoothscale(self.image, (55, 75))
        self.rect = self.image.get_rect()
        self.rect.center = (X_MAX/2, Y_MAX/2)
        self.dx = self.dy = 0
        self.groups = [all_group, weapon_group]
        self.in_position = False
        self.firing = self.shot = False
        self.health = 100
        self.score = 0
        self.shotsfired = pygame.mixer.Sound("sounds/shotsfired.wav")
        self.shotsfired.set_volume(0.3)

    def update(self):
        x, y = self.rect.center

        if not self.in_position:
            self.rect.center = x + self.dx, y + self.dy

        if self.firing:
            self.shot = Particle(x,y)
            self.shotsfired.play(maxtime= 1000)
            self.shot.add(self.groups)

    def steer(self, direction, operation):
        v = 7
        if operation == START:
            if direction in (UP, DOWN):
                self.dy = {UP: -v, DOWN: v}[direction]
            if direction in (LEFT, RIGHT):
                self.dx = {LEFT: -v, RIGHT: v}[direction]

        if operation == STOP:
            if direction in (UP, DOWN):
                self.dy = 0
            if direction in (LEFT, RIGHT):
                self.dx = 0

    def shoot(self, operation):
        if operation == START:
            self.firing = True
        if operation == STOP:
            self.firing = False

class Particle(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super(Particle, self).__init__()
        self.image = pygame.Surface((2, 5))
        self.image.fill((255, 255, 255))
        self.rect = self.image.get_rect()
        self.rect.center = (x, y-40)

    def update(self):
        self.rect.y -=10
        if self.rect.y < -10:
            self.kill()

class Bits(pygame.sprite.Sprite):
    def __init__(self):
        super(Bits, self).__init__()
        self.image = pygame.image.load("images/asteroid_sm_2.png").convert_alpha()
        self.image = pygame.transform.smoothscale(self.image, (30, 30))
        self.rect = self.image.get_rect()
        self.x = random.randint(0+30, 800-30)
        self.y = random.randint(0+30,600-30)
        self.rect.center = (self.x, self.y)

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed7[0] = -speed7[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed7[1] = -speed7[1]

        self.rect = self.rect.move(speed7)

class Bits2(Bits):
    def __init__(self):
        super(Bits2, self).__init__()
        self.image = pygame.image.load("images/asteroid_sm_3.png")
        self.image = pygame.transform.smoothscale(self.image, (30, 30))

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed8[0] = -speed8[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed8[1] = -speed8[1]

        self.rect = self.rect.move(speed8)

class Bits3(Bits):
    def __init__(self):
        super(Bits3, self).__init__()
        self.image = pygame.image.load("images/asteroid_sm_1.png")
        self.image = pygame.transform.smoothscale(self.image, (30, 30))

    def update(self):
        if self.rect.left < 0 or self.rect.right > X_MAX:
            speed9[0] = -speed9[0]
        if self.rect.top < 0 or self.rect.bottom > Y_MAX:
            speed9[1] = -speed9[1]

        self.rect = self.rect.move(speed9)

class Status(pygame.sprite.Sprite):
    def __init__(self, ship):
        super(Status, self).__init__()
        self.image = pygame.Surface((X_MAX, 30))
        self.rect = self.image.get_rect()
        self.rect.bottomleft = 25, Y_MAX
        self.font = pygame.font.Font("fonts/ScreenMatrix.ttf", 24)
        self.ship = ship

    def update(self):
        score = self.font.render("Health : {} Score : {}".format(
            self.ship.health, self.ship.score), True, (255, 255, 255))
        self.image.fill((0, 0, 0))
        self.image.blit(score, (0, 0))
        self.image.set_colorkey((0, 0, 0))

class Lose(pygame.sprite.Sprite):
    def __init__(self, ship, asteroids, asteroid_spawns, status):
        super(Lose, self).__init__()
        self.image = pygame.Surface((500, 200))
        self.rect = self.image.get_rect()
        self.rect.center = (X_MAX/2, Y_MAX/2)
        self.font = pygame.font.Font("fonts/ScreenMatrix.ttf", 75)
        self.font2 = pygame.font.Font("fonts/ScreenMatrix.ttf", 45)
        self.font3 = pygame.font.Font("fonts/ScreenMatrix.ttf", 25)
        self.ship = ship
        self.asteroids = asteroids
        self.asteroid_spawns = asteroid_spawns
        self.status = status
        self.check = 0

    def update(self):
        sorry = self.font.render("GAME OVER", True, (3, 168, 158))
        score = self.font2.render("High Score: {}".format(
            self.ship.score), True, (255, 255, 255))
        #play_again = self.font3.render("Press Enter to Play Again", True, (255, 255, 255))
        self.image.fill((0, 0, 0))
        self.image.set_colorkey((0, 0, 0))

        if self.ship.health <= 0:
            if self.check == 0:
                pygame.mixer.Sound("sounds/defeat.wav").play()
                self.check = 1

        if self.check == 1:
            self.ship.kill()
            for asteroid in self.asteroids:
                asteroid.kill()
            for asteroid in self.asteroid_spawns:
                asteroid.kill()
            self.status.kill()
            self.image.blit(sorry, (50, 40))
            self.image.blit(score, (70, 120))
            #self.image.blit(play_again, (70, 180))

        if self.ship.health > 0:
            if self.ship.score >= 0 and self.ship.score < 60:
                if len(self.asteroids) == 0:
                    for asteroid in self.asteroid_spawns:
                        asteroid.kill()
                        if self.check == 0:
                            pygame.mixer.Sound("sounds/defeat.wav").play()
                            self.check = 1

        if self.ship.health > 0:
            if self.ship.score >= 0 and self.ship.score < 60:
                if len(self.asteroids) == 0:
                    if len(self.asteroid_spawns) == 0:
                        if self.check == 0:
                            pygame.mixer.Sound("sounds/defeat.wav").play()
                            self.check = 1

        if self.check == 1:
            self.ship.kill()
            self.status.kill()
            self.ship.kill()
            self.image.blit(sorry, (50, 40))
            self.image.blit(score, (70, 120))
            #self.image.blit(play_again, (70, 180))

class Win(pygame.sprite.Sprite):
    def __init__(self, ship, asteroids, asteroid_spawns, status):
        super(Win, self).__init__()
        self.image = pygame.Surface((500, 200))
        self.rect = self.image.get_rect()
        self.rect.center = (X_MAX/2, Y_MAX/2)
        self.font = pygame.font.Font("fonts/ScreenMatrix.ttf", 75)
        self.font2 = pygame.font.Font("fonts/ScreenMatrix.ttf", 45)
        self.font3 = pygame.font.Font("fonts/ScreenMatrix.ttf", 25)
        self.ship = ship
        self.asteroids = asteroids
        self.asteroid_spawns = asteroid_spawns
        self.status = status
        self.check = 0

    def update(self):
        hurray = self.font.render("YOU WIN!", True, (3, 168, 158))
        score = self.font2.render("High Score: {}".format(
            self.ship.score), True, (255, 255, 255))
        #play_again = self.font3.render("Press Enter to Play Again", True, (255, 255, 255))
        self.image.fill((0, 0, 0))
        self.image.set_colorkey((0, 0, 0))

        if self.ship.score >= 60:
            if self.check == 0:
                pygame.mixer.Sound("sounds/victory.wav").play()
                self.check = 1

        if self.check == 1:
            self.ship.kill()
            for asteroid in self.asteroids:
                asteroid.kill()
            for asteroid in self.asteroid_spawns:
                asteroid.kill()
            self.status.kill()
            self.image.blit(hurray, (80, 40))
            self.image.blit(score, (65, 120))
            #self.image.blit(play_again, (70, 180))

def intro():

    screen = pygame.display.set_mode((X_MAX, Y_MAX))
    pygame.display.set_caption("Andromeda")

    background = pygame.image.load('images/background1.jpg').convert()
    background = pygame.transform.scale(background, (X_MAX, Y_MAX))
    theme = pygame.mixer.Sound("sounds/andromeda.wav").play(loops= 10)
    theme.set_volume(0.2)

    start = Start()
    letsplay = pygame.sprite.Group()
    letsplay.add(start)

    game_over = False

    while not game_over:
        for event in pygame.event.get():
            if event.type == QUIT:
                game_over = True
            if not game_over:
                if event.type == KEYDOWN and event.key == K_RETURN:
                    theme.pause()
                    letsgo = pygame.mixer.Sound("sounds/enteringgalaxy.wav").play()
                    letsgo.set_volume(0.7)
                    return

        screen.blit(background, (0, 0))
        letsplay.update()
        letsplay.draw(screen)
        pygame.display.flip()

    pygame.quit()

intro()

def main():

    screen = pygame.display.set_mode((X_MAX, Y_MAX))
    pygame.display.set_caption("Andromeda")

    background = pygame.image.load('images/background1.jpg').convert()
    background = pygame.transform.scale(background, (X_MAX, Y_MAX))
    theme = pygame.mixer.Sound("sounds/andromeda.wav").play(loops= 100)
    theme.set_volume(0.2)

    all_sprites = pygame.sprite.Group()
    asteroids = pygame.sprite.Group()
    asteroid_spawns = pygame.sprite.Group()
    weaponfire = pygame.sprite.Group()

    ship = Spaceship(all_sprites, weaponfire)
    status = Status(ship)
    lose = Lose(ship, asteroids, asteroid_spawns, status)
    win = Win(ship, asteroids, asteroid_spawns, status)

    asteroids.add(Asteroid(), Asteroid2(), Asteroid3(), Asteroid4(), Asteroid5(), Asteroid6())
    asteroid_spawns.add(Bits(), Bits2(), Bits3())

    all_sprites.add(ship, status, asteroids, lose, win)

    game_over = False

    while not game_over:
        for event in pygame.event.get():
            if event.type == QUIT:
                game_over = True
            if not game_over:
                if event.type == KEYDOWN:
                    if event.key == K_DOWN:
                        ship.steer(DOWN, START)
                    if event.key == K_LEFT:
                        ship.steer(LEFT, START)
                    if event.key == K_RIGHT:
                        ship.steer(RIGHT, START)
                    if event.key == K_UP:
                        ship.steer(UP, START)
                    if event.key == K_SPACE:
                        ship.shoot(START)
                    # if event.key == K_RETURN and not ship.alive():
                    #     theme.pause()
                    #     letsgo = pygame.mixer.Sound("sounds/enteringgalaxy.wav").play()
                    #     letsgo.set_volume(0.7)
                    #     main()

                if event.type == KEYUP:
                    if event.key == K_DOWN:
                        ship.steer(DOWN, STOP)
                    if event.key == K_LEFT:
                        ship.steer(LEFT, STOP)
                    if event.key == K_RIGHT:
                        ship.steer(RIGHT, STOP)
                    if event.key == K_UP:
                        ship.steer(UP, STOP)
                    if event.key == K_SPACE:
                        ship.shoot(STOP)

        hits = pygame.sprite.spritecollide(ship, asteroids, True)
        for hit in hits:
            pygame.mixer.Sound("sounds/houstonwearedown.wav").play()
            ship.health -= 10

        hits = pygame.sprite.spritecollide(ship, asteroid_spawns, True)
        for hit in hits:
            pygame.mixer.Sound("sounds/houstonwearedown.wav").play()
            ship.health -= 20

        for particle in weaponfire:
            hits = pygame.sprite.spritecollide(particle, asteroids, True)
            for hit in hits:
                pygame.mixer.Sound("sounds/greatshot.wav").play()
                weaponfire.remove(particle)
                all_sprites.remove(particle)
                ship.score += 10
                all_sprites.add(asteroid_spawns)

        for particle in weaponfire:
            hits = pygame.sprite.spritecollide(particle, asteroid_spawns, True)
            for hit in hits:
                pygame.mixer.Sound("sounds/greatshot.wav").play()
                weaponfire.remove(particle)
                all_sprites.remove(particle)
                ship.score += 15

        screen.blit(background, (0,0))
        all_sprites.update()
        all_sprites.draw(screen)
        pygame.display.flip()

    pygame.quit()

main()


import os

import numpy as np
import pandas as pd
import glob
import matplotlib.pyplot as plt

def read_file(fn):
    df = pd.read_csv(fn)
    tmp_dict = df.to_dict(orient='list')
    for key in tmp_dict.keys():
        tmp_dict[key] = tmp_dict[key]
    return tmp_dict
def make_mea_dicts(fn, wt_chips):
    raw_dict = read_file(fn)
    #print(raw_dict)
    wt_inds = []
    cond_inds = []
    for i,chip_id in enumerate(raw_dict['Wellplate ID']):
        #print(chip_id)
        if chip_id in wt_chips:
            wt_inds.append(i)
        else:
            cond_inds.append(i)
    print(f'wt_inds is:{wt_inds} and cond_inds is:{cond_inds}')
    processed_dict = {}
    for curr_key,curr_val in raw_dict.items():
        wt_vals = []
        cond_vals = []
        for i,elem in enumerate(curr_val):
            if i in wt_inds:
                wt_vals.append(float(elem))
            else:
                cond_vals.append(float(elem))
        processed_dict[curr_key] = [wt_vals,cond_vals]
    return processed_dict
def plot_from_dict(curr_dict, key, axs = None, fig = None, plt_prefix = './Plots/'):
    if not axs:
        fig,axs = plt.subplots(1,1)
    [wt_vals,cond_vals] = curr_dict[key]
    wt_x = range(len(wt_vals))
    cond_x = range(len(wt_vals),len(wt_vals) + len(cond_vals))
    if 'Mean' in key:
        key_name = key[5:]
        std_key = f'{key_name} std'
        cv_key = f'{key_name} CV'
        [wt_err, cond_err] = curr_dict[std_key]
        axs.bar(wt_x, wt_vals,yerr = wt_err, color='black')
        axs.bar(cond_x, cond_vals, yerr = cond_err, color='red')
        [wt_cv, cond_cv] = curr_dict[cv_key]
        all_x = list(wt_x) + list(cond_x)
        all_y = wt_vals + cond_vals
        all_cvs = wt_cv + cond_cv
        print(f'all_x {all_x}, all_y {all_y}, all_cvs {all_cvs}')
        for i in all_x:
            axs.text(all_x[i], all_y[i]*0.2, f'CV={all_cvs[i]}', rotation=90,color = 'white')
    else:
        axs.bar(wt_x, wt_vals, color='black')
        axs.bar(cond_x, cond_vals, color='red')
    axs.set_title(key)
    fn = f'{plt_prefix}{key}.pdf'
    fig.savefig(fn)

def plot_activity_analysis(fn,wt_chip,plt_fldr = './Plots/'):
    os.makedirs(plt_fldr,exist_ok=True)
    act_dict = make_mea_dicts(fn, wt_chips)
    plot_from_dict(act_dict,'Active Area',plt_prefix=plt_fldr)
    plot_from_dict(act_dict,'Mean Firing Rate',plt_prefix=plt_fldr)
    plot_from_dict(act_dict, 'Mean Spike Amplitude',plt_prefix=plt_fldr)
    plot_from_dict(act_dict, 'Mean ISI',plt_prefix=plt_fldr)

def plot_network_analysis(fn,wt_chips,plt_fldr = './Plots/'):
    os.makedirs(plt_fldr, exist_ok=True)
    network_dict = make_mea_dicts(fn, wt_chips)
    plot_from_dict(network_dict, 'Burst Frequency',plt_prefix=plt_fldr)
    plot_from_dict(network_dict, 'Spikes within Bursts',plt_prefix=plt_fldr)
    plot_from_dict(network_dict, 'Mean Spikes per Burst',plt_prefix=plt_fldr)
    plot_from_dict(network_dict, 'Mean Spikes per Burst per Electrode',plt_prefix=plt_fldr)
    plot_from_dict(network_dict, 'Mean Burst Duration',plt_prefix=plt_fldr)
    plot_from_dict(network_dict, 'Mean Burst Peak Firing Rate',plt_prefix=plt_fldr)
    plot_from_dict(network_dict, 'Mean IBI',plt_prefix=plt_fldr)
    plot_from_dict(network_dict, 'Mean ISI within Burst',plt_prefix=plt_fldr)
    plot_from_dict(network_dict, 'Mean ISI outside Burst',plt_prefix=plt_fldr)
def find_all_files(folder,file_name):
    ans = glob.glob(f'{folder}/**/{file_name}', recursive = True)
    return ans

wt_chips = [16364,16461,16384]

fn = '/Users/rbenshalom/Library/CloudStorage/Box-Box/MEA_Data/2022_06_10_AS_Rat/20220613_171016/csv/activity_summary_metrics.csv'
fn = '/Users/rbenshalom/Library/CloudStorage/Box-Box/MEA_Data/2022_06_10_AS_Rat/20220614_131237/csv/network_summary_metrics.csv'
#plot_activity_analysis(fn,wt_chips,plt_fldr='./Plots/Div19_activity/')
plot_network_analysis(fn,wt_chips,plt_fldr='./Plots/Div19_network/')


# plt
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy.spatial.distance import squareform, pdist

from src.utilities import config

LABEL_SIZE = 16
TITLE_SIZE = 20

class Doddigton:

    def __init__(self, features, y_labels):

        self.features = features
        self.y_label = y_labels

        # treshold lists
        self.distance_matrix = self.compute_distance_matrix().to_numpy()
        # threshold chosen after evaluation
        self.selected_threshold = 1.6

    def compute_distance_matrix(self, metric='euclidean'):
        return pd.DataFrame(squareform(pdist(np.array(self.features), metric=metric)))

    def verification(self, distance_matrix=None):

        if distance_matrix is None:
            distance_matrix = self.distance_matrix

        # dictionary that will contain all the results
        results = {user: dict({'fa': 0, 'fr': 0, 'ga': 0, 'gr': 0, 'ctr': 0, 'ctrImpostor': 0}) for user in
                   set(self.y_label)}
        rows, cols = distance_matrix.shape

        for y in range(rows):
            row_label = self.y_label[y]

            # results are grouped by label (here we're doing verification with multiple templates)
            grouped_results = dict()

            for x in range(cols):
                # same probe => skip
                if x == y:
                    continue
                # column label
                col_label = self.y_label[x]
                # storing results
                grouped_results.setdefault(col_label, []).append(distance_matrix[y, x])

            for label in grouped_results:
                # take minimum distance between templates
                d = min(grouped_results[label])
                if d <= self.selected_threshold:
                    # we have an acceptance, genuine or false?
                    if row_label == label:
                        results[row_label]['ga'] += 1
                    else:
                        results[row_label]['fa'] += 1
                else:
                    # we have a rejection, genuine or false?
                    if row_label == label:
                        results[row_label]['fr'] += 1
                    else:
                        results[row_label]['gr'] += 1

            results[row_label]['ctrImpostor'] = len(grouped_results) - 1
            results[row_label]['ctr'] += 1

        return results

    @staticmethod
    def plot_verification_results(results):

        fig, (axDDGT) = plt.subplots(ncols=1)
        fig.set_size_inches(15, 5)
        grr = []
        frr = []
        gar = []
        far = []

        for t in results:
            grr += [results[t]['gr'] / (results[t]['ctr'] * results[t]['ctrImpostor'])]
            frr += [results[t]['fr'] / results[t]['ctr']]
            gar += [results[t]['ga'] / results[t]['ctr']]
            far += [results[t]['fa'] / (results[t]['ctr'] * results[t]['ctrImpostor'])]

        axDDGT.scatter(far, frr, c=np.random.rand(len(frr), 3))

        for i, txt in enumerate(results):
            axDDGT.annotate(txt.split('_')[0], (far[i] + 0.003, frr[i] + 0.003))

        axDDGT.set_xlabel('FAR', fontsize=LABEL_SIZE)
        axDDGT.set_ylabel('FRR', fontsize=LABEL_SIZE)
        axDDGT.set_title('Doddington Zoo', size=TITLE_SIZE)

        plt.savefig(config.BASE_PLOT_PATH + "doddington_zoo_zoom.png", dpi=400)
        plt.clf()

        fig, (axDDGT) = plt.subplots(ncols=1)
        fig.set_size_inches(15, 5)

        axDDGT.scatter(far, frr, c=np.random.rand(len(frr), 3))

        for i, txt in enumerate(results):
            axDDGT.annotate(txt.split('_')[0], (far[i] + 0.003, frr[i] + 0.003))

        axDDGT.set_xlabel('FAR', fontsize=LABEL_SIZE)
        axDDGT.set_ylabel('FRR', fontsize=LABEL_SIZE)
        axDDGT.set_title('Doddington Zoo', size=TITLE_SIZE)

        axDDGT.set_ylim(0, 1)
        axDDGT.set_xlim(0, 1)
        axDDGT.axvspan(0, 0.33, 0.66, 1, color='orange', alpha=0.1)
        axDDGT.axvspan(0.33, 0.66, 0.66, 1, color='red', alpha=0.1)
        axDDGT.axvspan(0.66, 1, 0.66, 1, color='purple', alpha=0.1)
        axDDGT.axvspan(0, 0.66, 0, 0.66, color='green', alpha=0.1)
        axDDGT.axvspan(0, 0.3, 0, 0.3, color='lime', alpha=0.1)
        axDDGT.axvspan(0.66, 1, 0.33, 0.66, color='blue', alpha=0.1)
        axDDGT.axvspan(0.66, 1, 0, 0.33, color='cyan', alpha=0.1)

        axDDGT.annotate('Phantoms', (0.01, 0.95), weight="bold")
        axDDGT.annotate('Goats', (0.34, 0.95), weight="bold")
        axDDGT.annotate('Worms', (0.67, 0.95), weight="bold")
        axDDGT.annotate('Sheeps', (0.35, 0.35), weight="bold")
        axDDGT.annotate('Lambs/Wolves', (0.67, 0.61), weight="bold")
        axDDGT.annotate('Chameleons', (0.67, 0.28), weight="bold")
        axDDGT.annotate('Doves', (0.25, 0.25), weight="bold")

        plt.savefig(config.BASE_PLOT_PATH + "doddington_zoo.png", dpi=400)
        plt.clf()

    def eval_verification(self):

        # verification
        print("Verification Doddington Zoo")
        verification_results = self.verification()
        self.plot_verification_results(verification_results)


import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

class PRED(nn.Module):
    """  pred-coder """
    def __init__(self, look_back, look_forward , data_shape, concatTime=False):
        super(PRED, self).__init__()

        self.concatTime = concatTime

        self.channel_in = look_back + 2  if concatTime else look_back
        self.look_forward = look_forward
        self.data_shape = data_shape
        self.total_node = np.prod (data_shape)

        self.conv1 = nn.Conv2d(self.channel_in , 32, 3, padding=1)
        self.conv2 = nn.Conv2d(32, 64, 2 , padding=1)
        self.conv3 = nn.Conv2d(64, 128, 2 , padding=1)
        self.conv4 = nn.Conv2d(128, 256, 2 , padding=1)
        # self.conv5 = nn.Conv2d(256, 512, 2 , padding=1)

        self.pool2d_2 = nn.MaxPool2d(2)

        self.dense = nn.Linear( 2304 , self.total_node   )
        # self.dense2 = nn.Linear( self.total_node , self.look_forward * self.total_node   )

        self.convOut = nn.Conv2d( 1, look_forward, 3 , padding=1)
        self.convOut2 = nn.Conv2d( look_forward, look_forward, 3 , padding=1)

    def forward(self, x, x1):

        if self.concatTime :
            # x1 = F.pad(x1,(0,139))
            x1 = x1.unsqueeze(-1).repeat(1,1, 1,self.total_node)
            x1 = x1.reshape( x1.size(0), -1, self.data_shape[-2], self.data_shape[-1] )

            x = torch.cat( (x,x1), dim =-3)

        x1 = x1.view(x1.size(0),-1)

        x = F.relu(self.conv1(x))
        x = self.pool2d_2(x)

        x = F.relu(self.conv2(x))
        x = self.pool2d_2(x)

        x = F.relu(self.conv3(x))
        x = self.pool2d_2(x)

        x = F.relu(self.conv4(x))
        x = self.pool2d_2(x)

        # x = F.relu(self.conv5(x))
        # x = self.pool2d_2(x)

        x = x.view(x.size(0), -1)

        out = self.dense(x)
        # print(out.size())
        # out =  self.dense2(out)
        out = out.reshape ( x.size(0), 1, self.data_shape[-2], self.data_shape[-1] )
        # out = F.relu(self.deconv4(out))
        # out = F.relu(self.deconv5(out))
        out = (self.convOut(out))
        out = F.relu(self.convOut2(out))

        return out


# coding=utf8
import json
import os
import random

import itchat
import requests

from wxRobot import globalVars
from pic_extract import picExtract

REPLY_USER_NICKNAME = '冯泽明 付晓蕾 x X 安源 赵航 就是随便改个名'
REPLY_GROUPNAME = '叫啥叫啥！ 聊天机器人测试'

def get_response(msg):
    try:
        # 返回需要解码 [讲笑话的返回值特殊]
        r = requests.post(globalVars.get_apiUrl() + msg).content
        if '笑话' in msg:  # 注意 因为匹配关键词是 '笑话' 所以要用 in 判断是否存在于字符串中
            jsonStr = r.decode('utf-8-sig')
            jsonObj = json.loads(jsonStr)
            text = '《' + jsonObj['title'] + '》\n' + jsonObj['content']
            print(text)
            return text
        else:
            return r.decode('utf-8')
    except (RuntimeError, TypeError, NameError, IOError):
        return  # 将会返回一个None

@itchat.msg_register(['Text', 'Map', 'Card', 'Note', 'Sharing'])
def bot_reply(msg):
    text = msg['Text'].strip()
    print(msg['User'].NickName + ' 说:' + text)
    defaultReply = ' “' + text + '”,朕已阅! '  # 容错，服务器无响应
    reply = get_response(text)
    # 小胖子
    if msg['User'].NickName in REPLY_USER_NICKNAME:
        if '斗图' in msg['Text'].strip():
            doutu(msg)
        else:
            print('----给[' + msg['User'].NickName + ']的回复:' + (reply or defaultReply))
            return reply or defaultReply

@itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
def bot_media_replay(msg):
    # 小胖子
    if msg['User'].NickName in REPLY_USER_NICKNAME:
        try:
            # 这里需要进行一场处理，否则可能因发送的图片为动态图造成程序异常中断
            pic_text = picExtract.get_file_text(msg['Text']())
            print(msg['User'].NickName + '想要斗图!' + pic_text)  # 加一条输出，后台确认发图片消息的人
            itchat.send_msg('你发的图片上写着：“' + pic_text + '”', msg['User'].UserName)
            # 识别图片上的文字，暂未指定处理方案
        except (RuntimeError, TypeError, NameError, IOError):
            print('{!@系统异常：#发送的图片未能处理,图片来自[' + msg['User'].NickName + ']}')
        doutu(msg)

@itchat.msg_register('Text', isGroupChat=True)
def group_reply(msg):
    text = msg['Text'].replace('@晓晓', '').strip()
    print(msg['ActualNickName'] + ' 说:' + text)
    defaultReply = ' “' + text + '”,朕已阅! '  # 容错，服务器无响应
    reply = get_response(text)  # @我的时候消息要特殊处理 && 指定的群进行特殊处理
    if msg['isAt'] or msg['User']['NickName'] in REPLY_GROUPNAME:
        if '斗图' in text:
            doutu(msg)
        else:
            print('----群[' + msg['User']['NickName'] + ']的回复:' + (reply or defaultReply))
            return reply or defaultReply

def maxCount():
    number = 0
    for root, dirs, files in os.walk(globalVars.get_path()):
        # print(root)  # 当前目录路径
        # print(dirs)  # 当前路径下所有子目录
        # print(files) # 当前路径下所有非目录子文件
        for file in files:
            if int(file.split('.')[0]) > number:
                globalVars.set_count(int(file.split('.')[0]))
    return globalVars.get_count()

def doutu(msg):
    fileNumber = str(random.randint(0, globalVars.get_count())).zfill(2)  # zfill是python自带的补前置零函数
    fileName = globalVars.get_path() + '\\' + fileNumber
    if os.path.exists(fileName + '.jpg'):
        fileName += '.jpg'
    else:
        fileName += '.gif'
    print('\t[使用图片路径：' + fileName + ']\n')
    itchat.send_image(fileName, msg['User'].UserName)

if __name__ == '__main__':
    globalVars.set_path(os.path.abspath('.') + '\\doutu\\pictures')
    globalVars.set_count(maxCount())
    # 我们使用热启动,不用每次的都扫码登陆
    itchat.auto_login(hotReload=True)
    itchat.run()


#!/usr/bin/python
# -*- coding: UTF-8 -*-

class ArrayCode:
    def __init__(self):
        pass

    '''Given an array and a value, remove all instances of that > value in place and return the new length.
    The order of elements can be changed. It doesn't matter what you leave beyond the new length.'''
    def removeElement(self, nums, val):
        deleteCount = 0
        for i in range(0, len(nums)) :
            obj = nums[i - deleteCount]
            if obj == val :
                del nums[i - deleteCount]
                deleteCount += 1
        return len(nums)

    '''Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length.
    Do not allocate extra space for another array, you must do this in place with constant memory.'''
    def removeDuplicates(self, nums):
        lastValue = None
        deleteCount = 0
        for i in range(0, len(nums)) :
            obj = nums[i - deleteCount]
            if obj == lastValue :
                del nums[i - deleteCount]
                deleteCount += 1
            lastValue = obj
        return len(nums)

    '''Given a non-negative integer represented as a non-empty array of digits, plus one to the integer.
    You may assume the integer do not contain any leading zero, except the number 0 itself.
    The digits are stored such that the most significant digit is at the head of the list.'''
    def plusOne(self, digits):
        carry = 1
        for idx in range(len(digits) - 1, -1, -1):
            value = digits[idx]
            newValue = value + carry
            if newValue == 10:
                digits[idx] = 0
                if idx == 0:
                    digits.insert(0, 1)
            else:
                digits[idx] = newValue
                return digits
        return digits

    '''Given numRows, generate the first numRows of Pascal's triangle.
    For example, given numRows = 5, Return
    [
     [1],
     [1,1],
     [1,2,1],
     [1,3,3,1],
     [1,4,6,4,1]
    ]'''
    def pascalTriangle(self, numRows):
        result = []
        for i in range(0, numRows):
            if i == 0:
                result.append([1])
                continue
            elif i == 1:
                result.append([1,1])
                continue
            temp = []
            for j in range(0, i + 1):
                if j == 0 or j == i:
                    temp.append(1)
                else:
                    temp.append(result[i-1][j-1] + result[i-1][j])
            result.append(temp)
        return result

    '''Given an index k, return the kth row of the Pascal's triangle.
    For example, given k = 3,Return [1,3,3,1].'''
    def getPascalTriangleRow(self, rowIndex):
        if rowIndex < 0:
            return []
        elif rowIndex == 0:
            return [1]
        elif rowIndex == 1:
            return [1, 1]
        result = [1, 1]
        for j in range(2, rowIndex + 1):
            result.append(1)
            temp = 1
            for i in range(0, j + 1):
                if i == 0 or i == j:
                    result[i] = 1
                elif j - i < j / 2.0:
                    result[i] = result[j - i]
                else:
                    newTemp = result[i]
                    result[i] = temp + result[i]
                    temp = newTemp
        return result

    '''Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array.'''
    def mergeArray(self, nums1, m, nums2, n):
        diff = len(nums1) - m
        while diff > 0:
            nums1.pop()
            diff -= 1
        i = 0
        for j in range(0, n):
            count = len(nums1)
            while (i < count):
                if nums1[i] >= nums2[j]:
                    nums1.insert(i, nums2[j])
                    break
                else:
                    i += 1
            if i == count:
                nums1.insert(i, nums2[j])
                i += 1

    '''Given an array of intergers, find two numbers such that they add up to a specific target number.
    The function twoSum should return indices of the two numbers such that they add up to the target,
    where index1 must be less than index2 Please note that your returned answers (both index1 and index2) are not zero-based.
    You may assume that each input would have exactly one solution.Input: numbers={2, 7, 11, 15}, target=9 Output: index1=1, index2=2'''
    def twoSum(self, nums, target):
        import collections
        counter = collections.Counter(nums)
        for index, value in enumerate(nums):
            anotherNum = target - value
            if (anotherNum != value and counter[anotherNum] != 0) or (anotherNum == value and counter[anotherNum] > 1):
                anotherIndex = nums[index + 1:len(nums)].index(anotherNum)
                return [index, index + 1 + anotherIndex]

    '''Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0?
    Find all unique triplets in the array which gives the sum of zero.
    Note: Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)
    The solution set must not contain duplicate triplets.'''
    def threeSum(self, nums):
        newNums = sorted(nums)
        result = []
        for i in range(0, len(newNums) - 2):
            if i > 0 and newNums[i] == newNums[i - 1]:
                continue
            j = i + 1
            k = len(newNums) - 1
            while j < k:
                number = newNums[i] + newNums[j] + newNums[k]
                if number == 0:
                    temp = [newNums[i], newNums[j], newNums[k]]
                    result.append(temp)
                    while True:
                        k -= 1
                        if j >= k or newNums[k] != newNums[k+1]:
                            break
                    while True:
                        j += 1
                        if j >= k or newNums[j] != newNums[j-1]:
                            break
                elif number > 0:
                    while True:
                        k -= 1
                        if j >= k or newNums[k] != newNums[k+1]:
                            break
                else:
                    while True:
                        j += 1
                        if j >= k or newNums[j] != newNums[j-1]:
                            break
        return result

    '''Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target.
    Return the sum of the three integers. You may assume that each input would have exactly one solution.'''
    def threeSumClosest(self, nums, target):
        if len(nums) < 3:
            return
        newNums = sorted(nums)
        result = nums[0] + nums[1] + nums[2]
        for i in range(0, len(newNums) - 2):
            if i > 0 and newNums[i] == newNums[i - 1]:
                continue
            j = i + 1
            k = len(newNums) - 1
            while j < k:
                number = newNums[i] + newNums[j] + newNums[k]
                if abs(number - target) < abs(result - target):
                    result = number
                elif number - target > 0:
                    while True:
                        k -= 1
                        if j >= k or newNums[k] != newNums[k+1]:
                            break
                else:
                    while True:
                        j += 1
                        if j >= k or newNums[j] != newNums[j-1]:
                            break
        return result

        '''Given an array S of n integers, are there elements a, b, c and d in S such that a+b+c+d = target?
        Find all unique quadruplets in the array which gives the sume of target.
        Note:Elements in quadruplets (a, b, c, d) must be in non-descending order. (ie, a<=b<=c<=d)
        The solution must not contain duplicates quadruplets.'''
    def fourSum(self, nums, target):
        newNums = sorted(nums)
        result = []
        h = 0
        while h < len(newNums) - 3:
            smallTarge = target - newNums[h]
            tempResult = []
            i = h + 1
            while i < len(newNums) - 2:
                j = i + 1
                k = len(newNums) - 1
                while j < k:
                    number = newNums[i] + newNums[j] + newNums[k]
                    if number == smallTarge:
                        temp = [newNums[i], newNums[j], newNums[k]]
                        tempResult.append(temp)
                        while True:
                            k -= 1
                            if j >= k or newNums[k] != newNums[k+1]:
                                break
                        while True:
                            j += 1
                            if j >= k or newNums[j] != newNums[j-1]:
                                break
                    elif number > smallTarge:
                        while True:
                            k -= 1
                            if j >= k or newNums[k] != newNums[k+1]:
                                break
                    else:
                        while True:
                            j += 1
                            if j >= k or newNums[j] != newNums[j-1]:
                                break
                while True:
                    i += 1
                    if i >= len(newNums) - 2 or newNums[i] != newNums[i - 1]:
                        break
            if len(tempResult) > 0:
                for value in tempResult:
                    value.insert(0, newNums[h])
                    result.append(value)
            while True:
                    h += 1
                    if h >= len(newNums) - 3 or newNums[h] != newNums[h - 1]:
                        break
        return result

    '''Given four lists A, B, C, D of integer values, compute how many tuples (i, j, k, l) there are such that A[i] + B[j] + C[k] + D[l] is zero.
    To make problem a bit easier, all A, B, C, D have same length of N where 0 ≤ N ≤ 500.
    All integers are in the range of -228 to 228 - 1 and the result is guaranteed to be at most 231 - 1.'''
    def fourSumCount(self, A, B, C, D):
        pass

    '''Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.
    (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).
    Find the minimum element.
    You may assume no duplicate exists in the array.'''
    def findMin(self, nums):
        if len(nums) == 1:
            return nums[0]
        elif len(nums) == 2:
            return nums[0] if nums[0] < nums[1] else nums[1]
        if nums[0] < nums[-1]:
            return nums[0]
        mid = len(nums) / 2
        if nums[mid] > nums[0]:
            return self.findMin(nums[mid:])
        else :
            return self.findMin(nums[:mid+1])

    '''Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.
    (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).
    Find the minimum element.
    The array may contain duplicates.'''
    def findMin2(self, nums):
        if len(nums) == 1:
            return nums[0]
        elif len(nums) == 2:
            return nums[0] if nums[0] < nums[1] else nums[1]
        start = 0
        end = len(nums) - 1
        while True:
            if start >= end:
                return nums[start]
            elif nums[start] != nums[end]:
                break
            start += 1
        if nums[start] < nums[end]:
            return nums[start]
        mid = start + (len(nums) - start)/ 2
        if nums[mid] >= nums[start]:
            return self.findMin2(nums[mid:])
        else :
            return self.findMin2(nums[start:mid+1])

    '''Largest Rectangle in Histogram
    https://leetcode.com/problems/largest-rectangle-in-histogram/'''
    def largestRectangleArea(self, heights):
        s = []
        heights = heights[:]
        heights.append(0);
        sum = 0
        i = 0
        while i < len(heights):
            if len(s) == 0 or heights[i] > heights[s[-1]]:
                s.append(i)
                i += 1
            else:
                t = s[-1]
                del s[-1]
                sum = max(sum, heights[t] * (i if len(s) == 0 else i - s[-1] - 1))
        return sum

    '''Given a 2D binary matrix filled with 0's and 1's,
    find the largest rectangle containing all ones and return its area.'''
    def maximalRectangle(self, matrix):
        if len(matrix) == 0:
            return 0
        result = 0
        nums = map(lambda x:int(x) ,list(matrix[0]))
        for i in range(0, len(matrix)):
            if i != 0:
                for idx in range(0, len(nums)):
                    string = matrix[i]
                    val = int(string[idx])
                    nums[idx] = val if val == 0 else nums[idx] + val
            tempMax = self.largestRectangleArea(nums)
            result = max(result, tempMax)
        return result

    '''Determine whether an integer is a palindrome. Do this without extra space.'''
    def isPalindrome(self, x):
        if x < 0:
            return False
        string = str(x)
        for i in range(0, len(string) / 2):
            if string[i] != string[-(i+1)]:
                return False
        return True

    '''Write an efficient algorithm that searches for a value in an m x n matrix.
    This matrix has the following properties:
    Integers in each row are sorted from left to right.
    The first integer of each row is greater than the last integer of the previous row.'''
    def searchMatrix(self, matrix, target):
        m = len(matrix)
        if m == 0:
            return False
        n = len(matrix[0])
        if n == 0:
            return False
        array = map(lambda x:x[0], matrix)
        start = 0
        end = m - 1
        while end - start > 1:
            mid = (start + end) / 2
            if target > array[mid]:
                start = mid
            elif target < array[mid]:
                end = mid
            else:
                return True
        if target < array[end]:
            i = start
        elif target == array[end]:
            return True
        elif target > matrix[m-1][n-1]:
            return False
        else:
            i = end
        from bisect import bisect_left
        index = bisect_left(matrix[i], target)
        if index < len(matrix[i]) and matrix[i][index] == target:
            return True
        else:
            return False

    '''Write an efficient algorithm that searches for a value in an m x n matrix.
    This matrix has the following properties:
    Integers in each row are sorted in ascending from left to right.
    Integers in each column are sorted in ascending from top to bottom.'''
    def searchMatrix2(self, matrix, target):
        row = len(matrix)
        if row == 0:
            return False
        col = len(matrix[0])
        if col == 0:
            return False
        i = col - 1
        j = 0
        while j < row and i >= 0:
            value = matrix[j][i]
            if target > value:
                j += 1
            elif target < value:
                i -= 1
            else:
                return True
        return False

    '''Given an array of integers sorted in ascending order, find the starting and ending position of a given target value.
    Your algorithm's runtime complexity must be in the order of O(log n).
    If the target is not found in the array, return [-1, -1].'''
    def searchRange(self, nums, target):
        from bisect import *
        index1 = bisect_left(nums, target)
        if index1 >= len(nums) or nums[index1] != target:
            return [-1, -1]
        index2 = index1;
        for i in range(index1+1, len(nums)):
            if nums[i] == target:
                index2 += 1
            else :
                break
        return [index1, index2]

    '''Given a sorted array and a target value, return the index if the target is found.
    If not, return the index where it would be if it were inserted in order.
    You may assume no duplicates in the array.'''
    def searchInsert(self, nums, target):
        start = 0
        end = len(nums) - 1
        while True:
            if end - start <= 1:
                if target <= nums[start]:
                    return start
                elif target > nums[end]:
                    return end + 1
                else:
                    return end
            mid = (start + end) / 2
            if nums[mid] == target:
                return mid
            elif nums[mid] > target:
                end = mid
            else:
                start = mid


from random import randint
from kivy import Config

Config.set('graphics', 'width', 600)
Config.set('graphics', 'height', 300)

from kivy.core.window import Keyboard
from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.checkbox import CheckBox
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.spinner import Spinner
from kivy.uix.stacklayout import StackLayout
from kivy.uix.textinput import TextInput

Window.size = (dp(600), dp(300))
DICES = [2, 3, 4, 6, 8, 10, 12, 20, 100]
modifier = 0
numero_de_tiradas = 1

Dice4 = CheckBox(group=True, color=(255, 0, 0, 3))
lista_num = Spinner(size_hint_y=0.5, text="1", values=("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"))
lista_mod = Spinner(size_hint_y=0.3, text="0", values=("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"))

change_num = TextInput(text=lista_num.text, size_hint=(0.5, 0.5), multiline=False)
change_mod = TextInput(text=lista_mod.text, size_hint=(0.5, 0.4), multiline=False)

Builder.load_string('''

<RV>:
    viewclass: 'Label'

    RecycleBoxLayout:

        default_size: None, dp(20)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
''')

"""class hola(RecycleBoxLayout):

    def __init__(self, **kwargs):
        super(hola, self).__init__(**kwargs)

        self.default_size = (None, dp(20))
        self.default_size_hint = (1, None)
        self.size_hint_y: None
        self.height: self.minimum_height
        self.viewclass = "Label"
        self.orientation = "vertical"
        """

class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)

class Boton(CheckBox):

    def __init__(self, **kwargs):
        super(Boton, self).__init__(**kwargs)
        self.color = (255, 0, 0, 3)
        self.dice = 0

# Fabian es mi bossu
class LoginScreen(BoxLayout):

    def __init__(self, **kwargs):
        super(LoginScreen, self).__init__(**kwargs)

        self.modifier = 0
        self.number_of_dices = 1
        self.type_dice = 0
        self.registro = 0
        self.orientation = "vertical"
        self.calculo_resultado = 0
        self.mod_is_negative = 1
        self.cb_list = []
        self.critical = ""

        Window.bind(on_key_up=self.read_key)

        # Añadiendo Widgets

        cuadricula_inicial = GridLayout()
        cuadricula_inicial.cols = 9
        cuadricula_inicial.rows = 2
        self.add_widget(cuadricula_inicial)

        # añadiendo primera cara textos y checkboxes

        for i in DICES:
            cuadricula_inicial.add_widget(Label(text="1d{0}".format(i), size_hint=(1, None), font_size=14, height=20))

        for i in DICES:
            cb = CheckBox(group=True, color=(255, 0, 0, 3), id=str(i))
            cb.bind(active=self.update_type_dice)
            cuadricula_inicial.add_widget(cb)
            self.cb_list.append(cb)

        # Segunda Caja:

        caja_de_modificadores = BoxLayout(orientation="horizontal")
        self.add_widget(caja_de_modificadores)

        # 2.1
        caja_num = StackLayout()
        caja_de_modificadores.add_widget(caja_num)
        caja_num.add_widget(Label(text="Dice Number", size_hint=(0.5, 0.5)))
        caja_num.add_widget(change_num)
        change_num.bind(text=self.input_number)
        caja_num.add_widget(lista_num)
        lista_num.bind(text=self.cuantas_lanzadas)

        # 2.2
        caja_mod = StackLayout()
        caja_de_modificadores.add_widget(caja_mod)
        caja_mod.add_widget(Label(text="Modifier", size_hint=(0.5, 0.4)))
        caja_mod.add_widget(change_mod)
        change_mod.bind(text=self.input_modifier)
        caja_mod.add_widget(Label(text="Negativo", size_hint=(0.5, 0.3)))
        self.cb_negative = CheckBox(size_hint=(0.5, 0.3))
        caja_mod.add_widget(self.cb_negative)
        self.cb_negative.bind(active=self.negative_modifier)
        caja_mod.add_widget(lista_mod)
        lista_mod.bind(text=self.cuanto_es_modificador)

        # 2.3
        self.btn = Button(text="Lanzar", font_size=18, )
        self.btn.bind(on_press=self.lanzar_dados)
        caja_de_modificadores.add_widget(self.btn)

        # Tercera caja

        self.resultado = Label(text="Esperando Lanzamiento de dados", color=(1, 0, 0, 0.8))
        self.add_widget(self.resultado)

        # Cuarta caja

        self.lista_registro = RV()
        self.add_widget(self.lista_registro)

        # xxxxxxxxxxxxxxxxxx

    def update_type_dice(self, obj, active):

        if active:
            self.type_dice = int(obj.id)

    # LANZAMIENTO DE LOS DADOS del imperio

    def lanzar_dados(self, obj):
        try:
            self.calculo_resultado = 0
            if self.registro > 0:
                self.lista_registro.data.insert(0, {
                    "text": "Lanzamiento Nº{} ".format(len(self.lista_registro.data) + 1) + self.resultado.text})

            for i in range(self.number_of_dices):
                self.calculo_resultado += randint(1, self.type_dice)
                if self.type_dice == 20 and self.number_of_dices == 1 and self.calculo_resultado == 20:
                    self.critical = " ES CRITICO"
                else:
                    self.critical = ""

            self.calculo_resultado += self.modifier * self.mod_is_negative

            if self.mod_is_negative == -1:
                self.resultado.text = "El resultado de {2}d{1} -{3} = {0}".format(
                    self.calculo_resultado, self.type_dice, self.number_of_dices, self.modifier) + self.critical
            else:
                self.resultado.text = "El resultado de {2}d{1} +{3} = {0}".format(
                    self.calculo_resultado, self.type_dice, self.number_of_dices, self.modifier) + self.critical

            self.registro = 1

            if {"text": "Esperando Lanzamiento de dados"} in self.lista_registro.data:
                self.lista_registro.data.remove({"text": "Esperando Lanzamiento de dados"})

        except ValueError:
            self.resultado.text = "NO SE HA SELECCIONADO NINGUN DADO"

    def cuanto_es_modificador(self, obj, text):

        self.modifier = int(text)
        change_mod.text = text

    # INPUT LISTA num de dados

    def cuantas_lanzadas(self, obj, text):

        self.number_of_dices = int(text)
        change_num.text = text

    def negative_modifier(self, obj, active):

        if obj.active:
            self.mod_is_negative = -1
        else:
            self.mod_is_negative = 1

    # ESCRIBIR MODIFICADOR

    def input_modifier(self, obj, text):

        if obj.text == "":
            pass

        elif obj.text == "-":

            obj.text = ""
            self.cb_negative.active = True

        elif obj.text == "+":

            obj.text = ""
            self.cb_negative.active = False
        elif "n" in obj.text:

            obj.text = lista_mod.text

        elif obj.text.isdigit():
            lista_mod.text = text
        else:

            obj.text = lista_mod.text

    # ESCRIBIR NUMERO DE DADOS

    def input_number(self, obj, text):

        if obj.text == "":

            pass

        elif "m" in obj.text:

            obj.text = lista_num.text

        elif obj.text.isdigit():

            lista_num.text = obj.text
        else:
            obj.text = lista_num.text

            # LECTURA DE TECLADO

    # LEECTURA DE TECLADO

    def read_key(self, obj, keycode, something):

        #  print((Keyboard.keycode_to_string(obj, keycode)))

        if (Keyboard.keycode_to_string(obj, keycode)) == "right":

            if not change_mod.focus and not change_num.focus:
                if self.type_dice == 100:
                    self.cb_list[DICES.index(self.type_dice)].active = False
                    self.cb_list[0].active = True
                elif self.type_dice == 0:
                    self.cb_list[0].active = True
                else:
                    self.cb_list[DICES.index(self.type_dice)].active = False
                    self.cb_list[DICES.index(self.type_dice) + 1].active = True

        if (Keyboard.keycode_to_string(obj, keycode)) == "left":
            if not change_mod.focus and not change_num.focus:
                if self.type_dice == 2:
                    self.cb_list[DICES.index(self.type_dice)].active = False
                    self.cb_list[len(self.cb_list) - 1].active = True
                elif self.type_dice == 0:
                    self.cb_list[0].active = True
                else:
                    self.cb_list[DICES.index(self.type_dice)].active = False
                    self.cb_list[DICES.index(self.type_dice) - 1].active = True

        if (Keyboard.keycode_to_string(obj, keycode)) == "enter":
            self.lanzar_dados(self.btn)
        if (Keyboard.keycode_to_string(obj, keycode)) == "up":

            if change_num.focus:
                change_num.focus = False
                change_mod.focus = True
            else:
                change_num.focus = True
                change_mod.focus = False
        if (Keyboard.keycode_to_string(obj, keycode)) == "down":
            change_num.focus = False
            change_mod.focus = False
        if (Keyboard.keycode_to_string(obj, keycode)) == "r":
            change_num.text = "1"
            change_mod.text = "0"
        if (Keyboard.keycode_to_string(obj, keycode)) == "n":
            change_num.focus = True
            change_num.text = ""
        if (Keyboard.keycode_to_string(obj, keycode)) == "m":
            change_mod.focus = True
            change_mod.text = ""
        if (Keyboard.keycode_to_string(obj, keycode)) == "spacebar":
            change_num.focus = False
            change_mod.focus = False

class DicesApp(App):

    def build(self):
        return LoginScreen()

if __name__ == '__main__':
    DicesApp().run()


# encoding: utf-8

# importar librerías externas
import torch
import random
import numpy as np
from collections import deque

# importar ei fichero ai_game.py que es el fichero que hemos creado
from ai_game import SnakeGameAI, Direction, Point
# importar el fichero model.py
from model import Linear_QNet, QTrainer
# importar el helper.py para plotear
from helper import plot

# Definición de variables de importancia
MAX_MEMORY = 100_000
BATCH_SIZE = 1000
LR = 0.001

class Agent:

    def __init__(self):
        self.n_games = 0
        self.epsilon = 0 # Aleatoriedad
        self.gamma = 0.9 # Ratio de descuento
        self.memory = deque(maxlen=MAX_MEMORY) # popleft()
        self.model = Linear_QNet(11, 256, 3)
        self.trainer = QTrainer(self.model, lr=LR, gamma=self.gamma)

    def get_state(self, game):
        head = game.snake[0]
        point_l = Point(head.x - 20, head.y)
        point_r = Point(head.x + 20, head.y)
        point_u = Point(head.x, head.y - 20)
        point_d = Point(head.x, head.y + 20)

        dir_l = game.direction == Direction.LEFT
        dir_r = game.direction == Direction.RIGHT
        dir_u = game.direction == Direction.UP
        dir_d = game.direction == Direction.DOWN

        state = [
            # Danger straight
            (dir_r and game.is_collision(point_r)) or
            (dir_l and game.is_collision(point_l)) or
            (dir_u and game.is_collision(point_u)) or
            (dir_d and game.is_collision(point_d)),

            # Danger right
            (dir_u and game.is_collision(point_r)) or
            (dir_d and game.is_collision(point_l)) or
            (dir_l and game.is_collision(point_u)) or
            (dir_r and game.is_collision(point_d)),

            # Danger left
            (dir_d and game.is_collision(point_r)) or
            (dir_u and game.is_collision(point_l)) or
            (dir_r and game.is_collision(point_u)) or
            (dir_l and game.is_collision(point_d)),

            # Move direction
            dir_l,
            dir_r,
            dir_u,
            dir_d,

            # Food location
            game.food.x < game.head.x,  # food left
            game.food.x > game.head.x,  # food right
            game.food.y < game.head.y,  # food up
            game.food.y > game.head.y  # food down
            ]

        return np.array(state, dtype=int)

    def remember(self, state, action, reward, next_state, done):
        self.memory.append((state, action, reward, next_state, done)) # popleft if MAX_MEMORY is reached

    def train_long_memory(self):
        if len(self.memory) > BATCH_SIZE:
            mini_sample = random.sample(self.memory, BATCH_SIZE) # list of tuples
        else:
            mini_sample = self.memory

        states, actions, rewards, next_states, dones = zip(*mini_sample)
        self.trainer.train_step(states, actions, rewards, next_states, dones)
        # Esto es lo mismo que lo anterior pero menos tricky.
        #for state, action, reward, nexrt_state, done in mini_sample:
        #    self.trainer.train_step(state, action, reward, next_state, done)

    def train_short_memory(self, state, action, reward, next_state, done):
        self.trainer.train_step(state, action, reward, next_state, done)

    def get_action(self, state):
        # Movimientos aleatorios: tradeoff exploration / exploitation
        self.epsilon = 80 - self.n_games
        final_move = [0,0,0]
        if random.randint(0, 200) < self.epsilon:
            # Move es un indice para modificar el movimiento entre
            # [1,0,0], [0,1,0] y [0,0,1]
            move = random.randint(0, 2)
            final_move[move] = 1
        else:
            state0 = torch.tensor(state, dtype=torch.float)
            prediction = self.model(state0)
            # Aquí el índice lo generamos vía torch.
            move = torch.argmax(prediction).item()
            final_move[move] = 1

        return final_move

def train():
    # Variables para el ploteo
    plot_scores = []
    plot_mean_scores = []
    total_score = 0
    record = 0
    # Generar objetos de las clases
    agent = Agent()
    game = SnakeGameAI()
    while True:
        # Capturamos estado anterior (old)
        state_old = agent.get_state(game)

        # Generamos movimiento
        final_move = agent.get_action(state_old)

        # Realizamos el movimiento y capturamos valores
        reward, done, score = game.play_step(final_move)
        state_new = agent.get_state(game)

        # Entrenamos short memory
        agent.train_short_memory(state_old, final_move, reward, state_new, done)

        # Almacenamos vía remember.
        agent.remember(state_old, final_move, reward, state_new, done)

        if done:
            # Entrenamos long memory, ploteamos resultados
            game.reset()
            agent.n_games += 1
            agent.train_long_memory()
            # Actualización del record
            if score > record:
                record = score
                agent.model.save()

            print('Juego', agent.n_games, 'Puntuación', score, 'Mejor puntuación:', record)
            # Ploteos
            plot_scores.append(score)
            total_score += score
            mean_score = total_score / agent.n_games
            plot_mean_scores.append(mean_score)
            plot(plot_scores, plot_mean_scores)

# Para arrancar el script vía función train.
if __name__ == '__main__':
    train()