package jp.ac.takushoku_u.cs;

/**
* TaskSequenceクラスは学習課題系列化法を行うためのクラスです。<BR>
* 系列化を行う場合に仮想項目が必要である課題構造の場合であっても、
* 自動的に仮想項目を付与し、系列化を行うことができます。
* 仮想項目は0行目および0列目に付与され、行名と列名はいずれも`Dummy'となります。
* @version 1.1.1
* @author 笠島 裕史(Kasajima Hiroshi)
*/
/*
* 最終更新日 2004年12月06日
*/
public class TaskSequence{
	
/**
* 系列化を行う隣接マトリクス(仮想項目なし)
*/
	private ArithmeticMatrix baseMatrix;
/**
* 系列化を行う隣接マトリクス(仮想項目付)
*/	
	private ArithmeticMatrix matrix;
	
/**
* 仮想項目を付与したか示す変数
*/
	private boolean giveDummy = false;
	
/**
* アルファの初期値
*/
	private static final double TASK_SEQUENCE_ALPHA = 0.5;
	
/**
* ベータの初期値
*/
	private static final double TASK_SEQUENCE_BETA = 0.5;
	
/**
* 有向枝があることを示す定数
*/
	private static final double TASK_SEQUENCE_TRUE = 1.0;
	
/**
* 有向枝がないことを示す定数
*/
	private static final double TASK_SEQUENCE_FALSE = 0.0;
	
/**
* マトリクスの初期値
*/
	private static final int TASK_SEQUENCE_MATRIX_SIZE = 5;
	
/**
* デフォルトで設定されている値で系列化を行う隣接マトリクスを設定します。
*/
	public TaskSequence(){
		baseMatrix = new ArithmeticMatrix(TASK_SEQUENCE_MATRIX_SIZE, TASK_SEQUENCE_MATRIX_SIZE);
		matrix = new ArithmeticMatrix(TASK_SEQUENCE_MATRIX_SIZE, TASK_SEQUENCE_MATRIX_SIZE);
	}
	
/**
* 系列化を行う隣接マトリクスを設定します。
* @param adjacencyMatrix 隣接マトリクス
* @exception NotSquareMatrixException 隣接マトリクスが正方行列ではない場合
*/
	public TaskSequence(ArithmeticMatrix adjacencyMatrix){
		baseMatrix = new ArithmeticMatrix(adjacencyMatrix.getRowLength(), adjacencyMatrix.getColLength());
		matrix = new ArithmeticMatrix(adjacencyMatrix.getRowLength(), adjacencyMatrix.getColLength());
		setMatrix(adjacencyMatrix);
	}
	
	
/**
* 元の隣接マトリクスを返します。
* @return 隣接マトリクス
* @see #getDummyMatrix()
*/
	public ArithmeticMatrix getMatrix(){
		return baseMatrix;
	}
	
/**
* 仮想項目込みの隣接マトリクスを返します。
* 仮想項目を付与しなくともよい隣接マトリクスの場合はgetMatrix()と同じマトリクスが返されます。
* @return 仮想項目を付与した隣接マトリクス
* @see #getMatrix()
*/
	public ArithmeticMatrix getDummyMatrix(){
		return matrix;
	}
	
/**
* 可到達マトリクスを返します。
* 仮想項目が必要な隣接マトリクスを設定した場合、仮想項目込みのマトリクスが返されます。
* @return 可到達マトリクス
*/
	public ArithmeticMatrix getReachMatrix(){
		return getReachMatrix(matrix);
	}
	
/**
* 隣接マトリクスから可到達マトリクスを返します。
* @param adjacencyMatrix 隣接マトリクス
* @return 可到達マトリクス
*/
	private ArithmeticMatrix getReachMatrix(ArithmeticMatrix adjacencyMatrix){
		ArithmeticMatrix returnMatrix = new ArithmeticMatrix(adjacencyMatrix.getRowLength(),
			adjacencyMatrix.getColLength(), adjacencyMatrix.getRowNameArray(), adjacencyMatrix.getColNameArray());
		return adjacencyMatrix.getReachMatrix();
	}
	
/**
* 合前提性マトリクスを返します。
* 仮想項目が必要な隣接マトリクスを設定した場合、仮想項目込みのマトリクスが返されます。
* @return 合前提性マトリクス
*/
	public ArithmeticMatrix getFMatrix(){
		ArithmeticMatrix reachMatrix = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
			matrix.getRowNameArray(), matrix.getColNameArray());
		reachMatrix.matrixCopy(getReachMatrix(matrix));
		
		return getFMatrix(reachMatrix);
	}
	
