package net.ssehub.kernel_haven.util.io.csv;

import static net.ssehub.kernel_haven.util.null_checks.NullHelpers.notNull;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

import net.ssehub.kernel_haven.util.io.AbstractTableWriter;
import net.ssehub.kernel_haven.util.null_checks.NonNull;
import net.ssehub.kernel_haven.util.null_checks.Nullable;

/**
 * A writer for writing tables as CSV files. The format escapes field values as defined in
 * <a href="https://tools.ietf.org/html/rfc4180">RFC4180</a>. However, in contrast to the RFC, it uses the semicolon
 * character (;) as the default field delimiter. It also uses single line-feed characters (\n) for line breaks.
 *
 * @author Adam
 */
public class CsvWriter extends AbstractTableWriter {
    
    public static final char DEFAULT_SEPARATOR = ';';

    private @NonNull OutputStream out;
    
    private char separator;
    
    /**
     * Creates a {@link CsvWriter} for the given output stream. Uses the {@link #DEFAULT_SEPARATOR}.
     * 
     * @param out The output stream to write the CSV to. This object will close this stream once it is closed.
     */
    public CsvWriter(@NonNull OutputStream out) {
        this(out, DEFAULT_SEPARATOR);
    }
    
    /**
     * Creates a {@link CsvWriter} for the given output stream.
     * 
     * @param out The output stream to write the CSV to. This object will close this stream once it is closed.
     * @param separator The separator character to use.
     */
    public CsvWriter(@NonNull OutputStream out, char separator) {
        this.out = out;
        this.separator = separator;
    }
    
    @Override
    public void close() throws IOException {
        out.close();
    }
    
    /**
     * Escapes the given field content. Adds " around, the while field, if a char that needs to be escaped is used
     * inside of it. In this case, also escapes any " characters with an additional ".
     * <br />
     * Chars that need to be escaped are:
     * <ul>
     *      <li>\n</li>
     *      <li>\r</li>
     *      <li>"</li>
     *      <li>separator</li>
     * </ul>
     * See <a href="https://tools.ietf.org/html/rfc4180">RFC4180</a>.
     * 
     * @param field The field value to escape.
     * @return The escaped field value.
     */
    private @NonNull String escape(@NonNull String field) {
        boolean mustBeEscaped = false;
        for (char c : field.toCharArray()) {
            if (c == separator || c == '\n' || c == '\r' || c == '"') {
                mustBeEscaped = true;
                break;
            }
        }
        
        if (mustBeEscaped) {
            StringBuilder str = new StringBuilder(field);
            // escape any " with an additional "
            for (int i = 0; i < str.length(); i++) {
                if (str.charAt(i) == '"') {
                    str.insert(i, '"');
                    i++; // don't visit the escaped char again
                }
            }
            str.insert(0, '"').append('"');
            field = notNull(str.toString());
        }
        return field;
    }
    
    /**
     * Writes a single line with the given fields. The fields will be separated by {@link #separator}. A \n will be
     * added at the end of the line.
     * 
     * @param fields The fields to write. Will be escaped.
     * 
     * @throws IOException If writing to the output stream fails.
     */
    private void writeLine(@Nullable Object /*@NonNull*/ ... fields) throws IOException {
        // TODO: commented out @NonNull annotation because checkstyle can't parse it
        StringBuffer line = new StringBuffer();
        for (Object field : fields) {
            String str = field != null ? field.toString() : "";
            if (str == null) {
                str = "";
            }
            line.append(escape(str)).append(separator);
        }
        if (line.length() > 0) {
            // remove trailing separator
            line.delete(line.length() - 1, line.length());
        }
        line.append('\n');
        
        out.write(line.toString().getBytes(Charset.forName("UTF-8")));
    }
    
    @Override
    public void writeRow(@Nullable Object /*@NonNull*/ ... columns) throws IOException {
        // TODO: commented out @NonNull annotation because checkstyle can't parse it
        writeLine(columns);
    }
    
    @Override
    public void flush() throws IOException {
        out.flush();
    }

}

@@ -173,16 +173,74 @@
 Stream;%0A
+import java.io.OutputStreamWriter;%0Aimport java.io.Writer;%0A
 import j
@@ -264,16 +264,16 @@
 harset;%0A
-
 %0Aimport 
@@ -918,36 +918,30 @@
 te @NonNull 
-OutputStream
+Writer
  out;%0A    %0A 
@@ -1554,32 +1554,32 @@
 to use.%0A     */%0A
-
     public CsvWr
@@ -1624,24 +1624,789 @@
 eparator) %7B%0A
+        this.out = new OutputStreamWriter(out, Charset.forName(%22UTF-8%22));%0A        this.separator = separator;%0A    %7D%0A    %0A    /**%0A     * Creates a %7B@link CsvWriter%7D for the given output writer. Uses the %7B@link #DEFAULT_SEPARATOR%7D.%0A     * %0A     * @param out The output writer to write the CSV to. This object will close this stream once it is closed.%0A     */%0A    public CsvWriter(@NonNull Writer out) %7B%0A        this(out, DEFAULT_SEPARATOR);%0A    %7D%0A    %0A    /**%0A     * Creates a %7B@link CsvWriter%7D for the given output writer.%0A     * %0A     * @param out The output writer to write the CSV to. This object will close this stream once it is closed.%0A     * @param separator The separator character to use.%0A     */%0A    public CsvWriter(@NonNull Writer out, char separator) %7B%0A
         this
@@ -4919,43 +4919,8 @@
 ng()
-.getBytes(Charset.forName(%22UTF-8%22))
 );%0A 

Add CsvWriter constructors for Writers