import * as Auth from "/services/auth/auth.js";
import * as ElectionModel from "/services/model/election-model.js";
import * as RoundModel from "/services/model/round-model.js";
import * as AreaModel from "/services/model/area-model.js";
import * as SectionModel from "/services/model/section-model.js";
import * as DeskModel from "/services/model/desk-model.js";
import * as PartyModel from "/services/model/party-model.js";
import * as DeskRoundModel from "/services/model/deskRound-model.js";
import * as VoteModel from "/services/model/vote-model.js";
import * as CandidateListModel from "/services/model/candidateList-model.js";

export async function mountCalculator(round) {
  const directMetropolitanCalculator = new DirectMetropolitanCalculator(round);
  await directMetropolitanCalculator.initModel();
  return directMetropolitanCalculator;
}

class DirectMetropolitanCalculator {
  constructor(round, filter) {
    this.round = round;
    this.ElectionModel = ElectionModel.getElectionModel();
    this.RoundModel = RoundModel.getRoundModel();
    this.AreaModel = AreaModel.getAreaModel();
    this.SectionModel = SectionModel.getSectionModel();
    this.DeskModel = DeskModel.getDeskModel();
    this.PartyModel = PartyModel.getPartyModel();
    this.DeskRoundModel = DeskRoundModel.getDeskRoundModel();
    this.VoteModel = VoteModel.getVoteModel();
    this.CandidateListModel = CandidateListModel.getCandidateListModel();
  }

  async initModel() {
    this.ElectionModel.current_user = await Auth.GetUser();
    this.RoundModel.current_user = await Auth.GetUser();
    this.AreaModel.current_user = await Auth.GetUser();
    this.SectionModel.current_user = await Auth.GetUser();
    this.DeskModel.current_user = await Auth.GetUser();
    this.PartyModel.current_user = await Auth.GetUser();
    this.DeskRoundModel.current_user = await Auth.GetUser();
    this.VoteModel.current_user = await Auth.GetUser();
    this.CandidateListModel.current_user = await Auth.GetUser();
  }

  async calculateResults(filter) {
    this.CandidateListModel.refreshCandidateLists();
    let calculator = this;
    this.filter = filter;
    this.deskRounds = await this.DeskRoundModel.getDeskRounds();
    this.deskRounds = this.deskRounds.filter(function (deskRound) {
      return deskRound.RoundID === calculator.round.ID;
    });
    this.candidateLists = await this.CandidateListModel.getCandidateLists();
    this.candidateLists = this.candidateLists.filter((candidateList) => {
      return candidateList.RoundID === calculator.round.ID;
    });

    this.stats = await this.calculateStats(this.deskRounds);

    let flag = true;
    switch (this.filter) {
      case "partial":
        if (this.stats.VotesExpressed === 0) {
          this.status = "no_results";
        } else {
          this.roundResults = await this.calculateRoundResults();
          this.status = "partial";
        }
        break;
      case "completed":
        flag = true;
        this.deskRounds.forEach((deskRound) => {
          if (!deskRound.Completed) flag = false;
        });
        if (flag) {
          this.roundResults = await this.calculateRoundResults();
          this.stats = await this.calculateStats(this.deskRounds);
          this.status = "completed";
        } else {
          this.roundResults = null;
          this.stats = null;
          this.status = "incompleted";
        }
        break;
      case "validated":
        flag = true;
        this.deskRounds.forEach((deskRound) => {
          if (!deskRound.Validated) flag = false;
        });
        if (flag) {
          this.roundResults = await this.calculateRoundResults();
          this.stats = await this.calculateStats(this.deskRounds);
          this.status = "validated";
        } else {
          this.roundResults = null;
          this.stats = null;
          this.status = "not validated";
        }
    }

    this.areasResults = await this.calculateAreasResults();
    return this;
  }

  async calculateRoundResults() {
    let partiesIDToKeep = [];
    this.candidateLists.forEach((candidateList) => {
      partiesIDToKeep.push(candidateList.PartyID);
    });
    partiesIDToKeep = partiesIDToKeep.filter(function (item, index) {
      return partiesIDToKeep.indexOf(item) >= index;
    });

    let parties = await this.PartyModel.getParties();
    parties = parties.filter((party) => partiesIDToKeep.includes(party.ID));
    parties.forEach((party) => {
      party.VoiceNumber = 0;
      let currentParty = party;
      this.candidateLists.forEach((candidateList) => {
        if (candidateList.PartyID == currentParty.ID) {
          currentParty.VoiceNumber = candidateList.Votes.reduce(
            (voiceNumber, vote) => {
              return voiceNumber + vote.VoiceNumber;
            },
            currentParty.VoiceNumber
          );
          currentParty.Percentage = Number(
            (currentParty.VoiceNumber / this.stats.VotesExpressed) * 100
          ).toFixed(2);
        }
      });
      party = currentParty;
    });
    parties.sort((a, b) => {
      return b.VoiceNumber - a.VoiceNumber;
    });
    return parties;
  }

