import calendar
import json
from datetime import datetime

from assessment.models import Assessment, Evaluation, Labelling
from django.http import HttpResponse
from django.shortcuts import redirect
from django.utils.translation import gettext as _
from django.views.generic import TemplateView
from home.forms import (
    DashboardEvaluationsStatsTabFilterForm,
    DashboardOrganisationsStatsTabFilterForm,
    DashboardUsersStatsTabFilterForm,
    LabellingStatusForm,
)
from home.models import Organisation, PlatformManagement, User

class DashboardView(TemplateView):
    """
    This class defines the admin dashboard page which is composed of several subpages (stats, labelling).
    It is accessible only for admin users.
    """

    template_name = "home/dashboard/dashboard-admin.html"

    users_form = DashboardUsersStatsTabFilterForm
    organisations_form = DashboardOrganisationsStatsTabFilterForm
    evaluations_form = DashboardEvaluationsStatsTabFilterForm

    context = {}  # context holder for the GET method
    dashboard_update = {}  # context holder for the POST method

    users_filters = {}
    organisations_filters = {}
    evaluations_filters = {}

    def get(self, request, *args, **kwargs):
        """
        GET which manages Filters Form and graphs initialisation when the dashboard page loads
        for the first time
        """
        if not request.user.is_staff:
            return redirect("home:homepage")

        self.context["tab"] = kwargs.get("tab", "stats")

        # initialize the forms and pass them as context
        self.context["users_filters_form"] = self.users_form
        self.context["organisations_filters_form"] = self.organisations_form
        self.context["evaluations_filters_form"] = self.evaluations_form

        # clear filters dictionaries
        self.users_filters.clear()
        self.organisations_filters.clear()
        self.evaluations_filters.clear()

        # initialize the stats and graphs for each dashboard tab
        # users
        users_stats = self.get_users_stats()
        months_list = [month for month, _ in users_stats["one_year_users_count"].items()]
        users_count = [count for _, count in users_stats["one_year_users_count"].items()]

        # organisations
        orgas_stats = self.get_number_organisations()
        sectors_list = [sector for sector, _ in orgas_stats["nb_orgas_per_sector"].items()]
        sizes_list = [size for size, _ in orgas_stats["nb_orgas_per_size"].items()]
        orgas_count_per_sector = [
            orgas_count for _, orgas_count in orgas_stats["nb_orgas_per_sector"].items()
        ]
        orgas_count_per_size = [
            orgas_count for _, orgas_count in orgas_stats["nb_orgas_per_size"].items()
        ]

        # evaluations
        evals_stats = self.get_evaluations_stats()
        versions_list = [
            version_number for version_number, _ in evals_stats["versions_stats"].items()
        ]
        evals_count_per_version = [
            nb_evals for _, nb_evals in evals_stats["versions_stats"].items()
        ]

        # pass the stats as context
        # users
        self.context["nb_users"] = users_stats["nb_users"]
        self.context["users_count_per_month"] = zip(months_list, users_count)
        self.context["min_date"] = users_stats["min_date"]

        # organisations
        self.context["nb_orgas"] = orgas_stats["nb_orgas"]
        self.context["orgas_count_per_sector"] = zip(sectors_list, orgas_count_per_sector)
        self.context["orgas_count_per_size"] = zip(sizes_list, orgas_count_per_size)
        self.context["creation_date"] = "01-01-2020"

        # evaluations
        self.context["nb_evals"] = evals_stats["total_nb_evals"]
        self.context["nb_completed_evals"] = evals_stats["nb_evaluations_completed"]
        self.context["nb_in_progress_evals"] = evals_stats["nb_evaluations_in_progress"]
        self.context["evals_count_per_version"] = zip(versions_list, evals_count_per_version)
        self.context["eval_creation_date"] = evals_stats["eval_creation_date"]

        # Manage the labellings
        self.context["labellings"] = [labelling for labelling in Labelling.objects.all()]
        self.context["labelling_threshold"] = PlatformManagement.get_labelling_threshold()
        self.context["labelling_status_form"] = LabellingStatusForm()

        return self.render_to_response(self.context)

    def post(self, request, *args, **kwargs):
        """
        POST which manages filters requests
        """
        if not request.user.is_staff:
            return redirect("home:homepage")

        # clear previous dashboard updates
        self.dashboard_update.clear()

        if request.method == "POST" and request.is_ajax:
            if "Inscription_date" in request.POST:
                self.update_stats_and_graphs(request, which_form=0)

            elif "creation_date" in request.POST:
                self.update_stats_and_graphs(request, which_form=1)

            elif "date" in request.POST:
                self.update_stats_and_graphs(request, which_form=2)
            else:
                self.dashboard_update["success"] = False
                self.dashboard_update["message"] = _(
                    "An error has occurred, please check your input"
                )

            return HttpResponse(
                json.dumps(self.dashboard_update), content_type="application/json"
            )

        else:
            return self.render_to_response(self.context)

    def update_stats_and_graphs(self, request, which_form):
        """
        Receive the values submitted through the filters form from one of the
        the three tabs, then calls stats and graphs functions
        """
        # handle users filter form
        if which_form == 0:
            form = DashboardUsersStatsTabFilterForm(request.POST)
            self.dashboard_update["which_tab"] = which_form
            if form.is_valid():
                self.users_filters["Inscription_date"] = form.cleaned_data.get(
                    "Inscription_date"
                ).strftime("%Y-%m-%d " "%H:%M:%S")
                users_stats = self.get_users_stats()

                self.dashboard_update["nb_users"] = users_stats["nb_users"]
                self.dashboard_update["months"] = [
                    month for month, nb_users in users_stats["one_year_users_count"].items()
                ]
                self.dashboard_update["users_count"] = [
                    nb_users for month, nb_users in users_stats["one_year_users_count"].items()
                ]
                self.dashboard_update["min_date"] = users_stats["min_date"]
                self.dashboard_update["success"] = True

            else:
                self.dashboard_update["success"] = False
                self.dashboard_update["message"] = _(
                    "An error has occurred, please check your input"
                )

        # handle organisations filter form
        elif which_form == 1:
            form = DashboardOrganisationsStatsTabFilterForm(request.POST)
            self.dashboard_update["which_tab"] = which_form
            if form.is_valid():
                self.organisations_filters["creation_date"] = form.cleaned_data.get(
                    "creation_date"
                ).strftime("%Y-%m-%d " "%H:%M:%S")

                # get the stats and graphs updates
                orgas_stats = self.get_number_organisations()

                self.dashboard_update["nb_orgas"] = orgas_stats["nb_orgas"]
                self.dashboard_update["sectors_list"] = [
                    str(sector)
                    for sector, nb_orgas in orgas_stats["nb_orgas_per_sector"].items()
                ]
                self.dashboard_update["sizes_list"] = [
                    str(size) for size, nb_orgas in orgas_stats["nb_orgas_per_size"].items()
                ]
                self.dashboard_update["orgas_count_per_sector"] = [
                    nb_orgas for sector, nb_orgas in orgas_stats["nb_orgas_per_sector"].items()
                ]
                self.dashboard_update["orgas_count_per_size"] = [
                    nb_orgas for size, nb_orgas in orgas_stats["nb_orgas_per_size"].items()
                ]
                self.dashboard_update["creation_date"] = form.cleaned_data.get(
                    "creation_date"
                ).strftime("%d-%m-%Y")
                self.dashboard_update["success"] = True

            else:
                self.dashboard_update["success"] = False
                self.dashboard_update["message"] = _(
                    "An error has occurred, please check your input"
                )

        # handle evaluations filter form
        elif which_form == 2:
            form = DashboardEvaluationsStatsTabFilterForm(request.POST)
            self.dashboard_update["which_tab"] = which_form
            if form.is_valid():
                self.evaluations_filters["date"] = form.cleaned_data.get("date").strftime(
                    "%Y-%m-%d " "%H:%M:%S"
                )
                self.evaluations_filters["date_raw"] = form.cleaned_data.get("date")
                self.evaluations_filters["sectors"] = form.cleaned_data.get("sectors")
                self.evaluations_filters["sizes"] = form.cleaned_data.get("sizes")

                evals_stats = self.get_evaluations_stats()
                self.dashboard_update["total_nb_evals"] = evals_stats["total_nb_evals"]
                self.dashboard_update["versions_list"] = [
                    version_number
                    for version_number, nb_evals in evals_stats["versions_stats"].items()
                ]

                self.dashboard_update["nb_evals_per_version"] = [
                    nb_evals for version, nb_evals in evals_stats["versions_stats"].items()
                ]
                self.dashboard_update["nb_completed_evals"] = evals_stats[
                    "nb_evaluations_completed"
                ]
                self.dashboard_update["nb_in_progress_evals"] = evals_stats[
                    "nb_evaluations_in_progress"
                ]
                self.dashboard_update["eval_creation_date"] = evals_stats["eval_creation_date"]
                self.dashboard_update["success"] = True
            else:
                self.dashboard_update["success"] = False
                self.dashboard_update["message"] = _(
                    "An error has occurred, please check your input"
                )

        else:
            self.dashboard_update["success"] = False
            self.dashboard_update["message"] = _(
                "An error has occurred, no relevant tabs were selected"
            )

    def get_number_organisations(self):
        """
        - Return the number of organisations based on the filters
        """
        orgas_stats = {"nb_orgas_per_sector": {}, "nb_orgas_per_size": {}}

        if not self.organisations_filters:
            # no filters to apply, return the total number of organisations
            orgas_stats["nb_orgas"] = Organisation.objects.all().count()

            # number of organisations per sector
            for sector_tuple in Organisation.SECTOR:
                orgas_stats["nb_orgas_per_sector"][
                    sector_tuple[1]
                ] = Organisation.objects.filter(sector=sector_tuple[0]).count()

            # number of organisations per size
            for size_tuple in Organisation.SIZE:
                orgas_stats["nb_orgas_per_size"][size_tuple[1]] = Organisation.objects.filter(
                    size=size_tuple[1]
                ).count()

        else:
            # apply filters here
            # organisation creation date
            orgas_stats["nb_orgas"] = Organisation.objects.filter(
                created_at__gte=self.organisations_filters["creation_date"]
            ).count()

            # number of organisations per sector
            for sector_tuple in Organisation.SECTOR:
                orgas_stats["nb_orgas_per_sector"][
                    sector_tuple[1]
                ] = Organisation.objects.filter(
                    sector=sector_tuple[0],
                    created_at__gte=self.organisations_filters["creation_date"],
                ).count()

            # number of organisations per size
            for size_tuple in Organisation.SIZE:
                orgas_stats["nb_orgas_per_size"][size_tuple[1]] = Organisation.objects.filter(
                    size=size_tuple[1],
                    created_at__gte=self.organisations_filters["creation_date"],
                ).count()

        return orgas_stats

    def get_users_stats(self):
        """
        Return the number of users based on the filters and call corresponding graph functions
        """
        users_stats = {"one_year_users_count": {}}

        if not self.users_filters:
            # no filters to apply, return the total number of users
            users_stats["nb_users"] = User.object.all().count()

            min_date = datetime(2020, 1, 1)
            users_stats["min_date"] = min_date.strftime("%d-%b-%Y")
            for i in range(12):
                date_to_filter_by = take_n_month_steps(min_date, i)
                users_stats["one_year_users_count"][
                    date_to_filter_by.strftime("%B-%Y")
                ] = User.object.filter(created_at__lte=date_to_filter_by).count()

        else:
            # apply filter
            users_stats["nb_users"] = User.object.filter(
                created_at__gte=self.users_filters["Inscription_date"]
            ).count()

            submitted_date = datetime.strptime(
                self.users_filters["Inscription_date"], "%Y-%m-%d %H:%M:%S"
            )
            users_stats["min_date"] = submitted_date.strftime("%d-%b-%Y")
            submitted_date_end_month = datetime(
                int(submitted_date.year),
                int(submitted_date.month),
                int(calendar.monthrange(submitted_date.year, submitted_date.month)[1]),
                23,
                59,
                59,
            )

            for i in range(12):
                date_to_filter_by = take_n_month_steps(submitted_date_end_month, i)
                users_stats["one_year_users_count"][
                    date_to_filter_by.strftime("%B-%Y")
                ] = User.object.filter(created_at__lte=date_to_filter_by).count()

        return users_stats

    def get_evaluations_stats(self):
        """
        Return the number of evaluations based on the filters(date,sector,size)
        """
        evals_stats = {"versions_stats": {}}
        versions = Assessment.objects.values("version")

        if not self.evaluations_filters:
            # no filters to apply, return the total number of evaluations
            # number of evaluations per status
            evals_stats["nb_evaluations_completed"] = Evaluation.objects.filter(
                is_finished=True
            ).count()
            evals_stats["nb_evaluations_in_progress"] = Evaluation.objects.filter(
                is_finished=False
            ).count()

            # number of evaluations per version
            for version in versions:
                evals_stats["versions_stats"][version["version"]] = Evaluation.objects.filter(
                    assessment=Assessment.objects.get(version=version["version"])
                ).count()
            evals_stats["eval_creation_date"] = "01-01-2020"

        else:
            # apply the filters
            evals_stats["eval_creation_date"] = self.evaluations_filters["date_raw"].strftime(
                "%d-%m-%Y"
            )
            if self.evaluations_filters["sectors"] == _(
                "all sectors"
            ) and self.evaluations_filters["sizes"] == _("all sizes"):
                # number of evaluations per version for all sectors and all sizes
                for version in versions:
                    evals_stats["versions_stats"][
                        version["version"]
                    ] = Evaluation.objects.filter(
                        assessment__version=version["version"],
                        created_at__gte=self.evaluations_filters["date"],
                    ).count()
                # number of evaluations per status for all sectors and all sizes
                evals_stats["nb_evaluations_completed"] = Evaluation.objects.filter(
                    is_finished=True, created_at__gte=self.evaluations_filters["date"]
                ).count()
                evals_stats["nb_evaluations_in_progress"] = Evaluation.objects.filter(
                    is_finished=False, created_at__gte=self.evaluations_filters["date"]
                ).count()
            elif self.evaluations_filters["sectors"] == _(
                "all sectors"
            ) and self.evaluations_filters["sizes"] != _("all sizes"):
                # number of evaluations per versions for all sectors but for a specified size
                for version in versions:
                    evals_stats["versions_stats"][
                        version["version"]
                    ] = Evaluation.objects.filter(
                        assessment__version=version["version"],
                        organisation__size=self.evaluations_filters["sizes"],
                        created_at__gte=self.evaluations_filters["date"],
                    ).count()
                # number of evaluations per status for all sectors but for a specified size
                evals_stats["nb_evaluations_completed"] = Evaluation.objects.filter(
                    is_finished=True,
                    created_at__gte=self.evaluations_filters["date"],
                    organisation__size=self.evaluations_filters["sizes"],
                ).count()
                evals_stats["nb_evaluations_in_progress"] = Evaluation.objects.filter(
                    is_finished=False,
                    created_at__gte=self.evaluations_filters["date"],
                    organisation__size=self.evaluations_filters["sizes"],
                ).count()
            elif self.evaluations_filters["sectors"] != _(
                "all sectors"
            ) and self.evaluations_filters["sizes"] == _("all sizes"):
                # number of evaluations per versions for all sizes but for a specified sector
                for version in versions:
                    evals_stats["versions_stats"][
                        version["version"]
                    ] = Evaluation.objects.filter(
                        assessment__version=version["version"],
                        organisation__sector=self.evaluations_filters["sectors"],
                        created_at__gte=self.evaluations_filters["date"],
                    ).count()
                # number of evaluations per status for all sizes but for a specified sector
                evals_stats["nb_evaluations_completed"] = Evaluation.objects.filter(
                    is_finished=True,
                    created_at__gte=self.evaluations_filters["date"],
                    organisation__sector=self.evaluations_filters["sectors"],
                ).count()
                evals_stats["nb_evaluations_in_progress"] = Evaluation.objects.filter(
                    is_finished=False,
                    created_at__gte=self.evaluations_filters["date"],
                    organisation__sector=self.evaluations_filters["sectors"],
                ).count()
            else:
                # number of evaluations per version for a specified sector and a size
                for version in versions:
                    evals_stats["versions_stats"][
                        version["version"]
                    ] = Evaluation.objects.filter(
                        assessment__version=version["version"],
                        organisation__sector=self.evaluations_filters["sectors"],
                        organisation__size=self.evaluations_filters["sizes"],
                        created_at__gte=self.evaluations_filters["date"],
                    ).count()
                # number of evaluations per status for a specified sector and a size
                evals_stats["nb_evaluations_completed"] = Evaluation.objects.filter(
                    is_finished=True,
                    created_at__gte=self.evaluations_filters["date"],
                    organisation__sector=self.evaluations_filters["sectors"],
                    organisation__size=self.evaluations_filters["sizes"],
                ).count()
                evals_stats["nb_evaluations_in_progress"] = Evaluation.objects.filter(
                    is_finished=False,
                    created_at__gte=self.evaluations_filters["date"],
                    organisation__sector=self.evaluations_filters["sectors"],
                    organisation__size=self.evaluations_filters["sizes"],
                ).count()
        evals_stats["total_nb_evals"] = (
            evals_stats["nb_evaluations_completed"] + evals_stats["nb_evaluations_in_progress"]
        )
        return evals_stats

