package edu.hawaii.ics435;

import java.util.Arrays;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Perceptron {

    //Using Byte because it's the smallest data type in Java that is still treated as a numeric type
    private Byte[][] inputs;
    private Byte[] labels;
    private Double[] weights;

    private static final Logger logger = Logger.getLogger("Perceptron");
    private static final Level LOGGING_LEVEL = Level.FINER;

    /**
     * This is some magic to get the logger to actually log properly
     */
    {
        logger.setLevel(LOGGING_LEVEL);
        logger.setUseParentHandlers(false);
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(LOGGING_LEVEL);
        logger.addHandler(handler);

    }

    public Perceptron(Byte[][] inputs, Byte[] labels) {

        this.inputs = inputs;
        this.labels = labels;
        this.weights = new Double[inputs[0].length];
        Arrays.fill(weights, 0.0);
    }

    protected void learn() {
        int mismatches = 0;
        int round = 0;
        logger.info("Starting training with input data...");
        do {
            mismatches = 0;
            for (int j = 0; j < inputs.length; j++) {
                logger.finer("Processing input " + j);
                Byte[] input = inputs[j];
                Byte output = classify(input);
                Byte desiredOutput = labels[j];
                if (output != desiredOutput) {
                    mismatches++;
                    //Misclassification, update the weights
                    Byte difference = (byte) (desiredOutput - output);
                    logger.finer("Old weights: " + Arrays.toString(weights));
                    for (int i = 0; i < input.length; i++) {
                        weights[i] = weights[i] + difference * input[i];
                    }
                    logger.finer("New weights: " + Arrays.toString(weights));
                }
            }

            logger.fine("Round " + round++ + " completed with " + mismatches + " mismatches");

        } while(mismatches>0);

        logger.info("Done training");
    }

    protected Byte classify(Byte[] input) {
        Double summation = 0.0;

        for(int i = 0; i < input.length; i++) {
            summation += input[i]*weights[i];
        }

        //TODO I'm assuming we have to round here. Not sure if this is true.
        //TODO I think a threshold value is supposed to be used somewhere around here
        return (byte) Math.round(Math.sin(summation));
    }
}

@@ -925,33 +925,34 @@
 his.
-inputs = input
+labels = label
 s;%0A
+%0A
         
 this
@@ -947,37 +947,82 @@
         
-this.labels = labels;
+//Need +1 for weights & input lengths in order to add a bias field
 %0A       
@@ -1064,16 +1064,18 @@
 %5D.length
++1
 %5D;%0A     
@@ -1104,16 +1104,273 @@
 , 0.0);%0A
+        this.inputs = new Byte%5Binputs.length%5D%5Binputs%5B0%5D.length+1%5D;%0A        for(int i = 0; i %3C inputs.length; i++) %7B%0A            System.arraycopy(inputs%5Bi%5D, 0, this.inputs%5Bi%5D, 1, inputs%5Bi%5D.length);%0A            this.inputs%5Bi%5D%5B0%5D = 1; //Set the bias%0A        %7D%0A
     %7D%0A%0A 

Add values for a bias