#-------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#-------------------------------------------------------------

# This script computes a solution for the stable marriage problem.
#
# result description:
#
# If cell [i,j] is non-zero, it means that acceptor i has matched with
# proposer j. Further, if cell [i,j] is non-zero, it holds the preference
# value that led to the match.
# Proposers.mtx:
# 2.0,1.0,3.0
# 1.0,2.0,3.0
# 1.0,3.0,2.0
# 
# Since ordered=TRUE, this means that proposer 1 (row 1) likes acceptor 2
# the most, followed by acceptor 1 and acceptor 3.
# If ordered=FALSE, this would mean that proposer 1 (row 1) likes acceptor 3
# the most (since the value at [1,3] is the row max),
# followed by acceptor 1 (2.0 preference value) and acceptor 2 (1.0 preference value).
# 
# Acceptors.mtx:
# 3.0,1.0,2.0
# 2.0,1.0,3.0
# 3.0,2.0,1.0
# 
# Since ordered=TRUE, this means that acceptor 1 (row 1) likes proposer 3
# the most, followed by proposer 1 and proposer 2.
# If ordered=FALSE, this would mean that acceptor 1 (row 1) likes proposer 1
# the most (since the value at [1,1] is the row max),
# followed by proposer 3 (2.0 preference value) and proposer 2
# (1.0 preference value).
# 
# Output.mtx (assuming ordered=TRUE):
# 0.0,0.0,3.0
# 0.0,3.0,0.0
# 1.0,0.0,0.0
# 
# Acceptor 1 has matched with proposer 3 (since [1,3] is non-zero) at a
# preference level of 3.0.
# Acceptor 2 has matched with proposer 2 (since [2,2] is non-zero) at a
# preference level of 3.0.
# Acceptor 3 has matched with proposer 1 (since [3,1] is non-zero) at a
# preference level of 1.0.
#
# INPUT:
# ----------------------------------------------------------------------------------
# P        proposer matrix P.
#          It must be a square matrix with no zeros.
# A        acceptor matrix A.
#          It must be a square matrix with no zeros.
# ordered  If true, P and A are assumed to be ordered,
#          i.e. the leftmost value in a row is the most preferred partner's index.
#          i.e. the leftmost value in a row in P is the preference value for the acceptor with
#          index 1 and vice-versa (higher is better).
# verbose  if the algorithm should print verbosely
# ----------------------------------------------------------------------------------
#
# OUTPUT:
# ----------------------------------------------------------------------------------------
# result_matrix  Result Matrix
# ----------------------------------------------------------------------------------------

m_stableMarriage = function(Matrix[Double] P, Matrix[Double] A, Boolean ordered = TRUE, Boolean verbose = FALSE)
  return (Matrix[Double] result_matrix)
{
  # variable names follow publication

  print("STARTING STABLE MARRIAGE");
  assert(nrow(A) == nrow(P));
  assert(ncol(A) == ncol(P));

  if(nrow(P) != ncol(P) | nrow(A) != ncol(A))
    stop("StableMarriage error: Wrong Input! Both P and A must be square.")

  n = nrow(P)
  # Let S be the identity matrix
  S = diag(matrix(1.0, rows=n, cols=1))
  result_matrix = matrix(0.0, rows=n, cols=n)
  # Pre-processing
  if(!ordered) {
    # If unordered, we need to order P
    ordered_P = matrix(0.0, rows=n, cols=n)
    transposed_P = t(P)

    parfor(i in 1:n)
      ordered_P[,i] = order(target=transposed_P, by=i, decreasing=TRUE, index.return=TRUE)
    P = t(ordered_P)
  }
  else {
    # If ordered, we need to unorder A
    unordered_A = matrix(0.0, rows=n, cols=n)
    # Since cells can be converted to unordered indices independently, we can nest parfor loops.
    parfor(i in 1:n) {
      parfor(j in 1:n, check=0) 
        unordered_A[i, as.scalar(A[i, j])] = n - j + 1
    }
    A = unordered_A
  }

  proposer_pointers = matrix(1.0, rows=n, cols=1)

  while(sum(S) > 0) {
    stripped_preferences = S %*% P
    mask_matrix = matrix(0.0, rows=n, cols=n)
    for(i in 1:n) {
      max_proposal = as.scalar(stripped_preferences[i, as.scalar(proposer_pointers[i])])
      if(max_proposal != 0) {
        proposer_pointers[i] = as.scalar(proposer_pointers[i]) + 1
        mask_matrix[max_proposal, i] = 1
      }
    }
    # make Hadamard Product
    Propose_round_results = mask_matrix * A
    best_proposers_vector = rowIndexMax(Propose_round_results)
    prev_best_proposers = rowIndexMax(result_matrix)

    for(i in 1:n, check=0) {
      new_best_proposer_index = as.scalar(best_proposers_vector[i])
      new_best = as.scalar(Propose_round_results[i, new_best_proposer_index])

      if(new_best > 0) {
        prev_best_proposer_index = as.scalar(prev_best_proposers[i])
        prev_best = as.scalar(result_matrix[i, prev_best_proposer_index])

        if (new_best > prev_best)
        {
          # Proposal is better than current fiance
          result_matrix[i, prev_best_proposer_index] = 0
          result_matrix[i, new_best_proposer_index] = new_best

          #Disable freshly married man to search for a new woman in the next round
          S[new_best_proposer_index, new_best_proposer_index] = 0

          # If a fiance existed, dump him/her
          if(prev_best > 0)
            S[prev_best_proposer_index, prev_best_proposer_index] = 1
        }
      }
    }
  }
  
  if(verbose)
    print("Result: \n"+toString(result_matrix))
}