def take_n_month_steps(date, nb_steps):
    """
    This function takes a "date" as a parameter then adds or subtracts "nb_steps" months from it (depending
    on the sign of nb_steps), It returns the resulting date
    """
    m, y = (date.month + nb_steps) % 12, date.year + (date.month + nb_steps - 1) // 12
    if not m:
        m = 12
    d = min(
        date.day,
        [
            31,
            29 if y % 4 == 0 and (not y % 100 == 0 or y % 400 == 0) else 28,
            31,
            30,
            31,
            30,
            31,
            31,
            30,
            31,
            30,
            31,
        ][m - 1],
    )
    return date.replace(day=d, month=m, year=y)


import matplotlib.pyplot as plt
import numpy as np
import os
filenames=[]

directory = "Evaluations_results"

for filename in sorted(os.listdir(directory)):
    if filename.endswith(".txt"):
        filenames.append(filename)

with open(directory+"/"+filenames[0], 'r') as f1:
    data1 = [line.strip().split() for line in f1.readlines()]

with open(directory+"/"+filenames[1], 'r') as f2:
    data2 = [line.strip().split() for line in f2.readlines()]

with open(directory+"/"+filenames[2], 'r') as f3:
    data3 = [line.strip().split() for line in f3.readlines()]

with open(directory+"/"+filenames[3], 'r') as f4:
    data4 = [line.strip().split() for line in f4.readlines()]