  async calculateAreasResults() {
    let calculator = this;
    this.AreaModel.refreshAreas();
    let areas = await this.AreaModel.getAreas();
    areas = areas.filter(function (area) {
      return area.ElectionID == calculator.round.ElectionID;
    });

    let areasCalculated = [];
    for (let i in areas) {
      areasCalculated.push(await this.calculateAreaResults(areas[i]));
    }
    areas = areasCalculated;
    return areas;
  }

  async calculateAreaResults(area) {
    let deskRounds = [];
    for (let i in area.Sections) {
      let section = await this.SectionModel.getSection(area.Sections[i].ID);
      for (let j in section.Desks) {
        for (let k in this.deskRounds) {
          if (section.Desks[j].ID == this.deskRounds[k].DeskID)
            deskRounds.push(this.deskRounds[k]);
        }
      }
    }

    let candidateListToKeep = this.candidateLists.filter((candidateList) => {
      return candidateList.AreaID === area.ID;
    });

    let sections = [];
    for (let i in area.Sections) {
      sections.push(
        await this.calculateSectionResults(
          area.Sections[i],
          candidateListToKeep.map((a) => ({ ...a }))
        )
      );
    }
    area.Sections = sections;

    let lastDate = new Date(area.Sections[0].DateCompletion);
    area.Sections.forEach((section) => {
      if (lastDate - new Date(section.DateCompletion) < 0)
        lastDate = new Date(section.DateCompletion);
    });
    area.DateCompletion = lastDate;

    area.stats = await this.calculateStats(deskRounds);

    let flag = true;
    switch (this.filter) {
      case "partial":
        if (area.stats.VotesExpressed === 0) {
          area.status = "no_results";
          return area;
        }
        area.status = "partial";
        break;
      case "completed":
        flag = true;
        deskRounds.forEach((deskRound) => {
          if (!deskRound.Completed) flag = false;
        });
        if (flag) {
          area.status = "completed";
        } else {
          area.status = "incompleted";
          return area;
        }
        break;
      case "validated":
        flag = true;
        deskRounds.forEach((deskRound) => {
          if (!deskRound.Validated) flag = false;
        });
        if (flag) {
          area.status = "validated";
        } else {
          area.status = "not validated";
          return area;
        }
    }

    candidateListToKeep.forEach((candidateList) => {
      candidateList.VoiceNumber = candidateList.Votes.reduce(
        (voiceNumber, vote) => {
          return voiceNumber + vote.VoiceNumber;
        },
        0
      );
      candidateList.Percentage = Number(
        (candidateList.VoiceNumber / area.stats.VotesExpressed) * 100
      ).toFixed(2);
    });
    area.candidateLists = candidateListToKeep;
    area.candidateLists.sort((a, b) => {
      return b.VoiceNumber - a.VoiceNumber;
    });

    area.Electeds = this.getElecteds(area);

    area.candidateLists.sort((a, b) => {
      return b.VoiceNumber - a.VoiceNumber;
    });

    return area;
  }

  async calculateSectionResults(section, candidateLists) {
    section = await this.SectionModel.getSection(section.ID);
    let deskRounds = [];
    for (let i in section.Desks) {
      for (let j in this.deskRounds) {
        if (section.Desks[i].ID == this.deskRounds[j].DeskID)
          deskRounds.push(this.deskRounds[j]);
      }
    }

    let lastDate = new Date(deskRounds[0].DateCompletion);
    deskRounds.forEach((desk) => {
      if (lastDate - new Date(desk.DateCompletion) < 0)
        lastDate = new Date(desk.DateCompletion);
    });
    section.DateCompletion = lastDate;

    section.stats = await this.calculateStats(deskRounds);
    let flag = true;
    switch (this.filter) {
      case "partial":
        if (section.stats.VotesExpressed === 0) {
          section.status = "no_results";
          return section;
        }
        section.status = "partial";
        break;
      case "completed":
        flag = true;
        deskRounds.forEach((deskRound) => {
          if (!deskRound.Completed) flag = false;
        });
        if (flag) {
          section.status = "completed";
        } else {
          section.status = "incompleted";
          return section;
        }
        break;
      case "validated":
        flag = true;
        deskRounds.forEach((deskRound) => {
          if (!deskRound.Validated) flag = false;
        });
        if (flag) {
          section.status = "validated";
        } else {
          section.status = "not validated";
          return section;
        }
    }

    candidateLists.forEach((candidateList) => {
      candidateList.Votes = candidateList.Votes.filter((vote) => {
        return deskRounds
          .map((deskRound) => deskRound.ID)
          .includes(vote.DeskRoundID);
      });
      candidateList.VoiceNumber = candidateList.Votes.reduce(
        (voiceNumber, vote) => {
          return voiceNumber + vote.VoiceNumber;
        },
        0
      );
      candidateList.Percentage = Number(
        (candidateList.VoiceNumber / section.stats.VotesExpressed) * 100
      ).toFixed(2);
    });
    section.candidateLists = candidateLists;
    section.candidateLists.sort((a, b) => {
      return b.VoiceNumber - a.VoiceNumber;
    });
    return section;
  }