/**
* 可到達マトリクスから合前提性マトリクスを返します。
* @param reachMatrix 可到達マトリクス
* @return 合前提性マトリクス
*/
	private ArithmeticMatrix getFMatrix(ArithmeticMatrix reachMatrix){
		ArithmeticMatrix returnMatrix = new ArithmeticMatrix(reachMatrix.getRowLength(), reachMatrix.getColLength(),
			reachMatrix.getRowNameArray(), reachMatrix.getColNameArray());
		ArithmeticMatrix TMatrix = new ArithmeticMatrix(reachMatrix.getRowLength(), reachMatrix.getColLength(),
			reachMatrix.getRowNameArray(), reachMatrix.getColNameArray());
		
		TMatrix.matrixCopy(reachMatrix.getTransposedMatrix());
		returnMatrix.matrixCopy(TMatrix.getMultiplyMatrix(reachMatrix));
		return returnMatrix;
	}
	
/**
* 合目標性マトリクスを返します。
* 仮想項目が必要な隣接マトリクスを設定した場合、仮想項目込みのマトリクスが返されます。
* @return 合目標性マトリクス
*/
	public ArithmeticMatrix getGMatrix(){
		ArithmeticMatrix reachMatrix = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
			matrix.getRowNameArray(), matrix.getColNameArray());
		reachMatrix.matrixCopy(getReachMatrix(matrix));
		
		return getGMatrix(reachMatrix);
	}
	
/**
* 可到達マトリクスから合目標性マトリクスを返します。
* @param reachMatrix 可到達マトリクス
* @return 合目標性マトリクス
*/
	private ArithmeticMatrix getGMatrix(ArithmeticMatrix reachMatrix){
		ArithmeticMatrix returnMatrix = new ArithmeticMatrix(reachMatrix.getRowLength(), reachMatrix.getColLength(),
			reachMatrix.getRowNameArray(), reachMatrix.getColNameArray());
		ArithmeticMatrix TMatrix = new ArithmeticMatrix(reachMatrix.getRowLength(), reachMatrix.getColLength(),
			reachMatrix.getRowNameArray(), reachMatrix.getColNameArray());
		
		TMatrix.matrixCopy(reachMatrix.getTransposedMatrix());
		returnMatrix.matrixCopy(reachMatrix.getMultiplyMatrix(TMatrix));
		return returnMatrix;
	}
	
/**
* ある2項目間の合前提性を返します。
* 仮想項目が必要な隣接マトリクスを設定した場合、仮想項目を考慮した値が返されます。
* 仮想項目を設定する必要のある隣接マトリクスの場合、
* 隣接マトリクスの行番号とvi、vjの番号が一致しないため注意が必要です。
* @param vi 既選択項目vi
* @param vj 選択項目vj
* @return 合前提性
* @exception NotElementException 指定された点が存在しない場合
*/

	public double getF(int vi, int vj){
		
		ArithmeticMatrix mat = new ArithmeticMatrix();
		mat.matrixCopy(getFMatrix());
		return mat.getCell(vi, vj);		//存在しない場合は例外
	}
	
/**
* ある2項目間の合目標性を返します。
* 仮想項目が必要な隣接マトリクスを設定した場合、仮想項目を考慮した値が返されます。
* 仮想項目を設定する必要のある隣接マトリクスの場合、
* 隣接マトリクスの行番号とvi、vjの番号が一致しないため注意が必要です。
* @param vi 既選択項目vi
* @param vj 選択項目vj
* @return 合目標性
* @exception NotElementException 指定された点が存在しない場合
*/
	public double getG(int vi, int vj){
		
		ArithmeticMatrix mat = new ArithmeticMatrix();
		mat.matrixCopy(getGMatrix());
		return mat.getCell(vi, vj);		//存在しない場合は例外
	}
	
/**
* コヒーレンス性マトリクスを返します。
* 返されるコヒーレンス性マトリクスは、合前提性マトリクス + 合目標性マトリクスです。
* 仮想項目が必要な隣接マトリクスを設定した場合、仮想項目込みのマトリクスが返されます。
* @return コヒーレンス性マトリクス
*/
	public ArithmeticMatrix getCoherenceMatrix(){
		ArithmeticMatrix FMatrix = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
			matrix.getRowNameArray(), matrix.getColNameArray());
		ArithmeticMatrix GMatrix = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
			matrix.getRowNameArray(), matrix.getColNameArray());
		FMatrix.matrixCopy(getFMatrix());
		GMatrix.matrixCopy(getGMatrix());
		return getCoherenceMatrix(FMatrix, GMatrix);
	}
	
