include "globals.mzn";

%%---------Input------------------------------------------------------------------------------------------------------------------------------------------

int: l; %scheduling horizon
set of int: TIMES = 0..l;
int: a; %number of attributes
set of int: ATTRIBUTES = 1..a;
array[1..a+1,1..a] of int: setup_costs; %setup_costs[i,j] costs for switching from attribute i to attribute j
array[1..a+1,1..a] of int: setup_times; %setup_times[i,j] time for switching from attribute i to attribute j
int: m; %number of machines
set of int: MACHINES = 1..m;
array[MACHINES] of int: min_cap; %minimum capacity per machine
array[MACHINES] of int: max_cap; %maximum capacity per machine
array[MACHINES] of int: initState; %initial state (=attribute) of every machine
int: s; %maximum number of shifts per machine
array[MACHINES,1..s] of TIMES: m_a_s; %machine availability start times
array[MACHINES,1..s] of TIMES: m_a_e; %machine availability end times
int: n; %number of jobs
array[1..n] of set of MACHINES: eligible_machine; %eligible_machine[i] contains j <-> job i can be executed on machine j
array[1..n] of int: earliest_start; %earliest start date for every job
array[1..n] of int: latest_end; %latest end date for every job
array[1..n] of int: min_time; %minimum time in machine for every job
array[1..n] of int: max_time; %maximum time in machine for every job
array[1..n] of int: size; %occupied space in machine for every job
array[1..n] of int: attribute; %attribute of every job

%new
%constants needed for the calculation of the objective function
int: upper_bound_integer_objective;
int: mult_factor_total_runtime;
int: mult_factor_finished_toolate;
int: mult_factor_total_setuptimes;
int: mult_factor_total_setupcosts;
int: running_time_bound;
int: min_duration;
int: max_duration;
int: max_setup_time;
int: max_setup_cost;
%end new


%%---------Decision variables-----------------------------------------------------------------------------------------------------------------------------

%we do not know how many batches there will be (per machine and in total), we thus create n batches and leave some empty
array[1..n] of var 1..n: batch_for_job; %batch[i]=j <-> job i is scheduled in batch j on some machine
array[1..n] of var MACHINES: machine_for_job; 
array[1..n*m] of var TIMES: start_times; %start_times[ba +n*(ma-1)] is start time of batch ba on machine ma
array[MACHINES, 1..n] of var 0..max_duration: duration; %duration[i] is the duration of i-th batch
 

%helper variables
array[MACHINES, 1..n] of var ATTRIBUTES: attribute_for_batch; %attribute_for_batch[i] is attribute of i-th batch
array[MACHINES] of var 0..n: batches_per_machine;
var 0..n: b = sum(i in MACHINES)(batches_per_machine[i]); %total number of batches that are used
array[MACHINES, 1..n] of var 1..s: shift_for_batch; %shift on machine for which batch is scheduled

%%---------Constraints------------------------------------------------------------------------------------------------------------------------------------

constraint forall (j in 1..n)(   
  batches_per_machine[machine_for_job[j]] >= batch_for_job[j]
);


%empty batches
constraint forall(ma in MACHINES, i in 1..n where i > batches_per_machine[ma])(
  start_times[i +n*(ma-1)]=l
  /\ 
  duration[ma, i]=0
  /\
  attribute_for_batch[ma,i] = 1
  /\
  shift_for_batch[ma,i] = s
); 


constraint forall (i in 1..n)( %jobs must be scheduled to batches with matching attributes
let {
  var 1..n: ba = batch_for_job[i];
  var MACHINES: ma = machine_for_job[i]
} in
  attribute_for_batch[ma,ba]=attribute[i]
);

constraint forall (i in 1..n)( %jobs must be scheduled to eligible machines
  exists(ma in eligible_machine[i])(ma = machine_for_job[i])
%   machine_for_job[i] in eligible_machine[i]
);

%duration of batch is given by jobs scheduled for this batch
%set duration of batch to maximal min_time of job in batch
constraint forall (machine in MACHINES where batches_per_machine[machine]>0, i in 1..n where i <= batches_per_machine[machine])(
  duration[machine,i] = max(j in 1..n where batch_for_job[j]=i /\ machine_for_job[j] = machine)(min_time[j])
);

constraint forall (machine in MACHINES where batches_per_machine[machine]>0, i in 1..n where machine_for_job[i]=machine)(
  duration[machine, batch_for_job[i]] <= max_time[i]
);

