import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;


public class Backend implements BackendInterface{

    private IterableSortedCollection<Song> tree; // field to hold the tree
    private Integer low; // field to store the low bound of filter range
    private Integer high; // field to store the high bound of filter range
    private boolean filterSet; // determines if the danceability filter has been set
    private Integer currThreshold; // determines the current danceability threshold

    public Backend(IterableSortedCollection<Song> tree) {
        this.tree = tree;
    }

    @Override
    public void readData(String filename) throws IOException {
        Comparator<Song> yearComparator = Comparator.comparingInt(Song::getYear);

        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            String line = reader.readLine(); // Read header

            if (line == null) return;

            // --- Header Parsing ---
            int titleIdx = -1;
            int artistIdx = -1;
            int genresIdx = -1;
            int yearIdx = -1;
            int bpmIdx = -1;
            int nrgyIdx = -1;
            int dnceIdx = -1;
            int dbIdx = -1;
            int liveIdx = -1;

            String[] headers = line.split(",");
            for (int i = 0; i < headers.length; i++) {
                switch (headers[i]) {
                    case "title": titleIdx = i; break;
                    case "artist": artistIdx = i; break;
                    case "top genre": genresIdx = i; break;
                    case "year": yearIdx = i; break;
                    case "bpm": bpmIdx = i; break;
                    case "nrgy": nrgyIdx = i; break;
                    case "dnce": dnceIdx = i; break;
                    case "dB": dbIdx = i; break;
                    case "live": liveIdx = i; break;
                }
            }

            // Parsing Data for Quotes
            while ((line = reader.readLine()) != null) {
                List<String> values = new ArrayList<>();
                StringBuilder currentField = new StringBuilder();
                boolean inQuotes = false;

                for (int i = 0; i < line.length(); i++) {
                    char c = line.charAt(i);

                    if (inQuotes) {
                        if (c == '"') {
                            if (i < line.length() - 1 && line.charAt(i + 1) == '"') {
                                currentField.append('"'); // This is an escaped quote
                                i++; // Skip the next quote
                            } else {
                                inQuotes = false; // This is the closing quote
                            }
                        } else {
                            currentField.append(c); // Add character to the current field
                        }
                    } else {
                        if (c == '"') {
                            inQuotes = true; // Start of a quoted field
                        } else if (c == ',') {
                            values.add(currentField.toString()); // End of a field
                            currentField.setLength(0); // Reset for the next field
                        } else {
                            currentField.append(c); // Add character to the current field
                        }
                    }
                }
                values.add(currentField.toString()); // Add the last field

                String[] valuesArray = values.toArray(new String[0]);

                Song songToAdd = new Song(
                        valuesArray[titleIdx],
                        valuesArray[artistIdx],
                        valuesArray[genresIdx],
                        Integer.parseInt(valuesArray[yearIdx]),
                        Integer.parseInt(valuesArray[bpmIdx]),
                        Integer.parseInt(valuesArray[nrgyIdx]),
                        Integer.parseInt(valuesArray[dnceIdx]),
                        Integer.parseInt(valuesArray[dbIdx]),
                        Integer.parseInt(valuesArray[liveIdx]),
                        yearComparator
                );
                tree.insert(songToAdd);
            }
        } catch (IOException e) {
            throw new IOException("Error reading or processing file: " + filename, e);
        }
    }

    @Override
    public List<String> getAndSetRange(Integer low, Integer high) {
        // Store the range for future calls, as specified.
        this.low = low;
        this.high = high;

        // Use the same yearComparator that songs were created with.
        Comparator<Song> yearComparator = Comparator.comparingInt(Song::getYear);

        // Create song objects to define the range for the iterator.
        // The tree's comparison logic will use the yearComparator on these songs.
        Song minSong = (low == null) ? null : new Song(null, null, null, low, 0, 0, 0, 0, 0, yearComparator);
        Song maxSong = (high == null) ? null : new Song(null, null, null, high, 0, 0, 0, 0, 0, yearComparator);

        tree.setIteratorMin(minSong);
        tree.setIteratorMax(maxSong);

        List<Song> songsInRange = new ArrayList<>();
        // The tree's iterator will now only yield songs within the year range.
        for (Song song : tree) {
            // If a filter is set, check if the song passes the loudness threshold.
            // Note: Higher dB values are louder, so we check >=.
            if (!filterSet || song.getLoudness() < currThreshold) {
                songsInRange.add(song);
            }
        }

        // The spec requires the final list to be ordered by year.
        songsInRange.sort(yearComparator);

        // Extract the titles from the sorted and filtered list of songs.
        List<String> songTitles = new ArrayList<>();
        for (Song song : songsInRange) {
            songTitles.add(song.getTitle());
        }

        return songTitles;
    }

    @Override
    public List<String> applyAndSetFilter(Integer threshold) {
        // Set or clear the filter based on the threshold, as specified.
        if (threshold == null) {
            this.filterSet = false;
            this.currThreshold = null;
        } else {
            this.filterSet = true;
            this.currThreshold = threshold;
        }

        // Apply the previously set year range, if it exists.
        Comparator<Song> yearComparator = Comparator.comparingInt(Song::getYear);
        Song minSong = (this.low == null) ? null : new Song(null, null, null, this.low, 0, 0, 0, 0, 0, yearComparator);
        Song maxSong = (this.high == null) ? null : new Song(null, null, null, this.high, 0, 0, 0, 0, 0, yearComparator);

        tree.setIteratorMin(minSong);
        tree.setIteratorMax(maxSong);

        List<Song> songsToReturn = new ArrayList<>();
        // Iterate through songs within the year range.
        for (Song song : tree) {
            // If the filter is not set, or if the song's loudness is less than the threshold, add it.
            // This correctly handles both applying and clearing the filter.
            if (!filterSet || song.getLoudness() < this.currThreshold) {
                songsToReturn.add(song);
            }
        }

        // The spec requires the final list to be ordered by year.
        songsToReturn.sort(yearComparator);

        // Extract the titles from the sorted and filtered list of songs.
        List<String> songTitles = new ArrayList<>();
        for (Song song : songsToReturn) {
            songTitles.add(song.getTitle());
        }
        return songTitles;
    }

    @Override
    public List<String> fiveMost() {
        // 1. Apply the previously set year range, if it exists.
        Comparator<Song> yearComparator = Comparator.comparingInt(Song::getYear);
        Song minSong = (this.low == null) ? null : new Song(null, null, null, this.low, 0, 0, 0, 0, 0, yearComparator);
        Song maxSong = (this.high == null) ? null : new Song(null, null, null, this.high, 0, 0, 0, 0, 0, yearComparator);

        tree.setIteratorMin(minSong);
        tree.setIteratorMax(maxSong);

        // 2. Iterate and collect all songs that pass the year range AND the loudness filter.
        List<Song> filteredSongs = new ArrayList<>();
        for (Song song : tree) {
            if (!filterSet || song.getLoudness() < this.currThreshold) {
                filteredSongs.add(song);
            }
        }

        // 3. Sort the collected songs by danceability in descending order.
        filteredSongs.sort(Comparator.comparingInt(Song::getDanceability).reversed());

        // 4. Determine how many songs to take (up to 5).
        int limit = Math.min(5, filteredSongs.size());
        List<Song> topFiveSongs = filteredSongs.subList(0, limit);

        // 5. Extract the titles from the top songs.
        List<String> songTitles = new ArrayList<>();
        for (Song song : topFiveSongs) {
            songTitles.add(song.getTitle());
        }
        return songTitles;
    }
}
