package pulling.availability_predection;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.RandomAccessFile; //import pulling.FileInterFace;
import java.io.IOException;
import java.text.DecimalFormat;

/**
 * This class is responsible for maintaining the file that stores information about the
 * availability history.
 * 
 * @author Majd Kokaly
 * @version 1
 */
public class AvailabilityLogger {
	/**
	 * Every entry in the file takes 15 bytes
	 */
	static final int TIMESLOT_LENGTH = 15;
	
	/**
	 * The values assigned to not calculated availabilities 
	 */
	static final double NOT_CALCULATED_YET = -1.0;
	
	/**
	 * The name of the file where everything is stored in
	 */
	static final String HISTORY_FILE_NAME = "availbility.his";

	/**
	 * This parameter defines the time in minutes the time between availability
	 * measurements
	 */
	private int timeresolution = 10;

	/** This parameter defines the number of past readings */
	private int pastReadingsCount = 4;

	public AvailabilityLogger() {
		initHistoryFile();
	}

	public AvailabilityLogger(int timeResolution, int pastReadingsCount) {
		this.setTimeresolution(timeResolution);
		this.setPastReadingsCount(pastReadingsCount);
		initHistoryFile();
	}

	public int getPastReadingsCount() {
		return pastReadingsCount;
	}

	private void setPastReadingsCount(int pastReadingsCount) {
		this.pastReadingsCount = pastReadingsCount;
	}

	private int getRecordLength() {
		return TIMESLOT_LENGTH * 2 + this.getPastReadingsCount() * 8;
	}

	public int getTimeresolution() {
		return timeresolution;
	}

	protected void setTimeresolution(int timeresolution) {
		this.timeresolution = timeresolution;
	}

