/**
 * 2402Assignment1.java
 *
 * Caitlin Ross
 * 100735219
 */
 
class HyperlistMatrix extends Object implements Matrix{
	
	//Fields
	
	private int numberOfRows;
	private int numberOfColumns;
	private Row head;
	
	
	//Constructors
	
	//Used to create an empty, zeroed matrix
	public HyperlistMatrix(int numRows, int numCols){
		numberOfRows = numRows;
		numberOfColumns = numCols;
	}
	
	//Used to convert a 2D array into a matrix
	public HyperlistMatrix(int array[][]){
		numberOfRows = array.length;
		numberOfColumns = array[0].length;
		head = null;
		for(int i=0; i<array.length; i++){
			for(int j=0; j<array[i].length; j++){
				
				//If the entry in the array is not 0, add it to the matrix
				if(array[i][j] !=0){
					try{
						setEntry(i+1, j+1, array[i][j]);
					}
					catch(IndexOutOfBoundsException e){
						System.out.print(e.getMessage());
					}
				}
			}
		}
	}
	
	//Used to deep copy one matrix into another
	public HyperlistMatrix(HyperlistMatrix matrix){
		numberOfRows = matrix.numberOfRows;
		numberOfColumns = matrix.numberOfColumns;
		
		//Check if matrix.head is null before initializing
		if(matrix.head != null)
			head = new Row(matrix.head);
		else
			head = null;
	}
	
	
	//Accessors
	
	//Returns the number of rows in the matrix
	public int getNumberOfRows(){
		return numberOfRows;
	}
	
	//Returns the number of columns in the matrix
	public int getNumberOfColumns(){
		return numberOfColumns;
	}
	
	//Returns the value of the specified Entry
	public int getEntry(int rowNumber, int columnNumber) throws IndexOutOfBoundsException {
		
		//Check the bounds of the matrix
		if((rowNumber > numberOfRows) || (columnNumber > numberOfColumns))
			throw new IndexOutOfBoundsException();
			
		//Find the Row specified
		Row aRow = findRow(rowNumber);
		
		//If the Row does not exist, the value is 0
		if (aRow == null)
			return 0;
			
		//If the Row does exist, return the value in the Entry
		return aRow.getEntry(columnNumber);
	}
	
	//Displays the entire matrix
	public String toString(){
		String display = "";
		Row current;
		for(int i=1; i<=numberOfRows; i++){
			
			//Find the Row with the current row number
			current = findRow(i);
			
			//If the Row does exist, display it
			if(current != null){
				display += current.toString();
			}
			
			//If the Row doesn't exist, display a blank row
			else{
				display += Row.blankRow(numberOfColumns);
			}
			current = null;
			display += "\n";
		}
		return display;
	}
	
	//Displays the list of Rows, where each Row is a list of Entries
	public void listView(){
		Row aRow = head;
		
		//Iterate through the list, displaying each Row
		while (aRow != null){
			aRow.displayList();
			aRow = aRow.getNext();
			System.out.print("\n");
		}
	}
	
		
	//Modifiers
	
	//Sets the value of the specified Entry
	public void setEntry(int rowNumber, int columnNumber, int value) throws IndexOutOfBoundsException {
		
		//Check the bounds of the matrix
		if ((rowNumber>numberOfRows) || (columnNumber>numberOfColumns))
			throw new IndexOutOfBoundsException();
			
		//Find the Row specified
		Row aRow = findRow(rowNumber);
		
		//If the Row doesn't exist, create a new one
		if (aRow == null)
			aRow = insertNewRow(rowNumber);
			
		//Set the Entry of the Row
		aRow.setEntry(columnNumber, value);
		
		//If the Row is now empty, remove it
		if(aRow.isEmpty())
			removeRow(aRow);
	}
	
	
	//Helper Methods
	
	//Inserts a new, blank Row
	private Row insertNewRow(int rowNum){
		
		//Find the Row in the list that comes before the one being added
		Row rowBefore = findRowBefore(rowNum);
		Row newRow;
		
		//If there is none, add the Row to the beginning
		if(rowBefore == null)
			newRow = insertFirstRow(rowNum);
			
		//If there is a Row before the current one, insert this Row after it
		else
			newRow = insertAfter(rowBefore, rowNum);
		return newRow;
	}
	
	//Finds the row with the given row number
	private Row findRow(int rowNum){
		Row toReturn = head;
		
		//Iterate through the Rows until one is found with the given row number
		while ((toReturn != null) && (toReturn.getRowNumber() != rowNum)){
			toReturn = toReturn.getNext();
		}
		return toReturn;
	}
	
	//Finds the existing Row that comes before the row number specified
	//Returns null if Row is to be first in the list
	private Row findRowBefore(int rowNum){
		Row before = head;
		
		//Iterate through the Rows until the nextRow has a greater row number
		while ((before != null) && (before.getNext() != null) && (before.getNext().getRowNumber() < rowNum)){
			before = before.getNext();
		}
		return before;
	}
	
	//Creates and adds a Row after the given Row
	private Row insertAfter(Row before, int rowNumber)
	{
		if (before != null){
			
			//Create a new Row to insert, and set the nextRow to the nextRow of the previous Row
			Row newRow = new Row(rowNumber, numberOfColumns, before.getNext());
			
			//Reset the nextRow of the previous Row
			before.setNext(newRow);
			return newRow;
		}
		else
			return null;
	}
	
	//Creates and adds a Row at the head of the list
	private Row insertFirstRow(int rowNum){
		
		//Create a new Row that points to the old head
		Row newRow = new Row(rowNum, numberOfColumns, head);
		
		//Reset the head
		head = newRow;
		return newRow;
	}
	
	//Removes a given Row
	private void removeRow(Row aRow){
		
		//If the Row is the head, reset the head
		if(aRow == head){
			head = head.getNext();
		}
		
		//If the Row isn't the head, find the Row before, and reset the pointers
		else{
			Row before = findRowBefore(aRow.getRowNumber());
			before.setNext (aRow.getNext());
		}
	}

}