with open(directory+"/"+filenames[4], 'r') as f5:
    data5 = [line.strip().split() for line in f5.readlines()]

fig, ax = plt.subplots(figsize=(14, 10.5))
combined_data = []
k=0
mul1=1
mul2=1
mul3=1
mul4=1
mul5=1
for row in data1:
    name=row[0].split('.trace.gz')[0]
    name=name.split('.champsimtrace')[0]
    value1=row[1]
    mul1*=float(value1)
    value2 = data2[k][1]
    mul2*=float(value2)
    value3 = data3[k][1]
    mul3*=float(value3)
    value4 = data4[k][1]
    mul4*=float(value4)
    value5 = data5[k][1]
    mul5*=float(value5)
    k+=1
    combined_data.append((name, float(value1), float(value2),float(value3),float(value4),float(value5)))

# Sort the data by name
combined_data.sort(key=lambda x: x[0])

# Extract the data into separate lists for plotting
names = [x[0] for x in combined_data]
values1 = [x[1] for x in combined_data]
values2 = [x[2] for x in combined_data]
values3 = [x[3] for x in combined_data]
values4 = [x[4] for x in combined_data]
values5 = [x[5] for x in combined_data]

N = len(names)
ind = np.arange(N)
width = 0.164

bar1 = plt.bar(ind, values1, width, color='r')
bar2 = plt.bar(ind+width, values2, width, color='c')
bar3 = plt.bar(ind+2*width, values3, width, color='y')
bar4 = plt.bar(ind+3*width, values4, width, color='m')
bar5 = plt.bar(ind+4*width, values5, width, color='b')