/**
* コヒーレンス性マトリクスを返します。
* 返されるコヒーレンス性マトリクスは、合前提性マトリクス + 合目標性マトリクスです。
* 仮想項目が必要な隣接マトリクスを設定した場合、仮想項目込みのマトリクスが返されます。
* @param alpha アルファの値
* @param beta ベータの値
* @return コヒーレンス性マトリクス
*/
	public ArithmeticMatrix getCoherenceMatrix(double alpha, double beta){
		ArithmeticMatrix FMatrix = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
			matrix.getRowNameArray(), matrix.getColNameArray());
		ArithmeticMatrix GMatrix = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
			matrix.getRowNameArray(), matrix.getColNameArray());
		FMatrix.matrixCopy(getFMatrix());
		GMatrix.matrixCopy(getGMatrix());
		return getCoherenceMatrix(FMatrix, GMatrix, alpha, beta);
	}
	
/**
* 合前提性マトリクスと合目標性マトリクスからコヒーレンス性マトリクスを求めます。
* 返されるコヒーレンス性マトリクスは、合前提性マトリクス + 合目標性マトリクスです。
* @param FMatrix 合前提性マトリクス
* @param GMatrix 合目標性マトリクス
* @return コヒーレンス性マトリクス
* 
*/
	private ArithmeticMatrix getCoherenceMatrix(ArithmeticMatrix FMatrix, ArithmeticMatrix GMatrix){
		return FMatrix.getAddMatrix(GMatrix);
	}
	
/**
* 合前提性マトリクスと合目標性マトリクスからコヒーレンス性マトリクスを求めます。
* 返されるコヒーレンス性マトリクスは、合前提性マトリクス + 合目標性マトリクスです。
* @param FMatrix 合前提性マトリクス
* @param GMatrix 合目標性マトリクス
* @param alpha アルファの値
* @param beta ベータの値
* @return コヒーレンス性マトリクス
* 
*/
	private ArithmeticMatrix getCoherenceMatrix(ArithmeticMatrix FMatrix, ArithmeticMatrix GMatrix, double alpha, double beta){
		ArithmeticMatrix Fm = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
			matrix.getRowNameArray(), matrix.getColNameArray());
		ArithmeticMatrix Gm = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
			matrix.getRowNameArray(), matrix.getColNameArray());
		Fm.matrixCopy(FMatrix.getTimesMatrix(alpha));
		Gm.matrixCopy(GMatrix.getTimesMatrix(beta));
		return Fm.getAddMatrix(Gm);
	}
	
/**
* 系列化された課題番号を配列で返します。
* アルファとベータの値はデフォルト値となります。
* @return 系列化された順を格納した配列
*/
	public int[] getTaskSequenceArray(){
		return getTaskSequenceArray(TASK_SEQUENCE_ALPHA, TASK_SEQUENCE_BETA);
	}
	
/**
* 系列化された課題番号を配列で返します。
* 配列の中身は系列化された隣接マトリクスの行番号です。
* @param alpha アルファの値
* @param beta ベータの値
* @return 系列化された順を格納した配列
* @exception SetValueOutOfBoundsException アルファとベータの値が不正である場合
*/
	public int[] getTaskSequenceArray(double alpha, double beta){
		
		//アルファとベータは適正かどうか
		if(isNormalIndex(alpha, beta)){
			
			//可到達マトリクス
			ArithmeticMatrix reachMatrix = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
				matrix.getRowNameArray(), matrix.getColNameArray());
			reachMatrix.matrixCopy(getReachMatrix(matrix));
			
			//コヒーレンス性マトリクス
			ArithmeticMatrix CMatrix = new ArithmeticMatrix(matrix.getRowLength(), matrix.getColLength(),
				matrix.getRowNameArray(), matrix.getColNameArray());
			CMatrix.matrixCopy(getCoherenceMatrix(alpha, beta));
			
			//結果を返す配列
			int[] returnArray;
			
			//選択可能項目が入った配列
				boolean[] el = new boolean[reachMatrix.getRowLength()];
			
			//仮想項目を設定した場合
			if(isDummy()){
				returnArray = new int[reachMatrix.getRowLength()-1];
				
				//選択項目
				int item = 0;
				
				//初期項目を選択する
				System.arraycopy(getReachElement(reachMatrix), 0, el, 0, reachMatrix.getRowLength());
				for(int i = 0; i < reachMatrix.getRowLength() -1; i++){
					if(el[i]){
						item = i;
					}
				}
				
				//構造から除外する
				for(int i = 0; i < reachMatrix.getColLength(); i++){
					reachMatrix.setCell(item, i, 0);
				}
				
				//系列化をする
				for(int i = 0;i < reachMatrix.getRowLength()-1; i++){
					
					//選択されている項目から次に選択する項目を探す
					System.arraycopy(getReachElement(reachMatrix), 0, el, 0, reachMatrix.getRowLength());
					item = getItemNumber(item, el, CMatrix);
					returnArray[i] = item -1;
					for(int j = 0; j < reachMatrix.getColLength(); j++){
						reachMatrix.setCell(item, j, 0);
					}
				}
			}
			
			//仮想項目の無い場合
			else{
				returnArray = new int[reachMatrix.getRowLength()];
				
				//選択項目
				int item = 0;
				
				//初期項目を選択する
				System.arraycopy(getReachElement(reachMatrix), 0, el, 0, reachMatrix.getRowLength());
				for(int i = 0; i < reachMatrix.getRowLength(); i++){
					if(el[i]){
						item = i;
					}
				}
				returnArray[0] = item;
				for(int i = 0; i < reachMatrix.getColLength(); i++){
					reachMatrix.setCell(item, i, 0);
				}
				
				//系列化をする
				for(int i = 1;i < reachMatrix.getRowLength(); i++){
					
					//選択されている項目から次に選択する項目を探す
					System.arraycopy(getReachElement(reachMatrix), 0, el, 0, reachMatrix.getRowLength());
					item = getItemNumber(item, el, CMatrix);
					returnArray[i] = item;
					for(int j = 0; j < reachMatrix.getColLength(); j++){
						reachMatrix.setCell(item, j, 0);
					}
				}
			}
			return returnArray;
		}
		else{		//エラーの場合
			throw new SetValueOutOfBoundsException("alpha(" + alpha + "),beta("+ beta +")");
		}
	}
	
