package org.supercsv.io;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.List;

import org.supercsv.exception.NullInputException;
import org.supercsv.exception.SuperCSVException;
import org.supercsv.prefs.CsvPreference;
import org.supercsv.util.CSVContext;

/**
 * The writer class capable of writing arrays, maps,... to a CSV file. Notice that the cell processors can also be
 * utilized when writing. E.g. they can help ensure that only numbers are written in numeric columns, that numbers are
 * unique or the output does not contain certain characters or exceed specified string lengths.
 * 
 * @author Kasper B. Graversen
 */
public abstract class AbstractCsvWriter implements ICsvWriter {
final StringBuilder sb = new StringBuilder();
BufferedWriter outStream;
int lineNo;
CsvPreference preference;

public AbstractCsvWriter(final Writer stream, final CsvPreference preference) {
	setPreferences(preference);
	outStream = new BufferedWriter(stream);
	lineNo = 1;
}

/**
 * {@inheritDoc}
 */
public void close() throws IOException {
	outStream.flush();
	outStream.close();
}

/**
 * Make a string ready for writing by escaping various characters as specified by the CSV format
 * 
 * @param csvElem
 *            an elem of a csv file
 * @return an escaped version of the csv elem ready for persisting
 */
protected String escapeString(final String csvElem) {
	if( csvElem.length() == 0 ) { return ""; }
	
	sb.delete(0, sb.length()); // reusing builder object
	
	final int delimiter = preference.getDelimiterChar();
	final char quote = (char) preference.getQuoteChar();
	final char whiteSpace = ' ';
	final String EOLSymbols = preference.getEndOfLineSymbols();
	
	boolean needForEscape = false; // if newline or start with space
	if( csvElem.charAt(0) == whiteSpace ) {
		needForEscape = true;
	}
	
	char c;
	final int lastPos = csvElem.length() - 1;
	for( int i = 0; i <= lastPos; i++ ) {
		
		c = csvElem.charAt(i);
		
		if( c == delimiter ) {
			needForEscape = true;
			sb.append(c);
		} else if( c == quote ) {
			// if its the first character, escape it and set need for space
			if( i == 0 ) {
				sb.append(quote);
				sb.append(quote);
				needForEscape = true;
			} else {
				sb.append(quote);
				sb.append(quote);
				needForEscape = true; // TODO review comments above
			}
		} else if( c == '\n' ) {
			needForEscape = true;
			sb.append(EOLSymbols);
		} else {
			sb.append(c);
		}
	}
	
	// if element contains a newline (mac,windows or linux), escape the
	// whole with a surrounding quotes
	if( needForEscape ) { return quote + sb.toString() + quote; }
	
	return sb.toString();
	
}

/**
 * {@inheritDoc}
 */
public int getLineNumber() {
	return lineNo;
}

/**
 * {@inheritDoc}
 */
public ICsvWriter setPreferences(final CsvPreference preference) {
	this.preference = preference;
	return this;
}

/**
 * The actual write to stream
 */
protected void write(final List<? extends Object> content) throws IOException {
	write(content.toArray());
}

protected void write(final Object... content) throws IOException {
	// convert object array to strings and write them
	final String[] strarr = new String[content.length];
	int i = 0;
	try {
		for( final Object o : content ) {
			strarr[i++] = o.toString();
		}
		write(strarr);
	}
	catch(final NullPointerException e) {
		throw new NullInputException("Object at possition " + i + " is null.", new CSVContext(getLineNumber(), i), e);
	}
}

protected void write(final String... content) throws IOException {
	lineNo++;
	
	final int delimiter = preference.getDelimiterChar();
	int i = 0;
	switch( content.length ) {
	case 0:
		throw new SuperCSVException("There is no content to write for line " + getLineNumber(), new CSVContext(
			getLineNumber(), 0));
		
	case 1: // just write last element after switch
		break;
	
	default:
		// write first 0..N-1 elems
		for( ; i < content.length - 1; i++ ) {
			outStream.write(escapeString(content[i]));
			outStream.write(delimiter);
		}
		break;
	}
	
	// write last elem (without delimiter) and the EOL
	outStream.write(escapeString(content[i]));
	outStream.write(preference.getEndOfLineSymbols());
	return;
}

/**
 * {@inheritDoc}
 */
public void writeHeader(final String... header) throws IOException, SuperCSVException {
	this.write(header);
}

}

@@ -3322,17 +3322,8 @@
 0;%0D%0A
-%09try %7B%0D%0A%09
 %09for
@@ -3360,105 +3360,29 @@
 %0D%0A%09%09
-%09strarr%5Bi++%5D = o.toString();%0D%0A%09%09%7D%0D%0A%09%09write(strarr);%0D%0A%09%7D%0D%0A%09catch(final NullPointerException e
+if( o == null 
 ) %7B
+ 
 %0D%0A
+%09
 %09%09th
@@ -3422,17 +3422,16 @@
 t at pos
-s
 ition %22 
@@ -3445,17 +3445,16 @@
  is null
-.
 %22, new C
@@ -3463,16 +3463,22 @@
 Context(
+%0D%0A%09%09%09%09
 getLineN
@@ -3494,15 +3494,80 @@
 i), 
-e);%0D%0A%09%7D
+(Throwable) null); %7D%0D%0A%09%09strarr%5Bi++%5D = o.toString();%0D%0A%09%7D%0D%0A%09write(strarr);
 %0D%0A%7D%0D

use of 'if' instead of 'try-catch(NPE)' : clearer and better perf