plt.axhline(y=mul1**(1/len(names)), color='r', linestyle='--')  # Add a horizontal line at y
plt.axhline(y=mul2**(1/len(names)), color='b', linestyle='--')
plt.axhline(y=mul3**(1/len(names)), color='g', linestyle='--')
plt.axhline(y=mul4**(1/len(names)), color='y', linestyle='--')
plt.axhline(y=mul5**(1/len(names)), color='c', linestyle='--')
for i in range(len(filenames)):
    filenames[i]=filenames[i].replace(".txt","").replace(f"_graphs","")

with open('evaluations.txt', 'w') as f:
    print(directory,file=f)
    print(f"Cummulative ipc of {filenames[0]}:",mul1**(1/len(names)),file=f)
    print(f"Cummulative ipc of {filenames[1]}:",mul2**(1/len(names)),file=f)
    print(f"Cummulative ipc of {filenames[2]}:",mul3**(1/len(names)),file=f)
    print(f"Cummulative ipc of {filenames[3]}:",mul4**(1/len(names)),file=f)
    print(f"Cummulative ipc of {filenames[4]}:",mul5**(1/len(names)),file=f)
plt.xlabel("Traces")
plt.ylabel('IPC') # or IPC or MISS RATE
plt.title("IPC comparison")
plt.xticks(ind+(2.5)*width, names)
plt.xticks(rotation=90)
plt.legend((bar1, bar2, bar3, bar4, bar5), (filenames[0], filenames[1], filenames[2],filenames[3],filenames[4]))
#plt.ylim(ymin=100000)
#plt.ylim(0, 1.15)
# Adjust top margin
fig.subplots_adjust(top=0.95)
plt.savefig("Evaluations_results.png")