/**
* 系列化された課題番号をSequenceで返します。
* アルファとベータの値はデフォルト値になります。
* @return 系列化された順を格納した配列
*/
	public Sequence getTaskSequence(){
		return getTaskSequence(TASK_SEQUENCE_ALPHA, TASK_SEQUENCE_BETA);
	}
	
/**
* 系列化された課題番号をSequenceで返します。
* @param alpha アルファの値
* @param beta ベータの値
* @return 系列化された順を格納した配列
* @exception SetValueOutOfBoundsException アルファとベータの値が不正である場合
*/
	public Sequence getTaskSequence(double alpha, double beta){
		
		//アルファとベータは適正かどうか
		if(isNormalIndex(alpha, beta)){
			Sequence seq = new Sequence(getTaskSequenceArray(alpha, beta));
			for(int i = 0; i < seq.getLength(); i++){
				seq.setItemName(i, baseMatrix.getRowName(seq.getItem(i)));
			}
			return seq;
		}
		else{		//エラーの場合
			throw new SetValueOutOfBoundsException("alpha(" + alpha + "),beta("+ beta +")");
		}
	}
	
/**
* 他の要素へ可到達であるが、他の要素からその要素へは可到達ではない要素を可到達マトリクスから探します。
* すなわち、選択可能項目を探します。
* 結果はサイズが要素数のboolean型の配列で返されます。
* @param reachMatrix 可到達マトリクス
* @return 検索結果の配列
*/
	public boolean[] getReachElement(ArithmeticMatrix reachMatrix){
		boolean[] returnArray = new boolean[reachMatrix.getRowLength()];
		for(int i = 0; i < reachMatrix.getRowLength(); i++){
			int count = 0;
			for(int j = 0; j < reachMatrix.getColLength(); j++){
				if(reachMatrix.getCell(j,i) == TASK_SEQUENCE_TRUE){
					count++;
				}
			}
			
			if(count == 1){
				returnArray[i] = true;
			}
			else{
				returnArray[i] = false;
			}
		}
		
		return returnArray;
	}
	
/**
* 選択可能項目が入った配列から選択される項目を探します。
* @param item 選択する項目
* @param choiceArray 選択可能項目が入った配列
* @param coherenceMatrix コヒーレンス性マトリクス
* @return 選択される項目
*/
	private int getItemNumber(int item, boolean[] choiceArray, ArithmeticMatrix coherenceMatrix){
		int itemNumber = 0;
		double val = Double.NEGATIVE_INFINITY;
		for(int i = 0; i < choiceArray.length; i++){
			if(choiceArray[i] && coherenceMatrix.getCell(item, i) > val){
				val = coherenceMatrix.getCell(item, i);
				itemNumber = i;
			}
		}
		
		return itemNumber;
	}
	
