/*
 * Queue - A Queueing system that can be used to handle labs in higher education
 * Copyright (C) 2016-2024  Delft University of Technology
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package nl.tudelft.queue.csv;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.databind.RuntimeJsonMappingException;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.opencsv.CSVWriter;
import com.opencsv.ICSVWriter;

public class CsvHelper {

	/**
	 * Convert a csv file to a list of objects.
	 *
	 * @param  csvFile             The csv file to be read and converted
	 * @param  readerFor           The class to which each row of the csv file needs to be converted.
	 * @param  schema              The schema expected for the CSV file.
	 * @param  <T>                 The type matching the class of readerFor.
	 * @return                     A list of type T containing the objects found in the CSV.
	 * @throws EmptyCsvException   Thrown when the csv is empty.
	 * @throws InvalidCsvException Thrown when the csv could not be parsed.
	 */
	public static <T> List<T> readCsv(MultipartFile csvFile, Class<T> readerFor, CsvSchema schema)
			throws EmptyCsvException, InvalidCsvException {
		if (csvFile.isEmpty()) {
			throw new EmptyCsvException("Can't parse empty CSV file.");
		}

		CsvMapper mapper = new CsvMapper();
		try (InputStream stream = csvFile.getInputStream()) {
			return mapper.readerFor(readerFor).with(schema).<T>readValues(stream).readAll();
		} catch (RuntimeJsonMappingException | IOException e) {
			throw new InvalidCsvException(
					"We could not parse your CSV file, please check the example format.");
		}
	}

	/**
	 * Writes the given values and header to a single CSV String with the given separator.
	 *
	 * @param  header    The header of the CSV, or null if none should be provided.
	 * @param  values    The values to write to CSV.
	 * @param  separator The separator used to separate CSV columns.
	 * @param  <T>       The type of the value to write (has to be CsvAble).
	 * @return           The written CSV String.
	 */
	public static <T extends CsvAble> String writeCsvValues(String[] header, List<T> values, char separator) {
		return writeCsv(header, values.stream().map(CsvAble::toColumns).collect(Collectors.toList()),
				separator);
	}

	/**
	 * Writes the given header and columns to a single CSV String with the given separator.
	 *
	 * @param  header    The header of the CSV, or null if none should be provided.
	 * @param  values    The column values to write to CSV.
	 * @param  separator The separator used to separate CSV columns.
	 * @return           The written CSV String.
	 */
	public static String writeCsv(String[] header, List<String[]> values, char separator) {
		List<String[]> valuesWithHeader = new ArrayList<>(values);
		if (header != null) {
			valuesWithHeader.add(0, header);
		}

		StringWriter writer = new StringWriter();
		new CSVWriter(writer, separator, ICSVWriter.DEFAULT_QUOTE_CHARACTER,
				ICSVWriter.DEFAULT_ESCAPE_CHARACTER, ICSVWriter.DEFAULT_LINE_END)
				.writeAll(valuesWithHeader);

		return writer.toString();
	}

	/**
	 * Writes a CSV String representing the given values of the given class type with the given schema.
	 *
	 * @param  values      The values to write to CSV.
	 * @param  clazz       The class type of the values.
	 * @param  schema      The schema to write the values away with.
	 * @param  <T>         The type of the values.
	 * @return             The String representing the values as a CSV.
	 * @throws IOException probably never, as StringWriter cannot really throw an IOException.
	 */
	public static <T> String writeCsv(List<T> values, Class<T> clazz, CsvSchema schema)
			throws IOException {
		StringWriter writer = new StringWriter();
		new CsvMapper().writerFor(clazz)
				.with(schema)
				.writeValues(writer)
				.writeAll(values);

		return writer.toString();
	}

}