from collections import deque
from math import inf
def bfs(start_row,start_col,visited,election_graph):
    election_number=election_graph[start_row][start_col]

    dy=[-1,0,1,0]
    dx=[0,1,0,-1]

    queue=deque([(start_row,start_col)])
    count=0
    while queue:
        row,col=queue.popleft()

        if visited[row][col]:
            continue
        visited[row][col]=True
        count+=graph[row][col]
        for dir in range(4):
            next_row=row+dy[dir]
            next_col=col+dx[dir]

            if next_row < 0 or next_row>=num or next_col < 0 or next_col >=num:
                continue

            if election_graph[next_row][next_col]!=election_number:
                continue

            queue.append((next_row,next_col))

    return count

def allocate_election_number(top_left,top_right,bottom_left,bottom_right,d1,d2):
    election_graph=[[0] * num for _ in range(num)]

    for row in range(num):
        for col in range(num):
            if 0<=row<bottom_left[0] and 0<=col<=top_left[1]:
                election_graph[row][col]=1
            elif 0 <= row <= top_right[0] and top_left[1] < col <num:
                election_graph[row][col]=2
            elif bottom_left[0] <= row <num and 0 <= col < bottom_right[1]:
                election_graph[row][col]=3
            elif top_right[0] < row <num and bottom_right[1] <= col < num:
                election_graph[row][col]=4

    #경계선 표시
    for i in range(d1+1):
        election_graph[top_left[0]+i][top_left[1]-i]=5
        election_graph[top_right[0]+i][top_right[1]-i]=5
    for i in range(d2+1):
        election_graph[top_left[0]+i][top_left[1]+i]=5
        election_graph[bottom_left[0]+i][bottom_left[1]+i]=5

    return election_graph

def solution():

    min_count=inf
    for start_row in range(num):
        for start_col in range(num):
            for d1 in range(1,num-1):
                for d2 in range(1,num-1):
                    #경계를 넘어서는지 여부 판단
                    top_left=(start_row,start_col)
                    top_right=(start_row+d2,start_col+d2)
                    bottom_left=(start_row+d1,start_col-d1)
                    bottom_right=(start_row+d1+d2,start_col+d2-d1)

                    if top_right[0] < 0 or top_right[0]>=num or top_right[1] < 0 or top_right[1] >=num:
                        continue
                    if bottom_left[0] < 0 or bottom_left[0]>=num or bottom_left[1] < 0 or bottom_left[1] >=num:
                        continue
                    if bottom_right[0] < 0 or bottom_right[0]>=num or bottom_right[1] < 0 or bottom_right[1] >=num:
                        continue

                    #기준선을 따라서 선거구역 할당
                    election_graph=allocate_election_number(top_left,top_right,bottom_left,bottom_right,d1,d2)

                    visited=[[False] * num for _ in range(num)]
                    #각각의 선거구역의 인구수를 파악하기 위한 배열
                    election_counts=[]
                    #각 꼭짓점 부분과, 기준선상의 점 하나를 이용해서 선거구역에 대한 bfs component을 구할 수 있도록 한다.
                    for row,col in [(0,0),(0,num-1),(num-1,0),(num-1,num-1)]:
                        election_counts.append(bfs(row,col,visited,election_graph))

                    election_counts.append(sum_of_citizen-sum(election_counts))
                    min_count=min(min_count,max(election_counts)-min(election_counts))

    return min_count

if __name__ == "__main__":
    num=0
    graph=[]
    sum_of_citizen=0
    with open("E:\\Codes\\Code-Test-Preparing\\algorithm\\Tasks_By_Algorithms\\DFS&BFS\\input17779.txt","r") as file:
        num=int(file.readline())
        for _ in range(num):
            row=list(map(int,file.readline().split()))
            graph.append(row)
            sum_of_citizen+=sum(row)

    print(solution())


from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render

from cash.models import CashReport
from hours.models import WorkedHours
from reports.models import Report

@login_required
def calendar_navigate(request):
    """Show calendar."""

    return render(request, 'home/calendar.html')

@permission_required(['hours.view_workedhours', 'reports.view_report',
                      'cash.view_cashreport'])
def calendar_show_day(request, year, month, day):
    """Show day."""

    reports = Report.objects.filter(
        created_on__year=year,
        created_on__month=month,
        created_on__day=day,
        caffe=request.user.caffe
    ).all()

    cash_reports = CashReport.objects.filter(
        created_on__year=year,
        created_on__month=month,
        created_on__day=day,
        caffe=request.user.caffe
    ).all()

    worked_hours = WorkedHours.objects.filter(
        date__year=year,
        date__month=month,
        date__day=day,
        caffe=request.user.caffe
    ).all()

    return render(request, 'calendar/full_day.html', {
        'reports': reports,
        'cash_reports': cash_reports,
        'worked_hours': worked_hours,
        'date': {
            'year': year,
            'month': month,
            'day': day
        }
    })


from django.shortcuts import render
from rsefficiency.modules.base import get_base_url
import json
import os
from django.http import HttpResponse

