
package fr.perceptron;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Random;
import java.util.stream.Stream;

public class DataSet {

    public DataPoint[] points;
    public int numPoints;
    public int numBatches;
    public int currentBatch;

    public DataSet(int numBatches) {
        this.numBatches = numBatches;
        this.currentBatch = 0;
    }

    public void loadFromArray(double[][] inputs, double[] labels) {
        if (inputs.length != labels.length) return;
        int num = inputs.length;
        
        this.points = new DataPoint[num];
        this.numPoints = num;        

        for (int i=0; i<num; i++) {
            DataPoint p = new DataPoint();
            p.inputs = inputs[i];
            p.expectedOutputs = new double[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
            p.expectedOutputs[(int) labels[i]] = 1;
            this.points[i] = p;
        } 
    }

    public void loadDataSet(String path) {
        int numLines = 0;
        // count the number of lines in the file
        try (Stream<String> lines = Files.lines(Paths.get(path))) {
            numLines = (int) lines.count();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // create the array of DataPoints
        this.points = new DataPoint[numLines];
        this.numPoints = numLines;

        // read the file again and fill the array
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String line;
            int i = 0;
            while ((line = br.readLine()) != null) {
                String[] values = line.split(",");
                DataPoint point = new DataPoint();
                point.inputs = new double[values.length - 1];
                point.expectedOutputs = new double[1];

                // fill the inputs
                for (int j = 1; j < values.length; ++j) {
                    point.inputs[j - 1] = Double.parseDouble(values[j]);
                }

                // fill the expected output
                point.expectedOutputs[0] = Double.parseDouble(values[0]);
                this.points[i] = point;
                i++;
            }
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        } catch (NumberFormatException e) {
            System.err.println("Error parsing number: " + e.getMessage());
        }

        // check if the number of points is correct
        if (this.points.length != numLines) {
            System.err.println("Warning: file has " + numLines
            + " lines but " + this.points.length + " points were created.");
        }

        // reset the current batch
        this.currentBatch = 0;
    }

    public DataSet generateBatch() {

        // if the current batch is greater than the number of batches,
        // return null
        if (this.currentBatch >= this.numBatches) {
            return null;
        }

        int batchSize = this.numPoints / this.numBatches;
        // make sure the batch size is correct for the last batch
        if (this.currentBatch == this.numBatches - 1) {
            batchSize = this.numPoints - (this.numBatches - 1) * batchSize;
        }

        // generate a batch of data points
        DataSet batch = new DataSet(0);
        batch.points = new DataPoint[batchSize];
        batch.numPoints = batchSize;

        // fill the batch with the points
        int batchStart = this.currentBatch * batchSize;
        for (int i = 0; i < batchSize; ++i) {
            // get the index of the point in the original data set
            int index = batchStart + i;
            batch.points[i] = new DataPoint();
            batch.points[i].inputs = this.points[index].inputs;
            batch.points[i].expectedOutputs = this.points[index].expectedOutputs;
        }

        // increment the current batch
        this.currentBatch++;

        return batch;
    }

    public void resetBatches() {
        this.currentBatch = 0;
        shuffle();
    }

    public void shuffle() {
        Random rnd = new Random();
        for (int i = numPoints - 1; i > 0; i--) {
            int index = rnd.nextInt(i + 1);
            DataPoint temp = points[i];
            points[i] = points[index];
            points[index] = temp;
        }
    }

    // Ensure that the number of points per batch is set to 'size'
    // and update the number of batches accordingly
    // also reset the current batch to 0
    public void batchesOf(int size) {
        this.currentBatch = 0;
        this.numBatches = this.numPoints / size;
        if (this.numPoints % size != 0) {
            this.numBatches++;
        }
        System.out.println("Number of batches: " + this.numBatches);
        System.out.println("Batch size: " + size);
    }

    public void setNumBatches(int numBatches) {
        this.numBatches = numBatches;
    }
}
