package de.larsgrefer.sense_hat;

import lombok.RequiredArgsConstructor;
import lombok.ToString;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.time.Duration;

/**
 * @author Lars Grefer
 */
@RequiredArgsConstructor
@ToString
public class SenseHat {

    private final File frameBuffer;
    private final EnvironmentSensorAdapter environmentSensorAdapter;

    public SenseHat() throws IOException {
        frameBuffer = FrameBufferHelper.findDeviceFile()
                .orElseThrow(() -> new IllegalStateException("No Framebuffer found"));
        environmentSensorAdapter = new PythonSensorAdapter();

    }

    public SenseHat(File frameBuffer) {
        this.frameBuffer = frameBuffer;
        environmentSensorAdapter = new PythonSensorAdapter();
    }

    public void fill(SenseHatColor senseHatColor) throws IOException {
        try (BufferedSink buffer = Okio.buffer(Okio.sink(frameBuffer))) {

            for (int i = 0; i < 64; i++) {
                buffer.writeShortLe(senseHatColor.getSenseHatColor());
            }
            buffer.flush();
        }
    }

    public void setPixel(int x, int y, SenseHatColor senseHatColor) throws IOException {
        if (x < 0 || x > 7) throw new IndexOutOfBoundsException("x");
        if (y < 0 || y > 7) throw new IndexOutOfBoundsException("y");

        int pixNum = 8 * y + x;

        try (RandomAccessFile randomAccessFile = new RandomAccessFile(frameBuffer, "rw")) {

            int color = senseHatColor.getSenseHatColor();

            randomAccessFile.seek(pixNum * 2);
            randomAccessFile.write(color & 0xFF);
            randomAccessFile.write((color >>> 8) & 0xFF);
        }
    }

    public BufferedImage getImage() throws IOException {
        BufferedImage image = new BufferedImage(8, 8, BufferedImage.TYPE_USHORT_565_RGB);

        short[] data = getDisplayData();
        image.getRaster().setDataElements(0, 0, 8, 8, data);

        return image;
    }

    private short[] getDisplayData() throws IOException {
        short[] data = new short[64];
        try (BufferedSource buffer = Okio.buffer(Okio.source(frameBuffer))) {

            for (int i = 0; i < 64; i++) {
                data[i] = buffer.readShortLe();
            }

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        return data;
    }

    private void setDisplayData(short[] displayData) throws IOException {
        try (BufferedSink buffer = Okio.buffer(Okio.sink(frameBuffer))) {

            for (int i = 0; i < 64; i++) {
                buffer.writeShortLe(displayData[i]);
            }
            buffer.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public void setImage(BufferedImage image) throws IOException {
        if (image.getType() == BufferedImage.TYPE_USHORT_565_RGB) {

            short[] data = (short[]) image.getRaster().getDataElements(0, 0, 8, 8, new short[64]);
            setDisplayData(data);

        } else {
            BufferedImage convertedImg = convertImage(image);

            setImage(convertedImg);
        }
    }

    private BufferedImage convertImage(BufferedImage image) {
        BufferedImage convertedImg = new BufferedImage(8, 8, BufferedImage.TYPE_USHORT_565_RGB);
        Graphics graphics = convertedImg.getGraphics();
        graphics.drawImage(image, 0, 0, null);
        return convertedImg;
    }

    public void fadeTo(BufferedImage image, Duration duration) throws IOException {

        if(image.getType() == BufferedImage.TYPE_USHORT_565_RGB) {
            short[] oldData = getDisplayData();

            short[] newData = (short[]) image.getRaster().getDataElements(0, 0, 8, 8, new short[64]);

            short[] data = new short[64];

            long d = duration.toMillis();
            long start = System.currentTimeMillis();
            while (true) {
                long currentDuration = System.currentTimeMillis() - start;
                double factor = currentDuration == 0 ? 0 : d / currentDuration;

                if (factor < 1) {
                    for (int i = 0; i < 64; i++) {
                        SenseHatColor old = new SenseHatColor(oldData[i]);
                        SenseHatColor newCol = new SenseHatColor(newData[i]);

                        data[i] = (short) old.mix(newCol, factor).getSenseHatColor();
                    }
                    setDisplayData(data);
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    setDisplayData(newData);
                    break;
                }
            }

        } else {
            fadeTo(convertImage(image), duration);
        }
    }

    public SenseHatColor[][] getColors() throws IOException {
        SenseHatColor[][] colors = new SenseHatColor[8][8];

        try (BufferedSource buffer = Okio.buffer(Okio.source(frameBuffer))) {

            for (int i = 0; i < 64; i++) {
                colors[i / 8][i % 8] = new SenseHatColor(buffer.readShortLe());
            }

            return colors;
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public void setColors(SenseHatColor[][] colors) throws IOException {
        if (colors.length != 8) throw new IllegalArgumentException(new IndexOutOfBoundsException());

        try (BufferedSink buffer = Okio.buffer(Okio.sink(frameBuffer))) {

            for (int i = 0; i < 64; i++) {
                SenseHatColor color = colors[i / 8][i % 8];
                buffer.writeShortLe(color == null ? 0 : color.getSenseHatColor());
            }
            buffer.flush();
        }
    }

    public double getPressure() {
        return environmentSensorAdapter.getPressure();
    }

    public double getTemperatureFromPressure() {
        return environmentSensorAdapter.getTemperatureFromPressure();
    }

    public double getHumidity() {
        return environmentSensorAdapter.getHumidity();
    }

    public double getTemperatureFromHumidity() {
        return environmentSensorAdapter.getTemperatureFromHumidity();
    }

    public double getTemperature() {
        return (getTemperatureFromHumidity() + getTemperatureFromHumidity()) / 2;
    }
}

@@ -90,16 +90,50 @@
 String;%0A
+import lombok.extern.slf4j.Slf4j;%0A
 import o
@@ -467,16 +467,23 @@
 oString%0A
+@Slf4j%0A
 public c
@@ -4281,38 +4281,11 @@
 ion 
-== 0 ? 0 : d / currentDuration
+/ d
 ;%0A%0A 
@@ -4313,16 +4313,17 @@
 ctor %3C 1
+d
 ) %7B%0A    
@@ -4819,34 +4819,52 @@
         
-e.printStackTrace(
+log.error(e.getLocalizedMessage(), e
 );%0A     

fix fading