def quest_index(request):
    try:
        file_path = os.path.join(os.path.dirname(__file__), 'static_data/member_quests.json')
        member_quests = json.loads(open(file_path).read())
    except:
        data = {'success': False, 'error_id': 2, 'error_msg:': 'IO Error', 'directory': file_path}
        return HttpResponse(json.dumps(data), 'application/json')

    try:
        file_path = os.path.join(os.path.dirname(__file__), 'static_data/f2p_quests.json')
        f2p_quests = json.loads(open(file_path).read())
    except:
        data = {'success': False, 'error_id': 2, 'error_msg:': 'IO Error', 'directory': file_path}
        return HttpResponse(json.dumps(data), 'application/json')

    try:
        file_path = os.path.join(os.path.dirname(__file__), 'static_data/quests.json')
        quests = json.loads(open(file_path).read())
    except:
        data = {'success': False, 'error_id': 2, 'error_msg:': 'IO Error', 'directory': file_path}
        return HttpResponse(json.dumps(data), 'application/json')

    f2p_quest_list = []
    member_quest_list = []

    # Check quest
    for f2p_quest in f2p_quests:
        f2p_quest_name = f2p_quest['quest_name'].lower().replace("'", "").replace(' ', "_")
        if f2p_quest_name in quests:
            f2p_quest_list.append(f2p_quest)

    for member_quest in member_quests:
        member_quest_name = member_quest['quest_name'].lower().replace("'", "").replace('- ', '').replace(' ', "_").replace('!', '')
        if member_quest_name in quests:
            member_quest_list.append(member_quest)

    print (len(f2p_quests) + len(member_quests)) - (len(f2p_quest_list) + len(member_quest_list))

    data = {
        'base_url': get_base_url(),
        'f2p_quests': json.dumps(f2p_quest_list),
        'member_quests': json.dumps(member_quest_list),
        'quest': {}
    }

    return render(request, 'quest.html', data)

def quest(request, quest_name):
    try:
        file_path = os.path.join(os.path.dirname(__file__), 'static_data/quests.json')
        quests = json.loads(open(file_path).read())
    except:
        data = {'success': False, 'error_id': 2, 'error_msg:': 'IO Error', 'directory': file_path}
        return HttpResponse(json.dumps(data), 'application/json')

    quest_data_name = quest_name.strip().lower().replace('-', '_')

    if quest_data_name in quests:
        data = {
            'base_url': get_base_url(),
            'f2p_quests': {},
            'member_quests': {},
            'quest': json.dumps(quests[quest_data_name])
        }

        return render(request, 'quest.html', data)
    else:
        html = 'quests/' + quest_data_name + '.html'
        return render(request, html)


from framework.utils import *
from framework.RpySeries import *
from framework.symbol import *
from framework.base import *
from framework.meta_data import *
from framework.stats_basic import *
import Retropy_framework as frm

def divs(symbolName, period=None, fill=False):
    name = get_name(symbolName)
    if name.startswith("~"):
        divs = sym[-1:0] # we just want an empty series with DatetimeIndex
        #divs = pd.Series(index=pd.DatetimeIndex(freq="D"))
        divs.name = name
    else:
        divs = get(symbolName, mode="divs")
        divs = divs[divs>0]
    if period:
        divs = wrap(divs.rolling(period).sum())
    if fill:
        price = get(symbolName)
        divs = divs.reindex(price.index.union(divs.index), fill_value=0)
    divs.name = divs.name + " divs"
    return divs

def get_divs_interval(divs):
    divs = divs.resample("M").sum()
    divs = divs[divs>0]
    monthds_diff = (divs.index.to_series().diff().dt.days/30).dropna().apply(lambda x: int(round(x)))
    monthds_diff = monthds_diff[-5:].median()
    return monthds_diff

def get_yield_types(sym, **args):
    n = get_name(sym)
    true_yield = name(get_yield(sym, type='true', **args), n + " true")
    normal_yield = name(get_yield(sym, type='normal', **args), n + " normal")
    rolling_yield = name(get_yield(sym, type='rolling', **args), n + " rolling")
    return[true_yield, normal_yield, rolling_yield]

def get_named(s, func):
    return (func(s), f"{get_pretty_name(s)} {get_func_name(func)}")

def get_yield_true(sym):
    return get_yield(sym, type='true')

def get_yield_true_no_fees(sym):
    return get_yield(sym, type='true', reduce_fees=False)

def get_yield_normal(sym):
    return get_yield(sym, type='normal')

def get_yield_normal_no_fees(sym):
    return get_yield(sym, type='normal', reduce_fees=False)

def get_yield_rolling(sym):
    return get_yield(sym, type='rolling')

def get_yield_rolling_no_fees(sym):
    return get_yield(sym, type='rolling', reduce_fees=False)

def get_yield(sym, type=None, drop_special_divs=False, keep_trim=False, reduce_fees=True, altPriceName=None, divs_only=False, do_resample=True):
    type = type or 'true'
    if type == 'true':
        return get_yield_helper(sym, window_months=1, drop_special_divs=drop_special_divs, keep_trim=keep_trim, reduce_fees=reduce_fees, altPriceName=altPriceName, divs_only=divs_only, do_resample=do_resample)
    if type == 'normal':
        yld_true = get_yield_helper(sym, window_months=1, drop_special_divs=drop_special_divs, keep_trim=keep_trim, reduce_fees=reduce_fees, altPriceName=altPriceName, divs_only=divs_only, do_resample=do_resample)
        return mm(yld_true, 5)
    if type == 'rolling':
        return get_yield_helper(sym, window_months=12, drop_special_divs=drop_special_divs, keep_trim=keep_trim, reduce_fees=reduce_fees, altPriceName=altPriceName, divs_only=divs_only, do_resample=do_resample)
    raise Exception(f"Invalid yield type {type}")