/**
* アルファの値が適正かどうかチェックします。
* @param alpha アルファの値
* @return 値が適正であればtrue
*/
	public boolean isNormalAlpha(double alpha){
		return (0.0 <= Math.abs(alpha)) && (Math.abs(alpha) <= 1.0);
	}
	
/**
* ベータの値が適正かどうかチェックします。
* @param beta ベータの値
* @return 値が適正であればtrue
*/
	public boolean isNormalBeta(double beta){
		return (0.0 <= Math.abs(beta)) && (Math.abs(beta) <= 1.0);
	}
	
/**
* アルファとベータの値が適正かどうかチェックします。
* @param alpha アルファの値
* @param beta ベータの値
* @return 値が適正であればtrue
*/
	public boolean isNormalIndex(double alpha, double beta){
		boolean condition = (Math.abs(alpha) + Math.abs(beta) == 1.0);
		return condition && isNormalAlpha(alpha) && isNormalBeta(beta);
	}
	
/**
* 仮想項目が必要かどうかチェックします。
* @param adjacencyMatrix 調べる隣接マトリクス
* @return 仮想項目が必要であればtrue
*/
	private boolean needDummy(ArithmeticMatrix adjacencyMatrix){
		ArithmeticMatrix RMatrix = new ArithmeticMatrix(adjacencyMatrix.getRowLength(), adjacencyMatrix.getColLength(),
			adjacencyMatrix.getRowNameArray(), adjacencyMatrix.getColNameArray());
		RMatrix.matrixCopy(adjacencyMatrix.getReachMatrix());
		
		boolean[] el = new boolean[RMatrix.getRowLength()];
		System.arraycopy(getReachElement(RMatrix), 0, el, 0, RMatrix.getRowLength());
		int count = 0;
		for(int i = 0; i < el.length; i++){
			if(el[i]){
				count++;
			}
		}
		
		//初期項目が1を超える場合
		return count > 1;
	}
	
/**
* 隣接マトリクスを設定します。
* @param m 設定する隣接マトリクス
* @exception NotSquareMatrixException 隣接マトリクスが正方行列ではない場合
*/
	public void setMatrix(ArithmeticMatrix m){
		if(m.isSquare()){
		baseMatrix.matrixCopy(m);
		setDummy();
		}
		else{
			throw new NotSquareMatrixException();
		}
	}
	
/**
* 仮想項目を設定します。
*/
	private void setDummy(){
		matrix.matrixCopy(baseMatrix);
		if(needDummy(baseMatrix)){
			giveDummy = true;
			matrix.reSizeMatrix(baseMatrix.getRowLength()+1, baseMatrix.getColLength()+1);
			matrix.matrixMove(0, 0, 1, 1, baseMatrix.getRowLength(), baseMatrix.getColLength());
			matrix.rowNameMove(0, 1, baseMatrix.getRowLength());
			matrix.colNameMove(0, 1, baseMatrix.getColLength());
			matrix.setRowName(0, "Dummy");
			matrix.setColName(0, "Dummy");
			
			ArithmeticMatrix RMatrix = new ArithmeticMatrix(baseMatrix.getRowLength(), baseMatrix.getColLength(),
				baseMatrix.getRowNameArray(), baseMatrix.getColNameArray());
			RMatrix.matrixCopy(baseMatrix.getReachMatrix());
			
			for(int i = 0; i < matrix.getColLength(); i++){
				matrix.setCell(i, 0, TASK_SEQUENCE_FALSE);
			}
			
			boolean[] el = new boolean[RMatrix.getRowLength()];
			System.arraycopy(getReachElement(RMatrix), 0, el, 0, RMatrix.getRowLength());
			for(int i = 1; i < el.length+1; i++){
				if(el[i-1]){
					matrix.setCell(0, i, TASK_SEQUENCE_TRUE);
				}
				else{
					matrix.setCell(0, i, TASK_SEQUENCE_FALSE);
				}
			}
		}
		else{
			giveDummy = false;
		}
	}
	
/**
* 仮想項目を設定したかを返します。
* @return 仮想項目を設定した場合はtrue
*/
	public boolean isDummy(){
		return giveDummy;
	}
	
/**
* 正規化する値を求めます。
* 返される値は最大の目標課題数です。
* @return 正規化する値
*/
	private int getRegular(){
		ArithmeticMatrix reachMatrix = new ArithmeticMatrix();
		reachMatrix.matrixCopy(matrix.getReachMatrix());
		double max = 0.0;
		int maxIndex = 0;
		double count = 0.0;
		for(int i = 0; i < matrix.getColLength(); i++){
			count = reachMatrix.getColSum(i);
			if(count > max){
				maxIndex = i;
				max = count;
			}
		}
		return (int)count;
	}
}