  async calculateStats(deskRounds) {
    let subscribed = 0;
    let blank = 0;
    let nullVote = 0;
    let totalVotes = 0;
    let VotesExpressed = 0;

    for (let i in deskRounds) {
      let desk = await this.DeskModel.getDesk(deskRounds[i].DeskID);
      subscribed += desk.Subscribed;
      deskRounds[i].Votes.forEach((vote) => {
        totalVotes += vote.VoiceNumber;
        if (vote.Blank) blank += vote.VoiceNumber;
        else if (vote.NullVote) nullVote += vote.VoiceNumber;
        else VotesExpressed += vote.VoiceNumber;
      });
    }
    return {
      Abstention: Number(
        ((subscribed - totalVotes) / subscribed) * 100
      ).toFixed(2),
      Blank: Number((blank / totalVotes) * 100).toFixed(2),
      NullVote: Number((nullVote / totalVotes) * 100).toFixed(2),
      VotesExpressed: VotesExpressed,
    };
  }

  getElecteds(area) {
    let electeds = [];

    // order candidates by rank and remove refused or removed candidates
    area.candidateLists.forEach((candidateList) => {
      candidateList.Candidates.sort(function (a, b) {
        return a.Rank - b.Rank;
      });
      for (let i = 0; i < candidateList.Candidates.length; i++) {
        if (
          candidateList.Candidates[i].Refused ||
          candidateList.Candidates[i].Removed
        ) {
          candidateList.Candidates.splice(i, 1);
        }
      }
    });

    // première étape
    let seatForFirst = parseInt(area.SeatNumber / 2);
    if ((parseInt(area.seatNumber) / 2) % 2 != 0) seatForFirst += 1;
    electeds = electeds.concat(
      area.candidateLists[0].Candidates.splice(0, seatForFirst)
    );

    // deuxième étape
    let leftSeats = area.SeatNumber - seatForFirst;
    let electoralQuotien = area.stats.VotesExpressed / leftSeats;

    area.candidateLists.forEach((candidateList) => {
      let seatsAttributed = parseInt(
        candidateList.VoiceNumber / electoralQuotien
      );
      candidateList.SeatsAttributed = seatsAttributed;
      leftSeats -= seatsAttributed;
      if (seatsAttributed > 0) {
        electeds = electeds.concat(
          candidateList.Candidates.splice(0, seatsAttributed)
        );
      }
    });

    // //troisème étape
    var day = new Date();
    while (leftSeats > 0) {
      area.candidateLists.forEach((candidateList) => {
        candidateList.Average =
          candidateList.VoiceNumber /
          (parseInt(candidateList.SeatsAttributed) + 1);
      });
      area.candidateLists.sort(function (a, b) {
        return b.Average - a.Average;
      });
      if (area.candidateLists[0].Average === area.candidateLists[1].Average) {
        if (area.candidateLists[1].vote > area.candidateLists[0].vote)
          [area.candidateLists[0], area.candidateLists[1]] = [
            area.candidateLists[1],
            area.candidateLists[0],
          ];
        if (area.candidateLists[0].vote == area.candidateLists[1].vote) {
          if (
            area.candidateLists[0].Candidates[0].Birthdate == "" ||
            area.candidateLists[1].Candidates[0].Birthdate == ""
          ) {
            area.errorAgeAverage = true;
          }
          ageCandidate1 = ageCount(
            day,
            new Date(area.candidateLists[0].Candidates[0].Birthdate)
          );
          ageCandidate2 = ageCount(
            day,
            new Date(area.candidateLists[1].Candidates[0].Birthdate)
          );
          if (ageCandidate2 > ageCandidate1) {
            [area.candidateLists[0], area.candidateLists[1]] = [
              area.candidateLists[1],
              area.candidateLists[0],
            ];
          }
        }
      }
      electeds = electeds.concat(
        area.candidateLists[0].Candidates.splice(0, 1)
      );
      area.candidateLists[0].SeatsAttributed += 1;
      leftSeats -= 1;
    }

    return electeds;
  }
}