# for each div, how any months passed from the previous div (the first one gets the same value as the second)
def get_divs_intervals(divs):
    divs = divs.resample("M").sum()
    divs = divs[divs>0]
    monthds_diff = (divs.index.to_series().diff().dt.days/30).fillna(method='backfill').apply(lambda x: int(round(x)))
    return monthds_diff

def rolling_timeframe(d, f, offset):
    vals = []
    index = d[d.index[0] + offset:].index
    for dt in index:
    #    print(f"start: {dt - pd.DateOffset(years=1)}, end: {dt}")
        vals.append(f(d[(d.index <= dt) & (d.index > dt - offset)]))
    res = pd.Series(vals, index)
    return res

def get_yield_helper(symbolName, dists_per_year=None, altPriceName=None, window_months=12, drop_special_divs=False, keep_trim=False, reduce_fees=True, divs_only=False, do_resample=True):
    # if isinstance(symbolName, tuple) and dists_per_year is None:
    #     symbolName, dists_per_year = symbolName
    start = None
    if is_series(symbolName):
        start = symbolName.index[0]
        symbolName = symbolName.name
    if is_symbol(symbolName):
        sym_mode = symbolName.mode
    else:
        sym_mode = None
    if symbolName.startswith("~"):
        return pd.Series()
    price = get(altPriceName or symbolName, mode="PR")
    divs = get(symbolName, mode="divs")
    divs = divs[divs>0]
    if len(divs) == 0:
        return divs
    if keep_trim and start:
        price = price[start:]
        divs = divs[start:]

    if drop_special_divs:
        vc = divs.index.day.value_counts()
        vc = vc.reindex(range(31))
        df = pd.DataFrame([vc, vc.shift(1), vc.shift(-1)]).T.fillna(0)
        special_divs_days = set(df[(df.iloc[:, 0] > 0) & (df.iloc[:, 0] <= 2) & (df.iloc[:, 1] == 0) & (df.iloc[:, 2] == 0)].index)
        dropped_divs = divs[divs.index.day.isin(special_divs_days)]
        if dropped_divs.shape[0] > 0:
            dates = ', '.join([str(x)[:10] for x in dropped_divs.index.values])
            print_norep(f"Dropped {dropped_divs.shape[0]} special dividends in {get_pretty_name(symbolName)} on {dates}")
        divs = divs[~divs.index.day.isin(special_divs_days)]

    if sym_mode == "NTR":
        divs *= 0.75
    elif sym_mode == "PR":
        divs *= 0

    # sometimes 2 or more divs can happen in the same month (capital gains)
    # we must resample and sum to 1-month resolution to correctly calculate the months_between_dists
    # and later to correctly do a rolling sum
    # NOTE: this causes a falw in the yield calculation
    # the actual dividends are usually mid-month, while the resample changes the dates to end of month
    # and thus later we divide by price at end of month, and not at the dividend date
    if do_resample:
        divs = divs.resample("M").sum()
        price = price.resample("M").mean()
    divs = divs[divs>0]

    divs, price = sync(divs, price)

    if len(divs) == 1 and window_months == 1: # we can't get_divs_intervals
        return divs[0:0]
    if len(divs) == 0:
        return divs

    if window_months > 1:
        mult = 12 / window_months # annualizer
        offset = pd.DateOffset(months=window_months-1, days=15)
        divs = rolling_timeframe(divs, lambda x: np.sum(x), offset)
        if divs_only:
            yld = divs * mult
        else:
            yld = divs * mult / price * 100
    else:
        divs_intervals = get_divs_intervals(divs)
        mult = 12 / divs_intervals # annualizer
        _, mult = expand(divs, mult)
        mult = mult.fillna(method='bfill')
        if divs_only:
            yld = divs * mult
        else:
            yld = divs * mult / price * 100

    res = name(yld, symbolName).dropna()
    if reduce_fees and sym_mode != "PR" and not divs_only:
        fees = get_meta_fee(symbolName)
        res -= fees
    return res

def get_curr_yield_max(s):
    return max([get_curr_yield_true(s), get_curr_yield_normal(s), get_curr_yield_rolling(s)])

def get_curr_yield_min(s):
    return min([get_curr_yield_true(s), get_curr_yield_normal(s), get_curr_yield_rolling(s)])

def get_curr_yield_min2(s):
    return min([get_curr_yield_normal(s), get_curr_yield_rolling(s)])

def get_curr_yield_true(s):
    return get_curr_yield(s, type='true')

def get_curr_yield_true_no_fees(s):
    return get_curr_yield(s, type='true', reduce_fees=False)

def get_curr_yield_normal(s):
    return get_curr_yield(s, type='normal')

def get_curr_yield_normal_no_fees(s):
    return get_curr_yield(s, type='normal', reduce_fees=False)

def get_start_yield_normal(s):
    return get_start_yield(s, type='normal')

def get_start_yield_normal_no_fees(s):
    return get_start_yield(s, type='normal', reduce_fees=False)

def get_curr_yield_rolling(s, reduce_fees=True):
    return get_curr_yield(s, type='rolling')

def get_curr_yield_rolling_no_fees(s):
    return get_curr_yield(s, type='rolling', reduce_fees=False)

def get_curr_yield(s, type=None, reduce_fees=True, live=True):
    type = type or 'rolling'
    yld = get_yield(s, type=type, reduce_fees=reduce_fees, divs_only=live).dropna()
    if yld.shape[0] == 0:
        return 0
    if live:
        yld = yld[-1] / pr(s)[-1] * 100
        if reduce_fees and get(s).name.mode != "PR":
            fees = get_meta_fee(s)
            yld -= fees
        return yld
    return yld[-1]

