package de.hft.stuttgart.water;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvException;

public class EvapotranspirationCalculator {

	/**
	 * Rs/Rso für Nacht = 0.5
	 *
	 * @param args
	 * @throws IOException
	 * @throws CsvException
	 */
	public static void main(String[] args) throws IOException, CsvException {
		List<String[]> csvValues;
		Path csvPath = Paths.get("Wuerzburg-hour-Meteonorm8.csv");
		try (CSVReader csvReader = new CSVReader(Files.newBufferedReader(csvPath))) {
			csvValues = csvReader.readAll();
		}

		int tempIndex = -1;
		int irrIndex = -1;
		int humidityIndex = -1;
		int windSpeedIndex = -1;
		String[] header = csvValues.get(1);
		for (int i = 0; i < header.length; i++) {
			if (header[i].startsWith("GHI (W/m^2)")) {
				irrIndex = i;
			} else if (header[i].startsWith("Dry-bulb (C)")) {
				tempIndex = i;
			} else if (header[i].startsWith("RHum (%)")) {
				humidityIndex = i;
			} else if (header[i].startsWith("Wspd (m/s)")) {
				windSpeedIndex = i;
			}
		}

		// Würzburg (coordinates from Meteonorm)
		double longitude = 9.967;
		double latitude = 49.767;
		// germany = 345
		// Longitude of middle of time zone (UTC+1)
		double lz = 345;
		// West_positive_longitude
		double lm = 360 - longitude;
		double altitude = 177;

		double svf = 1;
		double advection = 1.6;
		double tr = 0.9;

		LocalDateTime startOfYear = LocalDateTime.of(2005, 1, 1, 1, 0);

		LocalDateTime start = LocalDateTime.of(2005, 1, 1, 1, 0);
		LocalDateTime end = LocalDateTime.of(2006, 1, 1, 0, 0);

		// LocalDateTime start = LocalDateTime.of(2005, 4, 1, 0, 0);
		// LocalDateTime end = LocalDateTime.of(2005, 10, 1, 0, 0);

		List<Double> et0us = new ArrayList<>();
		List<Double> et0s = new ArrayList<>();
		List<Double> demands = new ArrayList<>();
		List<String> csvLines = new ArrayList<>();
		LocalDateTime counter = start;
		// System.out.println("Date,ETIa (L/m²/d),ET0 (L/m²/d)");
		int startHours = (int) Duration.between(startOfYear, start).toHours();
		int hours = (int) Duration.between(start, end).toHours();
		DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
		Locale.setDefault(new Locale("en", "US"));
		csvLines.add("DateTime;Temperature;Humidity;GHI;DayOrNight;NetLongwaveRadiation;WindSpeed;SaturationVaporPressure;ActualVaporPressure;Psy;ExtraterrestrialRadiation;NetRadiation;ET0;ET0u");
		csvLines.add(
				"dd/mm/yyyy HH:MM;[°C];[%];[W/m²];[0/1];[MJ/(m².h)];[m/s];[kPa];[kPa];[kPa/°C];[MJ/(m².h)];[MJ/(m².h)];[mm/h];[mm/h]");
		for (int i = startHours; i <= startHours + hours; i++) {
			StringBuffer csvLine = new StringBuffer();
			csvLine.append(String.format("%s;", counter.format(f)));
			String[] row = csvValues.get(i + 2);
			double temp = Double.parseDouble(row[tempIndex]);
			double humidity = Double.parseDouble(row[humidityIndex]);
			double incomingRadiation = Double.parseDouble(row[irrIndex]);

			csvLine.append(String.format("%.1f", temp));
			csvLine.append(String.format(";%.0f", humidity));
			csvLine.append(String.format(";%.0f", incomingRadiation));
			csvLine.append(String.format(";%d", incomingRadiation > 0 ? 1 : 0));

			// convert to MJ/h - 1 W = 0.0036 MJ/h
			incomingRadiation *= 0.0036;
			double windSpeed = Double.parseDouble(row[windSpeedIndex]);
			double saturationVaporPressure = saturationVaporPressure(temp);
			double actualVaporPressure = actualVaporPressure(saturationVaporPressure, humidity);
			int dayOfYear = counter.getDayOfYear();
			double hourOfDay = counter.getHour() + 0.5;

			double extraterrestrialRadiation = ra(dayOfYear, hourOfDay, lz, lm, latitude);
			double netLongwaveRadiation = rnl(temp, actualVaporPressure, incomingRadiation, altitude,
					extraterrestrialRadiation);
			double netRadiation = rn(incomingRadiation, netLongwaveRadiation);
			double et0 = et0(temp, actualVaporPressure, incomingRadiation, altitude, windSpeed, humidity,
					netRadiation);
			double et0u = et0u(svf, advection, et0);

			csvLine.append(String.format(";%.5f", netLongwaveRadiation));
			csvLine.append(String.format(";%.1f", windSpeed));
			csvLine.append(String.format(";%.5f", saturationVaporPressure));
			csvLine.append(String.format(";%.5f", actualVaporPressure));
			csvLine.append(String.format(";%.5f", gamma(altitude)));
			csvLine.append(String.format(";%.5f", extraterrestrialRadiation));
			csvLine.append(String.format(";%.5f", netRadiation));
			csvLine.append(String.format(";%.3f", et0));
			csvLine.append(String.format(";%.3f", et0u));

			et0s.add(et0);
			et0us.add(et0u);
			counter = counter.plusHours(1);
			csvLines.add(csvLine.toString());
		}

		// System.out.println(rain.stream().collect(Collectors.summarizingDouble(Double::doubleValue)));

		Double et0Sum = et0s.stream().collect(Collectors.summingDouble(Double::doubleValue));
		System.out.println(String.format("Summe aller ET0 : %4.0f mm / y", et0Sum));

		Double et0uSum = et0us.stream().collect(Collectors.summingDouble(Double::doubleValue));
		System.out.println(String.format("Summe aller ET0u: %4.0f mm / y", et0uSum));
		double et0uFactor = (0.865 * Math.log10(1 / et0uSum) + 3.36);

		try (BufferedWriter bw = Files.newBufferedWriter(Paths.get("all_values.csv"), StandardCharsets.UTF_8)) {
			bw.write(csvLines.get(0) + ";ET0u;ETIa\n");
			bw.write(csvLines.get(1) + ";[mm/h];[mm/h]\n");

			for (int h = 0; h < et0us.size(); h++) {
				double et0u = et0us.get(h);
				double etia = 0;
				if (et0u > 0) {
					etia = etia(tr, et0u, et0uFactor);
				}
				demands.add(etia);
				bw.write(String.format("%s;%.3f;%.3f%n", csvLines.get(h + 2), et0u, etia));
			}
		}

		System.out.println(String.format("Summe aller ETIa: %4.0f mm / y",
				demands.stream().collect(Collectors.summingDouble(Double::doubleValue))));

	}

