using Core.Entities;
using Google.OrTools.Sat;

namespace Core.Calculation;
public class TeamScheduler
{
	private readonly IList<Student> _studentObjs;
	private readonly IList<Team> _teamObjs;

	private readonly int[] _students;
	private readonly int[] _teams;
	private readonly int[] _timeSlots;

	private readonly List<Tuple<int,int>> _scheduleSeparateTeams = new ();

	public TeamScheduler(IList<Team>  teams, int numTimeSlots)
	{
		_teamObjs = teams;
		_studentObjs = teams.SelectMany(t => t.Students).Distinct().ToList();

		_students = Enumerable.Range(0, _studentObjs.Count).ToArray();
		_teams = Enumerable.Range(0, _teamObjs.Count).ToArray();
		_timeSlots = Enumerable.Range(0, numTimeSlots).ToArray();
	}

	public void ScheduleSeparate(Team team1, Team team2)
	{
		var one = _teamObjs.IndexOf(team1);
		var two = _teamObjs.IndexOf(team2);
		_scheduleSeparateTeams.Add(Tuple.Create(one,two));
	}

	public static TeamScheduler CreateInstance(IList<Team> teams, int numTimeSlots)
	{
		return new TeamScheduler(teams, numTimeSlots);
	}

	public IList<Team>[] Solve()
	{
		// Model.
		var model = new CpModel();

		// Data
		var m = new int[_students.Length,_teams.Length];
		foreach (var i in _students)
		foreach (var t in _teams)
			m[i, t] = _studentObjs[i].Teams.Contains(_teamObjs[t]) ? 1 : 0;

		// Variables.
		// x - 1 if meeting of team t takes place at time slot s, else 0
		var x = new IntVar[_teams.Length, _timeSlots.Length];
		foreach (var t in _teams)
		foreach (var s in _timeSlots)
			x[t, s] = model.NewIntVar(0, 1,$"team time slots[{t},{s}]");

		// y - 1 if individual i has meetings at time slot s, 0 otherwise
		var y = new IntVar[_students.Length, _timeSlots.Length];
		foreach (var i in _students)
		foreach (var s in _timeSlots)
			y[i, s] = model.NewIntVar(0, 1, $"individual time slots[{i},{s}]");

		// each team meets exactly one time
		foreach (var t in _teams)
			model.AddLinearConstraint(LinearExpr.Sum(_timeSlots.Select(s => x[t, s])), 1L, 1L);
		
		// individual must have at least one team meeting at the given time slot to attend
		foreach (var i in _students)
		foreach (var s in _timeSlots)
			model.Add(
				new BoundedLinearExpression(
					y[i, s],
					LinearExpr.Sum(_teams.Select(t => m[i, t] * x[t, s])),
					false));

		// maximize number of times individuals meet
		var indTimeSlotVars = LinearExpr.NewBuilder();
		foreach (var i in _students)
		foreach (var s in _timeSlots)
			indTimeSlotVars.Add(y[i, s]);
		model.Minimize(indTimeSlotVars);

		foreach (var ts in _scheduleSeparateTeams)
		foreach (var s in _timeSlots)
			model.Add(x[ts.Item1, s] != x[ts.Item2, s]);
			//model.Add(
			//	new BoundedLinearExpression(
			//		x[ts.Item1, s],
			//		x[ts.Item2, s],
			//		false));

		var solver = new CpSolver();
		
		var cpSolverStatus = solver.Solve(model);
		Console.WriteLine($"Solver status: {cpSolverStatus}");

		if (cpSolverStatus is CpSolverStatus.Optimal or CpSolverStatus.Feasible)
		{
			Console.WriteLine($"Total cost: {solver.ObjectiveValue}\n");

			//foreach (var t in _teams)
			//foreach (var s in _timeSlots)
			//{
			//	if (solver.Value(x[t, s]) > 0)
			//		Console.WriteLine($"{_teamObjs[t].Name} : {s}");
			//}

			//foreach (var i in _students)
			//foreach (var s in _timeSlots)
			//{
			//	if (solver.Value(y[i, s]) > 0)
			//		Console.WriteLine($"{_studentObjs[i].Name} : {s}");
			//}

			var timeSlotTeams = new List<Team>[_timeSlots.Length];
			foreach (var s in _timeSlots)
			{
				var teams = new List<Team>();
				foreach (var t in _teams)
				{
					if (solver.Value(x[t, s]) > 0)
					{
						teams.Add(_teamObjs[t]); 
					}
				}
				timeSlotTeams[s] = teams;
			}
			return timeSlotTeams;
		}
		else
		{
			//Console.WriteLine("No solution found.");
			return Array.Empty<IList<Team>>();
		}

	}
}