	/**
	 * This method initiates the history files
	 */
	public void initHistoryFile() {
		TimeStamp timeStamp = new TimeStamp();
		try {
			RandomAccessFile file = new RandomAccessFile(HISTORY_FILE_NAME, "rw");
			for (int i = 0; i < WeekDay.values().length; i++) {
				timeStamp = new TimeStamp();
				for (int j = 0; j < 24.0 * 60.0 / (double) this.getTimeresolution(); j++) {
					writeFixedLengthString(WeekDay.values()[i] + "_" + timeStamp.toString(), TIMESLOT_LENGTH, file);
					for (int k = 0; k < getPastReadingsCount(); k++) {
						file.writeDouble(NOT_CALCULATED_YET);
					}
					timeStamp.addMinutes(this.getTimeresolution());
				}

			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	
	
	/**
	 * This method initiates the history files with random data
	 */
	public void initHistoryFileWithRandomData() {
		TimeStamp timeStamp = new TimeStamp();
		try {
			RandomAccessFile file = new RandomAccessFile(HISTORY_FILE_NAME, "rw");
			for (int i = 0; i < WeekDay.values().length; i++) {
				timeStamp = new TimeStamp();
				for (int j = 0; j < 24.0 * 60.0 / (double) this.getTimeresolution(); j++) {
					writeFixedLengthString(WeekDay.values()[i] + "_" + timeStamp.toString(), TIMESLOT_LENGTH, file);
					for (int k = 0; k < getPastReadingsCount(); k++) {
						file.writeDouble(Math.random());
					}
					timeStamp.addMinutes(this.getTimeresolution());
				}

			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	

	/**
	 * This method prints the history file
	 */
	public void printHistoryFile(int fractionsDigits) {
		if (fractionsDigits <= 1)
			fractionsDigits = 2;
		DecimalFormat formatter = new DecimalFormat();
		formatter.setMaximumFractionDigits(fractionsDigits);
		try {
			RandomAccessFile file = new RandomAccessFile(HISTORY_FILE_NAME, "r");
			for (int i = 0; i < WeekDay.values().length; i++) {
				for (int j = 0; j < 24.0 * 60.0 / (double) this.getTimeresolution(); j++) {
					System.out.print(readFixedLengthString(TIMESLOT_LENGTH, file) + ": ");
					for (int k = 0; k < getPastReadingsCount(); k++) {
						System.out.print ( formatter.format(file.readDouble()) + "\t");
					}
					System.out.print("j: " + (j + 1) + " \n");
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * This method set a reading for a specific entry at specific age.
	 * 
	 * @param reading
	 *            New value desired to be set.
	 * @param ageRank
	 *            every time slot has N number of reading. 0 means the most
	 *            recent reading.
	 * @param wd
	 *            is the weekday.
	 * @param ts
	 *            is the time stamp. Together with wd, the specific time slot is
	 *            defined. example: if you had this line: SUNDAY_22_20: .85 .83
	 *            .87 .82 .80 and setReadingQuick(.90, 0, Weekday.Monday, new
	 *            TimeStam(22, 20) ) was invoked, that line will be:
	 *            SUNDAY_22_20: .90 .83 .87 .82 .80. Other lines aren't effected
	 * 
	 */
	public void setReadingQuick(double reading, int ageRank, WeekDay wd, TimeStamp ts) throws IOException {
		RandomAccessFile file = new RandomAccessFile(HISTORY_FILE_NAME, "rw");
		// File pointer is moved to the right place
		file.seek(wd.ordinal() * (24 * 60 / this.getTimeresolution()) * getRecordLength() + // skip
				// all
				// previous
				// weekdays
				ts.howManyPeriodsBefore(this.getTimeresolution()) * getRecordLength() + // to
				// skip
				// all
				// the
				// previous
				// hours
				// in
				// that
				// day
				TIMESLOT_LENGTH * 2 + // to skip the name of the time slot
				((long) 8 * ageRank)); // Finally, to move the pointer to the
		// specific double

		// Write the reading
		file.writeDouble(reading);

		// Close the stream
		file.close();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		AvailabilityLogger logger = new AvailabilityLogger();
		logger.setTimeresolution(7);
		logger.printHistoryFile(2);

	}

	/**
	 * This method sets a new reading for a specific entry and age the rest of
	 * the readings by 1
	 * 
	 * @param reading
	 *            New value desired to be set.
	 * @param wd
	 *            is the weekday.
	 * @param ts
	 *            is the time stamp. (e.g. 12_10)
	 * 
	 */
	public void insertNewReading(double reading, WeekDay wd, TimeStamp ts) throws IOException {

		RandomAccessFile file = new RandomAccessFile(HISTORY_FILE_NAME, "rw");

		int recordsInPreviousWeekDays = (int) (wd.ordinal() * Math.ceil((24.0 * 60.0 / this.getTimeresolution())));

		// File pointer is moved to the right place
		// File pointer is moved to the right place
		file.seek(recordsInPreviousWeekDays * getRecordLength() + // skip
				// all
				// previous
				// weekdays
				// ts.howManyPeriodsBefore(this.getTimeresolution()) *
				// getRecordLength() + // to
				this.howManyPeriods(ts) * getRecordLength() + // skip
				// all
				// the
				// previous
				// time
				// slots
				// in
				// that
				// day
				TIMESLOT_LENGTH * 2); // to skip the name of the time slot

		// Old readings are read
		double oldReading[] = new double[getPastReadingsCount()];
		for (int i = 0; i < getPastReadingsCount(); i++) {
			oldReading[i] = file.readDouble();
		}

		// File pointer is moved to the right place AGAIN
		file.seek(recordsInPreviousWeekDays * getRecordLength() + // skip
				// all
				// previous
				// weekdays
				// ts.howManyPeriodsBefore(this.getTimeresolution()) *
				// getRecordLength() + // to
				this.howManyPeriods(ts) * getRecordLength() + // skip
				// all
				// the
				// previous
				// hours
				// in
				// that
				// day
				TIMESLOT_LENGTH * 2); // to skip the name of the time slot

		// Write the new reading
		file.writeDouble(reading);
		// Old readings are written after advancing the age. (not that the for
		// loop ends at PAST_READINGS_NUMBER -1 )
		for (int i = 0; i < getPastReadingsCount() - 1; i++) {
			file.writeDouble(oldReading[i]);
		}

		// Close the stream
		file.close();
	}

	/**
	 * This method reads a reading for a specific entry at specific age.
	 * 
	 * 
	 * @param ageRank
	 *            every time slot have N number of reading. 1 means the most
	 *            recent reading.
	 * @param wd
	 *            is the weekday.
	 * @param ts
	 *            is the time stamp. Together with wd, the specific time slot is
	 *            defined. example: if you had this line: SUNDAY_22_20: .85 .83
	 *            .87 .82 .80 and setReadingQuick(.90, 0, Weekday.Monday, new
	 *            TimeStam(22, 20) ) was invoked, that line will be:
	 *            SUNDAY_22_20: .90 .83 .87 .82 .80. Other lines aren't effected
	 * 
	 */
	public double getReading(int ageRank, WeekDay wd, TimeStamp ts) {
		double reading = 0;
		try {
			
			RandomAccessFile file = new RandomAccessFile(HISTORY_FILE_NAME, "rw");
			// File pointer is moved to the right place

			int recordsInPreviousWeekDays = (int) (wd.ordinal() * Math.ceil((24.0 * 60.0 / this.getTimeresolution())));

			file.seek(recordsInPreviousWeekDays * getRecordLength() + // skip
					// all
					// previous
					// weekdays
					// ts.howManyPeriodsBefore(this.getTimeresolution()) *
					// getRecordLength() + // to
					this.howManyPeriods(ts) * getRecordLength() + // skip
					// all
					// the
					// previous
					// time
					// slots
					// in
					// that
					// day
					TIMESLOT_LENGTH * 2 // to skip the name of the time slot
					 + ((long) 8 * (ageRank-1) ));
			
			// Write the reading
			reading = file.readDouble();

			// Close the stream
			if (file != null)
				file.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}

		return reading;
	}

	/**
	 * This method simply reads a fixed number of characters off a
	 * DataInputStream
	 * 
	 * @param n
	 *            size of read String
	 * @param inStream
	 *            DataInput to read from (e.g RandomAccessFile Object)
	 */
	public String readFixedLengthString(int n, DataInput inStream) throws IOException {
		// Declare an array of characters to store the read data
		char[] chars = new char[n];
		// Read characters (size times)
		for (int i = 0; i < n; i++) {
			chars[i] = inStream.readChar();
		}
		// retrurn read characters
		return new String(chars);
	}

	/**
	 * This method writes to output stream outStream the string toBeWritten. It
	 * adds blank spaces if toBeWritten is smaller than n, or write the first n
	 * characters of toBeWritten
	 * 
	 * @param n
	 *            size of written String
	 * @param outStream
	 *            outStream to read from (e.g RandomAccessFile Object)
	 */
	public void writeFixedLengthString(String toBeWritten, int n, DataOutput outStream) throws IOException {
		// Declare array to store data to be written to the output stream
		char[] chars = new char[n];

		// Move characters to array chars
		toBeWritten.getChars(0, toBeWritten.length(), chars, 0);

		// Fill the rest with blans spaces, if neccessay (i.e. if n >
		// toBeWritten.length)
		for (int i = Math.min(toBeWritten.length(), n); i < n; i++) {
			chars[i] = ' ';
		}
		outStream.writeChars(new String(chars));
	}

	private int howManyPeriods(TimeStamp ts) {
		return (ts.getHour() * 60 + ts.getMinute()) / this.getTimeresolution();
	}

}