def get_start_yield(s, type=None, reduce_fees=True):
    type = type or 'rolling'
    yld = get_yield(s, type=type, keep_trim=True, reduce_fees=reduce_fees).dropna()
    if yld.shape[0] == 0:
        return 0
    return yld[0]

def get_curr_net_yield(s, type=None):
    return get_curr_yield(s, type=type)*0.75

def get_TR_from_PR_and_divs(pr, divs):
    m = divs / pr + 1
    mCP = m.cumprod().fillna(method="ffill")
    tr = pr * mCP
    return wrap(tr, pr.name + " TR")

def get_income_median(s):
    income = get_income(s, smooth=1)
    income = income[income > 0]
    income = mm(income, 12)
    return income

def get_income_ulcer(s):
    return ulcer_pr(get_income_median(s))

def show_income_ulcer(*all):
    frm.show(lmap(get_income_median, all), ta=False, log=False, title="Income 12-month median")

def get_income(sym, value=100000, nis=False, per_month=True, smooth=12, net=True):
    start = None
    if is_series(sym):
        start = sym.index[0]
    prc = price(sym)
    if not start is None:
        prc = prc[start:]
    units = value / prc[0]
    div = divs(sym)
    if not start is None:
        div = div[start:]
    income = div * units
    if net:
        income *= 0.75
    if per_month:
        income = income.resample("M").sum()
        #income = income[income > 0]

        #interval = get_divs_interval(div)
        #income = ma(income, interval)
    else:
        income = income.resample("Y").sum()/12
    #income = income.replace(0, np.nan).interpolate()
#     if per_month:
#         income /= 12
    if nis:
        income = convertSeries(income, "USD", "ILS")
    if smooth:
        income = ma(income, smooth)
    return name(income, prc.name)

def get_cum_income(sym):
    income = get_income(sym, smooth=0)
    return income.cumsum()

def get_curr_yield_zscore(s):
    yld = get_yield_true(s)
    if yld is None:
        return None
    zs = zscore_modified(yld)
    if len(zs) == 0:
        return None
    return zs[-1]

def get_yield_live(s, type=None, reduce_fees=True, live=True, type_name=False):
    type = type or 'rolling'
    yld = get_yield(s, type=type, reduce_fees=reduce_fees, divs_only=live, do_resample=False).dropna()
    if yld.shape[0] == 0:
        return None
    prc = pr(s)
    yld = yld.reindex(prc.index)
    yld = yld.fillna(method='ffill')
    yld = yld / prc * 100
    if reduce_fees and get(s).name.mode != "PR":
        fees = get_meta_fee(s)
        yld -= fees
    if type_name:
        yld = name(yld, f"{get_name(s)} {type}")
    return yld.dropna()

def get_yield_live_all(s, reduce_fees=True):
    res = [get_yield_live(s, type='rolling', type_name=True, reduce_fees=reduce_fees),
           get_yield_live(s, type='normal', type_name=True, reduce_fees=reduce_fees),
           get_yield_live(s, type='true', type_name=True, reduce_fees=reduce_fees)]
    return res

def show_yield(*all, reduce_fees=False, detailed=True):
    ylds = lmap(partial(get_yield_live_all, reduce_fees=reduce_fees), all)
    ylds = flattenLists(ylds)
    ylds = [y for y in ylds if not y is None]
    title = f"{get_mode(all[0])} Yields {'with fees' if reduce_fees else 'no fees'}"
    if detailed and len(all) == 1 and len(ylds) > 0:
        s = all[0]
        dist = divs(s)
        dist = align_with(dist, ylds[0])
        prc = price(s)
        prc = align_with(prc, ylds[0])
        extra = [dist, prc]
        title=get_pretty_name_no_mode(s) + " " + title
    else:
        extra = []
    frm.show(0, *extra, ylds, lmap(last, ylds[2::3]), ta=False, log=False, title=title, sort=False)


"""this example demonstrates how to make a multiple
subplots of a VisualArray in multiple sides"""

import numpy as np
import matplotlib.pyplot as plt
from numpyviz import VisualArray

arr = np.array([[[1,2,13],
     [12,2,32],
     [61,2,6],
     [1,23,3],
     [1,21,3],
     [91,2,38]]])

fig = plt.figure()
ax = fig.add_subplot(1, 3, 1, projection='3d')
ax.set_title('a = array of shape (1, 6, 3)')
va = VisualArray(arr, fig=fig, ax=ax)
va.set_colors(np.array(list(np.ndindex(arr.shape))).T, color='yellow', basecolor='lightblue')
va.vizualize(fixview=True)

arr = arr[:,:,:2]
ax = fig.add_subplot(1, 3, 2, projection='3d')
ax.set_title('a = a[:,:,:2] \n'
             'a is of shape (1, 6, 2) now')
va = VisualArray(arr, fig=fig, ax=ax)
va.set_colors(np.array(list(np.ndindex(arr.shape))).T, color='yellow', basecolor='lightblue')
va.vizualize(fixview=True)

arr = arr.squeeze()
ax = fig.add_subplot(1, 3, 3, projection='3d')
ax.set_title('a = a.squeeze() \n'
             'a is of shape (6, 2) now')
va = VisualArray(arr, fig=fig, ax=ax)
va.set_colors(np.array(list(np.ndindex(va.arr.shape))).T, color='yellow', basecolor='lightblue')
va.vizualize(fixview=True, axis_labels=(None, 'axis=0', 'axis=1'))
plt.get_current_fig_manager().window.state('zoomed')
plt.show()