import java.io.*;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ReadDataUtil {

    private static final Pattern LINE_FORMAT = Pattern.compile("^\\d+(\\.\\d+)?, *[A-Za-z \\d:.-]+(, *[-+]?\\d+(\\.\\d+)?([eE][+-]?\\d+)?){6}, *$");
    private static final Pattern DATE_COLUMN_FORMAT = Pattern.compile("^A\\.D\\. (?<year>\\d{4})-(?<month>[A-Z][a-z]{2})-(?<day>[0-3]\\d) \\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$");
    private static final Pattern DATE_FORMAT_YYYY_MMM_DD = Pattern.compile("^(?<year>\\d{4})-(?<month>[A-Z][a-z]{2})-(?<day>[0-3]\\d)$");

    /**
     * Reads the position and velocity vector on the specified 'day' from the file with the
     * specified 'path', and sets position and current velocity of 'b' accordingly. If
     * successful the method returns 'true'. If the specified 'day' was not found in the file,
     * 'b' is unchanged and the method returns 'false'.
     * The file format is validated before reading the state.
     * Lines before the line "$$SOE" and after the line "$$EOE" the are ignored. Each line of the
     * file between the line "$$SOE" and the line "$$EOE" is required to have the following format:
     * JDTDB, TIME, X, Y, Z, VX, VY, VZ
     * where JDTDB is interpretable as a 'double' value, TIME is a string and X, Y, Z, VX, VY and
     * VZ are interpretable as 'double' values. JDTDB can be ignored. The character ',' must only
     * be used as field separator. If the file is not found, an exception of the class
     * 'StateFileNotFoundException' is thrown. If it does not comply with the format described
     * above, the method throws an exception of the class 'StateFileFormatException'. Both
     * exceptions are subtypes of 'IOException'.
     * Precondition: b != null, path != null, day != null and has the format YYYY-MM-DD.
     */
    public static boolean readConfiguration(NamedBody b, String path, String day) throws IOException {
        State state = State.Pre;
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(path))) {
            Scanner lines = new Scanner(in);
            long lineNum = 0;
            while (lines.hasNextLine() && state != State.Post) {
                lineNum++;
                String line = lines.nextLine();
                State nextState = state.next(line);

                if (state == State.In && nextState == State.In) {
                    Matcher m = LINE_FORMAT.matcher(line);
                    if (!m.matches()) {
                        throw new StateFileFormatException(path, lineNum);
                    }

                    String[] rows = line.split(", *");
                    String date;
                    try {
                        date = convertDateColumn(rows[1]);
                    } catch (IllegalArgumentException e) {
                        throw new StateFileFormatException(path, lineNum);
                    }
                    if (date.equals(day)) {
                        try {
                            double x = Double.parseDouble(rows[2]);  // [km]
                            double y = Double.parseDouble(rows[3]);  // [km]
                            double z = Double.parseDouble(rows[4]);  // [km]
                            double vx = Double.parseDouble(rows[5]);  // [km/s]
                            double vy = Double.parseDouble(rows[6]);  // [km/s]
                            double vz = Double.parseDouble(rows[7]);  // [km/s]
                            b.setState(new Vector3(x * 1000, y * 1000, z * 1000), new Vector3(vx * 1000, vy * 1000, vz * 1000));
                        } catch (NumberFormatException e) {
                            throw new StateFileFormatException(path, lineNum);
                        }
                        return true;
                    }
                }

                state = nextState;
            }
        } catch (IOException e) {
            if (e instanceof FileNotFoundException) {
                throw new StateFileNotFoundException(path);
            } else {
                throw e;
            }
        }
        return false;
    }

    private static String convertDateColumn(String column) {
        Matcher m = DATE_COLUMN_FORMAT.matcher(column);
        if (!m.matches()) {
            throw new IllegalArgumentException();
        }

        return m.group("year") + "-" + convertMonth(m.group("month")) + "-" + m.group("day");
    }

    public static String convertDate(String date) {
        Matcher m = DATE_FORMAT_YYYY_MMM_DD.matcher(date);
        if (!m.matches()) {
            throw new IllegalArgumentException();
        }
        return m.group("year") + "-" + convertMonth(m.group("month")) + "-" + m.group("day");
    }

    private static String convertMonth(String month) {
        return switch (month) {
            case "Jan" -> "01";
            case "Feb" -> "02";
            case "Mar" -> "03";
            case "Apr" -> "04";
            case "May" -> "05";
            case "Jun" -> "06";
            case "Jul" -> "07";
            case "Aug" -> "08";
            case "Sep" -> "09";
            case "Oct" -> "10";
            case "Nov" -> "11";
            case "Dec" -> "12";
            default -> throw new IllegalArgumentException();
        };
    }

    private enum State {
        Pre, In, Post;

        public State next(String line) {
            switch (this) {
                case Pre: if (line.equals("$$SOE")) return In; break;
                case In: if (line.equals("$$EOE")) return Post; break;
                case Post: break;
            }
            return this;
        }
    }
}
