package jp.ac.takushoku_u.cs;

/**
* IRSAnalysisクラスはIRS分析を行うためのクラスです。
* @version 1.1.2
* @author 笠島 裕史(Kasajima Hiroshi)
*/
/*
* 最終更新日 2005年01月18日
*/
public class IRSAnalysis{

/**
* 分析元のマトリクスを保持したマトリクス。
*/
	private ArithmeticMatrix matrix;
	
/**
* しきい値を保持した変数。
*/
	private double thresholdValue;
	
/**
* マトリクスの大きさの初期値。
*/
	private static final int IRSANALYSIS_MATRIX_VALUE = 5;

/**
* しきい値の初期値。
*/
	private static final double IRSANALYSIS_THRESHOLD = 0.5;

/**
* IRSマトリクスとIRSグラフマトリクスに入る値(順序関連がある場合）。
*/
	private static final int IRSANALYSIS_SEQUENCE_TRUE = 1;

/**
* IRSマトリクスとIRSグラフマトリクスに入る値(順序関連がない場合）。
*/
	private static final int IRSANALYSIS_SEQUENCE_FALSE = 0;

	
/**
* 設定されているデフォルトのサイズで分析を行うマトリクスを設定します。
*/
	public IRSAnalysis(){
		ArithmeticMatrix baseMatrix = new ArithmeticMatrix(IRSANALYSIS_MATRIX_VALUE,IRSANALYSIS_MATRIX_VALUE);		//初期マトリクスの作成
		setMatrix(baseMatrix);		//マトリクスの生成
		matrix.clearMatrix();		//マトリクスの要素を初期化
		setThreshold(IRSANALYSIS_THRESHOLD);		//しきい値の設定
	}
	
/**
* 引数を元に分析対象のマトリクスを設定します。
* しきい値はデフォルト値で設定されます。
* @param baseMatrix 分析対象のマトリクス
*/
	public IRSAnalysis(ArithmeticMatrix baseMatrix){
		setMatrix(baseMatrix);
		setThreshold(IRSANALYSIS_THRESHOLD);
	}
	
/**
* 引数を元に分析対象のマトリクスとしきい値を設定します。
* @param baseMatrix 分析対象のマトリクス
* @param threshold 設定するしきい値
*/
	public IRSAnalysis(ArithmeticMatrix baseMatrix, double threshold){
		setMatrix(baseMatrix);
		setThreshold(threshold);
	}
	
/**
* 引数を元に分析対象のマトリクスを設定します。
* しきい値はデフォルト値で設定されます。
* @param baseMatrix 分析対象のマトリクス
*/
	public IRSAnalysis(BooleanMatrix baseMatrix){
		setMatrix(baseMatrix.toArithmeticMatrix());
		setThreshold(IRSANALYSIS_THRESHOLD);
	}
	
/**
* 引数を元に分析対象のマトリクスとしきい値を設定します。
* @param baseMatrix 分析対象のマトリクス
* @param threshold 設定するしきい値
*/
	public IRSAnalysis(BooleanMatrix baseMatrix, double threshold){
		setMatrix(baseMatrix.toArithmeticMatrix());
		setThreshold(threshold);
	}
	
/**
* 分析対象として設定されているマトリクスを返します。
* @return 分析対象のマトリクス
*/
	public ArithmeticMatrix getMatrix(){
		return matrix;
	}
	
/**
* IRSマトリクスを返します。
* @return IRSマトリクス
*/
	public ArithmeticMatrix getIRSMatrix(){
		
		//順序性係数マトリクスの収得
		ArithmeticMatrix coefficientMatrix = new ArithmeticMatrix();
		coefficientMatrix.matrixCopy(getCoefficientMatrix());
		
		ArithmeticMatrix irsMatrix = new ArithmeticMatrix(matrix.getColLength(), matrix.getColLength(),
			matrix.getColNameArray(), matrix.getColNameArray());
		int value;
		for (int i = 0; i <  irsMatrix.getRowLength(); i++){
			for (int j = 0; j <  irsMatrix.getColLength(); j++){
				if (thresholdValue <= coefficientMatrix.getCell(i, j)){	//順序性があるかどうか調べ0,1を設定する
					value = IRSANALYSIS_SEQUENCE_TRUE;
				}
				else{
					value = IRSANALYSIS_SEQUENCE_FALSE;
				}
				irsMatrix.setCell(i, j, value);
			}
		}
		return irsMatrix;
	}
	
/**
* 順序性係数マトリクスを求めます。
* @return 順序性係数マトリクス
*/
	public ArithmeticMatrix getCoefficientMatrix(){
		ArithmeticMatrix coefficientMatrix = new ArithmeticMatrix(matrix.getColLength(), matrix.getColLength(),
			matrix.getColNameArray(), matrix.getColNameArray());
		for (int i = 0; i < coefficientMatrix.getRowLength(); i++){
			for (int j = 0; j < coefficientMatrix.getColLength(); j++){
				coefficientMatrix.setCell(i, j, getCoefficient(i, j));
			}
		}
		return coefficientMatrix;
	}
	
/**
* ある項目Ijと項目Ik間の度数分布表を求めます。
* @param Ij 項目Ij
* @param Ik 項目Ik
* @return 項目Ijから項目Ikへの度数分布表
* @exception NotElementException 指定した要素番号が存在しない場合
*/
	public ArithmeticMatrix getFrequencyMatrix(int Ij, int Ik){
		if (matrix.isColNumber(Ij) && matrix.isColNumber(Ik)){
		ArithmeticMatrix returnTable = new ArithmeticMatrix(2,2);	//0,1の2値のため
		returnTable.clearMatrix();
		for (int i = 0; i < matrix.getRowLength(); i++){		//度数分布表の作成
			double value = returnTable.getCell((int) matrix.getCell(i, Ij),
				(int) matrix.getCell(i, Ik)) +1;
			returnTable.setCell((int) matrix.getCell(i, Ij),
				(int) matrix.getCell(i, Ik), value);
		}
		 return returnTable;
			}
		else{
			throw new NotElementException
				("point(" + Ij + "," + Ik + ")");
		}
	}

/**
* 項目Ijから項目Ikへの順序性係数を求めます。
* @param Ij 項目Ij
* @param Ik 項目Ik
* @return 項目Ijから項目Ikへの順序性係数
* @exception NotElementException 指定した要素番号が存在しない場合
*/
	private double getCoefficient(int Ij, int Ik){
		
		if (matrix.isColNumber(Ij) && matrix.isColNumber(Ik)){
			double value;
			ArithmeticMatrix frequencyMatrix = new ArithmeticMatrix(2,2);
			frequencyMatrix = getFrequencyMatrix(Ij, Ik);	//度数分布表を求める
			
			//順序性係数を求める
			value = (frequencyMatrix.getCell(0,1) * (frequencyMatrix.getRowSum(0) +
			frequencyMatrix.getRowSum(1))) / (frequencyMatrix.getRowSum(0) * frequencyMatrix.getColSum(1));
			return 1 - value;
		}
		else{
			throw new NotElementException
				("point(" + Ij + "," + Ik + ")");
		}
	}
	
/**
* IRSグラフのスケルトンマトリクスを返します。
* @return スケルトンマトリクス
*/
	public ArithmeticMatrix getSkeletonMatrix(){
		
		//IRSマトリクスの収得
		ArithmeticMatrix irsMatrix = new ArithmeticMatrix(matrix.getColLength(), matrix.getColLength(),
			matrix.getColNameArray(), matrix.getColNameArray());
		irsMatrix.matrixCopy(getIRSMatrix());
		
		//スケルトンマトリクスの生成
		ArithmeticMatrix SkeletonMatrix = new ArithmeticMatrix(matrix.getColLength(), matrix.getColLength(),
			matrix.getColNameArray(), matrix.getColNameArray());
		
		//記入する要素順の収得
		int[] order = new int[matrix.getColLength()];
		System.arraycopy(getGraphElementArray(), 0, order, 0, order.length);
		SkeletonMatrix.clearMatrix();
		
		for (int i = 0; i < SkeletonMatrix.getRowLength(); i++){
			int searchRow = order[i];	 //実際に検索を行う行（平均値の低い順の要素）
			int branchCount = 0;		//その要素から他の要素にいくつ枝を描いたか
			boolean isUnder = true;		//その要素より平均値が高い(下に配置する)要素を検索中か

			for (int j = SkeletonMatrix.getRowLength() - 1; j >= 0; j--){
				int searchCol = order[j];	//実際に検索を行う列（要素）

				if (searchRow == searchCol){		//同じ要素を調べようとしているか
					isUnder = false;
					branchCount = 0;		//もう一度初期化
				}
				else{
				//他の要素と順序関係があるかどうか
					if (irsMatrix.getCell(searchRow, searchCol) == IRSANALYSIS_SEQUENCE_TRUE){

						// 枝を描く条件であるか
						// 1.未だこの要素より平均値が高い要素を検索しているか
						// 2.この要素から他の要素に枝をまだ描かれていないか
						// 3.この要素より上の要素から枝が描かれているか 
						if ((branchCount == 0) || (isUnder)||
							(SkeletonMatrix.getCell(searchCol,searchRow) == IRSANALYSIS_SEQUENCE_TRUE)){
								SkeletonMatrix.setCell(searchRow, searchCol, IRSANALYSIS_SEQUENCE_TRUE);
									
								//同一平均点でない場合は枝を描いたカウンタを進める
								if(matrix.getColAverage(searchRow) != matrix.getColAverage(searchCol)){
									branchCount = branchCount + 1;
								}
						}
						else{		//他の要素に枝を描いている場合、どこの要素に枝を描いたか探す
							int counter = 0;	//カウンター
							for (int k = irsMatrix.getRowLength() - 1; k > j; k--){
								
								//描いた枝の先の要素から調べている要素に関連性がないか
								if ((SkeletonMatrix.getCell(searchRow, order[k]) == IRSANALYSIS_SEQUENCE_TRUE) &&
									(irsMatrix.getCell(order[k], searchCol) == IRSANALYSIS_SEQUENCE_FALSE)){
										counter = counter + 1;		//ない場合はカウンターを増やす
								}
							}

							//描いた全ての他の枝の先からその要素へ順序関連がない場合は枝を描く
							if (branchCount == counter){
								SkeletonMatrix.setCell(searchRow, searchCol, IRSANALYSIS_SEQUENCE_TRUE);
								
								//同一平均点でない場合は枝を描いたカウンタを進める
								if(matrix.getColAverage(searchRow) != matrix.getColAverage(searchCol)){
										branchCount = branchCount + 1;
								}
							}
						}
					}
				}
			}
		}
		return SkeletonMatrix;
	}
	
/**
* 各項目の評定平均値の配列を返します。
* @return 平均値の配列が入った配列
*/
	public double[] getGraphAverageArray(){
		double[]  returnArray = new double[matrix.getColLength()];		//各項目の平均値を格納する
		System.arraycopy(matrix.getColAverageArray(), 0, returnArray, 0, returnArray.length);
		return returnArray;
	}

/**
* 項目をグラフを描く順に並べ替えた項目番号の配列を返します。
* 平均正答率の低い順に項目番号を並べ変えます。
* @return グラフを描く順に並べ換えた列番号（項目）が入った配列
*/
	public int[] getGraphElementArray(){
		double[]  averageRating = new double[matrix.getColLength()];		//各項目の平均値を格納する
		int[] returnArray = new int[matrix.getColLength()];		//平均評定値の低い順に並べた時の列番号（項目）を入れる
		System.arraycopy(getGraphAverageArray(), 0, averageRating, 0, averageRating.length);
		
		for (int i = 0; i < averageRating.length; i++){
			double minAverage = averageRating[0];		//初期値
			int minNumber = 0;
			for (int j = 1; j <  averageRating.length; j++){		//比較を行う
				if (minAverage > averageRating[j]){
					minAverage = averageRating[j];
					minNumber = j;
				}
			}
			averageRating[minNumber] = Integer.MAX_VALUE;	 //比較対象からはずす
			returnArray[i] = minNumber;
		}
		return returnArray;
	}
	
/**
* しきい値を返します。
* @return しきい値
*/
	public double getThreshold(){
		return thresholdValue;
	}

/**
* IRS分析の対象となるマトリクスを設定します。
* @param baseMatrix 設定する分析対象のマトリクス
*/
	public void setMatrix(ArithmeticMatrix baseMatrix){
		matrix = new ArithmeticMatrix(baseMatrix.getRowLength(), baseMatrix.getColLength());
		matrix.matrixCopy(baseMatrix);		//マトリクスの設定
	}
	
/**
* IRS分析の対象となるマトリクスの要素を修正します。
* @param row 修正する要素の行
* @param col 修正する要素の列
* @param value 修正値
* @exception NotElementException 指定した要素が存在しない場合
* @exception ValueFormatException 修正する値が0または1以外の値である場合
* @see #setMatrix(ArithmeticMatrix baseMatrix)
*/
	public void setElement(int row, int col, int value){
		if (matrix.isMatrixEntityPoint(row, col)){
			if(value == 0.0 || value == 1.0){
				matrix.setCell(row, col, value);
			}
			else{
				throw new ValueFormatException("value("+value+")");
			}
		}
		else{
			throw new NotElementException("Point(" + row + "," + col + ")");
		}
	}
	
/**
* しきい値を設定します。<BR>
* しきい値は0から1までの間で設定します。
* @param threshold 設定するしきい値
* @exception SetValueOutOfBoundsException しきい値が設定できる範囲外の場合
*/
	public void setThreshold(double threshold){
		if(isThreshold(threshold)){
			thresholdValue = threshold;
		}
		else{		//エラーの場合
			throw new SetValueOutOfBoundsException("Threshold(" + threshold + ")");
		}
	}
	
/**
* しきい値が正しい範囲内かチェックします。
* @param checkThreshold チェックするしきい値
* @return しきい値が範囲内であればtrueを、範囲外であればfalseを返します
*/
	private boolean isThreshold(double checkThreshold){
		return ((0 <= checkThreshold) && (checkThreshold <= 1));
	}

}