/*
 
* Copyright 2015 Oliver Siegmar
 
*
 
* 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 de.siegmar.fastcsv.reader;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 
* This is the main class for reading CSV data.
 
*
 
* @author Oliver Siegmar
 
*/

public final class CsvReader {

    
/**
     
* Field separator character (default: ',' - comma).
     
*/

    
private char fieldSeparator = ',';

    
/**
     
* Text delimiter character (default: '"' - double quotes).
     
*/

    
private char textDelimiter = '"';

    
/**
     
* Read first line as header line? (default: false).
     
*/

    
private boolean containsHeader;

    
/**
     
* Skip empty rows? (default: true)
     
*/
    
private boolean skipEmptyRows = true;

    
/**
     
* Throw an exception if CSV data contains different field count? (default: false).
     
*/

    
private boolean errorOnDifferentFieldCount;

    
/**
     
* Sets the field separator character (default: ',' - comma).
     
*/

    
public void setFieldSeparator(final char fieldSeparator) {
        
this.fieldSeparator = fieldSeparator;
    
}

    
/**
     
* Sets the text delimiter character (default: '"' - double quotes).
     
*/

    
public void setTextDelimiter(final char textDelimiter) {
        
this.textDelimiter = textDelimiter;
    
}

    
/**
     
* Specifies if the first line should be the header (default: false).
     
*/

    
public void setContainsHeader(final boolean containsHeader) {
        
this.containsHeader = containsHeader;
    
}

    
/**
     
* Specifies if empty rows should be skipped (default: true).
     
*/

    
public void setSkipEmptyRows(final boolean skipEmptyRows) {
        
this.skipEmptyRows = skipEmptyRows;
    
}

    
/**
     
* Specifies if an exception should be thrown, if CSV data contains different field count
     
* (default: false).
     
*/

    
public void setErrorOnDifferentFieldCount(final boolean errorOnDifferentFieldCount) {
        
this.errorOnDifferentFieldCount = errorOnDifferentFieldCount;
    
}

    
/**
     
* Reads an entire file and returns a CsvContainer containing the data.
     
*
     
* @param file the file to read data from.
     
* @param charset the character set to use - must not be {@code null}.
     
* @return the entire file's data - never {@code null}.
     
* @throws IOException if an I/O error occurs.
     
*/

    
public CsvContainer read(final File file, final Charset charset) throws IOException {
        
return read(
            
Objects.requireNonNull(file.toPath(), "file must not be null"),
            
Objects.requireNonNull(charset, "charset must not be null")
        
);
    
}

    
/**
     
* Reads an entire file and returns a CsvContainer containing the data.
     
*
     
* @param path the file to read data from.
     
* @param charset the character set to use - must not be {@code null}.
     
* @return the entire file's data - never {@code null}.
     
* @throws IOException if an I/O error occurs.
     
*/

    
public CsvContainer read(final Path path, final Charset charset) throws IOException {
        
Objects.requireNonNull(path, "path must not be null");
        
Objects.requireNonNull(charset, "charset must not be null");
        
try (final Reader reader = newPathReader(path, charset)) {
            
return read(reader);
        
}
    
}

    
/**
     
* Reads from the provided reader until the end and returns a CsvContainer containing the data.
     
*
     
* This library uses built-in buffering, so you do not need to pass in a buffered Reader
     
* implementation such as
 
.
     
* Performance may be even likely better if you do not.
     
*
     
* @param reader the data source to read from.
     
* @return the entire file's data - never {@code null}.
     
* @throws IOException if an I/O error occurs.
     
*/

    
public CsvContainer read(final Reader reader) throws IOException {
        
final CsvParser csvParser =
            
parse(Objects.requireNonNull(reader, "reader must not be null"));

        
final List<CsvRow> rows = new ArrayList<>();
        
CsvRow csvRow;
        
while ((csvRow = csvParser.nextRow()) != null) {
            
rows.add(csvRow);
        
}

        
if (rows.isEmpty()) {
            
return null;
        
}

        
final List<String> header = containsHeader ? csvParser.getHeader() : null;
        
return new CsvContainer(header, rows);
    
}

    
/**
     
* Constructs a new {@link CsvParser} for the specified arguments.
     
*
     
* @param path the file to read data from.
     
* @param charset the character set to use - must not be {@code null}.
     
* @return a new CsvParser - never {@code null}.
     
* @throws IOException if an I/O error occurs.
     
*/

    
public CsvParser parse(final Path path, final Charset charset) throws IOException {
        
return parse(newPathReader(
            
Objects.requireNonNull(path, "path must not be null"),
            
Objects.requireNonNull(charset, "charset must not be null")
        
));
    
}

    
/**
     
* Constructs a new {@link CsvParser} for the specified arguments.
     
*
     
* @param file the file to read data from.
     
* @param charset the character set to use - must not be {@code null}.
     
* @return a new CsvParser - never {@code null}.
     
* @throws IOException if an I/O error occurs.
     
*/

    
public CsvParser parse(final File file, final Charset charset) throws IOException {
        
return parse(
            
Objects.requireNonNull(file, "file must not be null").toPath(),
            
Objects.requireNonNull(charset, "charset must not be null")
        
);
    
}

    
/**
     
* Constructs a new {@link CsvParser} for the specified arguments.
     
*
     
* This library uses built-in buffering, so you do not need to pass in a buffered Reader
     
* implementation such as
 
.
     
* Performance may be even likely better if you do not.
     
*
     
* @param reader the data source to read from.
     
* @return a new CsvParser - never {@code null}.
     
* @throws IOException if an I/O error occurs.
     
*/

    
public CsvParser parse(final Reader reader) throws IOException {
        
return new CsvParser(Objects.requireNonNull(reader, "reader must not be null"),
            
fieldSeparator, textDelimiter, containsHeader, skipEmptyRows,
            
errorOnDifferentFieldCount);
    
}

    
private static Reader newPathReader(final Path path, final Charset charset) throws IOException {
        
return new InputStreamReader(Files.newInputStream(path, StandardOpenOption.READ), charset);
    
}

}