/*
 * Copyright 2007 Kasper B. Graversen
 * 
 * Licensed 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.
 */
package org.supercsv.io;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvConstraintViolationException;
import org.supercsv.exception.SuperCsvException;
import org.supercsv.prefs.CsvPreference;
import org.supercsv.util.Util;

/**
 * Defines the standard behaviour of a CSV reader.
 * 
 * @author Kasper B. Graversen
 * @author James Bassett
 */
public abstract class AbstractCsvReader implements ICsvReader {
	
	private final ITokenizer tokenizer;
	
	private final CsvPreference preferences;
	
	// the current tokenized columns
	private final List<String> columns = new ArrayList<String>();
	
	// the number of CSV records read
	private int rowNumber = 0;
	
	/**
	 * Constructs a new <tt>AbstractCsvReader</tt>, using the default {@link Tokenizer}.
	 * 
	 * @param reader
	 *            the reader
	 * @param preferences
	 *            the CSV preferences
	 * @throws NullPointerException
	 *             if reader or preferences are null
	 */
	public AbstractCsvReader(final Reader reader, final CsvPreference preferences) {
		if( reader == null ) {
			throw new NullPointerException("reader should not be null");
		} else if( preferences == null ) {
			throw new NullPointerException("preferences should not be null");
		}
		
		this.preferences = preferences;
		this.tokenizer = new Tokenizer(reader, preferences);
	}
	
	/**
	 * Constructs a new <tt>AbstractCsvReader</tt>, using a custom {@link Tokenizer} (which should have already been set
	 * up with the Reader, CsvPreference, and CsvContext). This constructor should only be used if the default Tokenizer
	 * doesn't provide the required functionality.
	 * 
	 * @param tokenizer
	 *            the tokenizer
	 * @param preferences
	 *            the CSV preferences
	 * @throws NullPointerException
	 *             if tokenizer or preferences are null
	 */
	public AbstractCsvReader(final ITokenizer tokenizer, final CsvPreference preferences) {
		if( tokenizer == null ) {
			throw new NullPointerException("tokenizer should not be null");
		} else if( preferences == null ) {
			throw new NullPointerException("preferences should not be null");
		}
		
		this.preferences = preferences;
		this.tokenizer = tokenizer;
	}
	
	/**
	 * Closes the Tokenizer and its associated Reader.
	 */
	public void close() throws IOException {
		tokenizer.close();
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String get(final int n) {
		return columns.get(n - 1); // column numbers start at 1
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String[] getHeader(final boolean firstLineCheck) throws IOException {
		
		if( firstLineCheck && tokenizer.getLineNumber() != 0 ) {
			throw new SuperCsvException(String.format(
				"CSV header must be fetched as the first read operation, but %d lines have already been read",
				tokenizer.getLineNumber()));
		}
		
		if( readRow() ) {
			return columns.toArray(new String[columns.size()]);
		}
		
		return null;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public int getLineNumber() {
		return tokenizer.getLineNumber();
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getUntokenizedRow() {
		return tokenizer.getUntokenizedRow();
	}
	
	/**
	 * {@inheritDoc}
	 */
	public int getRowNumber() {
		return rowNumber;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public int length() {
		return columns.size();
	}
	
	/**
	 * Gets the tokenized columns.
	 * 
	 * @return the tokenized columns
	 */
	protected List<String> getColumns() {
		return columns;
	}
	
	/**
	 * Gets the preferences.
	 * 
	 * @return the preferences
	 */
	protected CsvPreference getPreferences() {
		return preferences;
	}
	
	/**
	 * Calls the tokenizer to read a CSV row. The columns can then be retrieved using {@link #getColumns()}.
	 * 
	 * @return true if something was read, and false if EOF
	 * @throws IOException
	 *             when an IOException occurs
	 * @throws SuperCsvException
	 *             on errors in parsing the input
	 */
	protected boolean readRow() throws IOException {
		if( tokenizer.readColumns(columns) ) {
			rowNumber++;
			return true;
		}
		return false;
	}
	
	/**
	 * Executes the supplied cell processors on the last row of CSV that was read and populates the supplied List of
	 * processed columns.
	 * 
	 * @param processedColumns
	 *            the List to populate with processed columns
	 * @param processors
	 *            the cell processors
	 * @return the updated List
	 * @throws NullPointerException
	 *             if processedColumns or processors is null
	 * @throws SuperCsvConstraintViolationException
	 *             if a CellProcessor constraint failed
	 * @throws SuperCsvException
	 *             if the wrong number of processors are supplied, or CellProcessor execution failed
	 */
	protected List<Object> executeProcessors(final List<Object> processedColumns, final CellProcessor[] processors) {
		Util.executeCellProcessors(processedColumns, getColumns(), processors, getLineNumber(), getRowNumber());
		return processedColumns;
	}
	
}