	private static double etia(double tr, double et0u, double et0uFactor) {
		// return 1 * 1.6 * et0u;
		return tr * 1.25 * et0u * et0uFactor;
		// return tr * (1.61 * Math.log10(800) - 3.39) * et0u * (0.865 * Math.log10(1 /
		// et0u) + 3.36);
	}

	private static double et0u(double svf, double advection, double et0) {
		return svf * advection * et0;
	}

	private static double et0(double temperature, double actualVaporPressure, double shortWaveRadiation,
			double altitude,
			double windSpeed, double humidity, double netRadiation) {
		double gamma = gamma(altitude);
		double saturationVaporPressure = saturationVaporPressure(temperature);
		double delta = delta(temperature, saturationVaporPressure);
		double g = g(netRadiation, shortWaveRadiation > 0);
		double n = 0.408 * delta * (netRadiation - g)
				+ gamma * (37 / (temperature + 273.15)) * windSpeed * (saturationVaporPressure - actualVaporPressure);
		return n / (delta + gamma * (1 + 0.34 * windSpeed));
	}

	private static double actualVaporPressure(double saturationVaporPressure, double humidity) {
		return saturationVaporPressure * humidity / 100;
	}

	private static double saturationVaporPressure(double temperature) {
		return 0.6108 * Math.exp((17.27 * temperature) / (temperature + 237.3));
	}

	private static double gamma(double altitude) {
		double pressure = 101.3 * Math.pow((293 - 0.0065 * altitude) / 293, 5.26);
		return 0.665 * 0.001 * pressure;
	}

	private static double g(double netRadiation, boolean daylight) {
		if (daylight) {
			return 0.1 * netRadiation;
		} else {
			return 0.5 * netRadiation;
		}
	}

	// Net radiation
	// unit is apparently [MJ / (m².h)]
	private static double rn(double shortWaveRadiation, double rnl) {
		// 1 - 0.23: albedo effect of grass
		return (1 - 0.23) * shortWaveRadiation - rnl;
	}

	// NetLongwaveRadiation
	// unit is apparently [MJ / (m².h)]
	private static double rnl(double temperature, double actualVaporPressure, double rs, double altitude,
			double extraterrestrialRadiation) {
		// divide by 24 for hourly
		double sigma = 4.903E-9 / 24;
		double rso = (0.75 + 2E-5 * altitude) * extraterrestrialRadiation;
		double rsRsoQuotient = 0.5;
		if (rso != 0) {
			rsRsoQuotient = rs / rso;
		}
		if (rsRsoQuotient > 1) {
			rsRsoQuotient = 1;
		}
		return sigma * Math.pow(temperature + 273.15, 4) * (0.34 - 0.14 * Math.sqrt(actualVaporPressure))
				* (1.35 * rsRsoQuotient - 0.35);
	}

	// ExtraterrestrialRadiation
	// unit is apparently [MJ / (m².h)]
	private static double ra(int dayOfYear, double hourOfDay, double lz, double lm, double latitude) {
		double dr = 1 + 0.033 * Math.cos(2 * Math.PI * dayOfYear / 365d);
		double b = (2 * Math.PI * (dayOfYear - 81)) / 364;
		double sc = 0.1645 * Math.sin(2 * b) - 0.1255 * Math.cos(b) - 0.025 * Math.sin(b);
		double omega = Math.PI / 12 * ((hourOfDay + 0.06667 * (lz - lm) + sc) - 12);
		double phi = latitude * Math.PI / 180;
		double delta = 0.409 * Math.sin(2 * Math.PI / 365 * dayOfYear - 1.39);
		double omegas = Math.acos(-Math.tan(phi) * Math.tan(delta));
		double ra = 0;
		if (!(omega < -omegas || omega > omegas)) {
			double omega1 = omega - Math.PI * 1 / 24d;
			double omega2 = omega + Math.PI * 1 / 24d;
			ra = 12 * 60 / Math.PI * 0.0820 * dr * ((omega2 - omega1) * Math.sin(phi) * Math.sin(delta)
					+ Math.cos(phi) * Math.cos(delta) * (Math.sin(omega2) - Math.sin(omega1)));
		}
		return ra;
	}

	private static double delta(double temperatur, double e0) {
		return (4098 * e0) / Math.pow(temperatur + 237.3, 2);
	}
}