%batch cannot start before earliest start of job in it
constraint forall (j in 1..n)(
  start_times[batch_for_job[j]+n*(machine_for_job[j]-1)] >= earliest_start[j]
);
%start time of batch is given by start time and duration of previous batch and setup times between batches
constraint forall (ma in MACHINES where batches_per_machine[ma]>1, i in 1..n-1 where i < batches_per_machine[ma])(
    start_times[i+1+n*(ma -1)] >= start_times[i+n*(ma -1)]+duration[ma,i] + setup_times[attribute_for_batch[ma,i],attribute_for_batch[ma,i+1]]
);


%shift for batch
constraint forall (ma in MACHINES, i in 1..n where i <= batches_per_machine[ma])( 
  shift_for_batch[ma,i] = max(j in 1..s where start_times[i+n*(ma -1)] >= m_a_s[ma,j])(j)
); 

%job processing time of batch i on machine ma must lie within one shift on machine ma
%not first batch on machine
constraint forall (ma in MACHINES where  batches_per_machine[ma]>1, i in 2..n where i <= batches_per_machine[ma])( 
   start_times[i+n*(ma -1)] + duration[ma,i] <= m_a_e[ma,shift_for_batch[ma,i]]
     /\
    start_times[i+n*(ma -1)] - setup_times[attribute_for_batch[ma,i-1],attribute_for_batch[ma,i]] >=  m_a_s[ma,shift_for_batch[ma,i]]
);
%first batch on machine 
% leave enough time for setup from initial state of machine
constraint forall (ma in MACHINES where batches_per_machine[ma] > 0)( 
  start_times[1+n*(ma -1)] + duration[ma,1] <= m_a_e[ma,shift_for_batch[ma,1]]
  /\
  start_times[1+n*(ma -1)]  - setup_times[initState[ma],attribute_for_batch[ma,1]] >=  m_a_s[ma,shift_for_batch[ma,1]]
);


%total batch size must be less than max machine capacity
constraint forall (ma in MACHINES where batches_per_machine[ma]>0, i in 1..n where i <= batches_per_machine[ma])(
  sum(j in 1..n where machine_for_job[j] = ma /\ batch_for_job[j] = i)(size[j]) <= max_cap[ma]
);

%%---------Objective--------------------------------------------------------------------------------------------------------------------------------------

var min_duration*a..running_time_bound: running_time_oven :: output_var;

running_time_oven = sum(ma in MACHINES, i in 1..n)(duration[ma, i]); 

var 0..n: finished_too_late :: output_var;
finished_too_late = 
  count(ma in MACHINES, i in 1..n, j in 1..n)(
    i <= batches_per_machine[ma]
    /\ machine_for_job[j] = ma
    /\ batch_for_job[j] = i
    /\ start_times[i+n*(ma -1)] + duration[ma,i] > latest_end[j]
  );
  



var 0..n*max_setup_cost: total_setup_costs :: output_var;
total_setup_costs = sum(ma in MACHINES where batches_per_machine[ma]>1, 
ba in 1..n-1 where ba < batches_per_machine[ma])
  (setup_costs[attribute_for_batch[ma,ba],attribute_for_batch[ma,ba+1]])  
  +
  %for first batch on machine
  sum(ma in MACHINES where batches_per_machine[ma]>0)
  (setup_costs[initState[ma],attribute_for_batch[ma,1]]);

   
var 0..upper_bound_integer_objective: objective :: output_var;
objective = 
  mult_factor_total_runtime * running_time_oven
  + mult_factor_finished_toolate * finished_too_late

  + mult_factor_total_setupcosts * total_setup_costs;


%%%% Output %%%%%
output ["objective = \(objective);\n",
"running_time_oven = \(running_time_oven);\n",

"total_setup_costs = \(total_setup_costs);\n",
"finished_too_late = \(finished_too_late);\n",
"Total number of batches: \(b);\n",
"Number of batches per machine: \(batches_per_machine);\n",
"Jobs are scheduled for following batches: \(batch_for_job);\n",
"Jobs are scheduled for following machines: \(machine_for_job);\n",
"Start times of batches are: \(start_times);\n",
"Durations of batches are: \(duration);\n",
%"Setup times between batches: \(setup_times_between_batches);\n",
%"Setup costs between batches: \(setup_costs_between_batches);\n",
"Attribute of batch: \(attribute_for_batch);\n",
"The shift chosen for batch is: \(shift_for_batch);\n"
,"upper_bound_integer_objective: \(upper_bound_integer_objective);\n"
];
