package Camera;


import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

import Other.SomeCodes;
import lombok.Getter;
import lombok.Setter;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.tinylog.Logger;

public class GrabbingTask implements Runnable {
    // for while loop control
    private final AtomicBoolean isGrabbing;
    // for grabbing frames
    private final FrameGrabber grabber;

    // Consumers
    @Setter private Consumer<String> onMessageUpdate;
    @Setter private Consumer<Frame> onHQFrameUpdate;
    @Setter private Consumer<Frame> onLQFrameUpdate;
    @Setter private Consumer<String> onHQBase64Update;
    @Setter private Consumer<String> onLQBase64Update;

    // status of capture fps
    @Getter private int CaptureFPS = 0;

    @Getter @Setter private int lowquality_width = 640;
    @Getter @Setter private int lowquality_height = 360;

    // for FPS calculation
    private int intendedFps = 10;

    @SuppressWarnings("SameParameterValue")
    private void updateMessage(String message) {
        if (onMessageUpdate != null) {
            onMessageUpdate.accept(message);
        }
    }





    private void updateHQBase64(String base64) {
        if (onHQBase64Update != null) {
            onHQBase64Update.accept(base64);
        }
    }

    @SuppressWarnings("SameParameterValue")
    private void updateLQBase64(String base64) {
        if (onLQBase64Update != null) {
            onLQBase64Update.accept(base64);
        }
    }

    private void updateHQFrame(Frame frame) {
        if (onHQFrameUpdate != null) {
            onHQFrameUpdate.accept(frame);
        }
    }

    private void updateLQFrame(Frame frame) {
        if (onLQFrameUpdate != null) {
            onLQFrameUpdate.accept(frame);
        }
    }





    public GrabbingTask(AtomicBoolean isGrabbing, FrameGrabber grabber, int fps) {
        this.isGrabbing = isGrabbing;
        this.grabber = grabber;
        if (fps>0) intendedFps = fps;
    }


    public void Stop(){
        isGrabbing.set(false);
    }

    private void processFrame(Frame fr){
        if (fr!=null){
            updateHQFrame(fr);
            updateHQBase64(SomeCodes.FrameToBase64(fr));
            Frame resized = SomeCodes.ResizeFrame(fr, lowquality_width, lowquality_height);
            updateLQFrame(resized);
            updateLQBase64(SomeCodes.FrameToBase64(resized));
        } else updateMessage("processFrame have null frame");
    }

    private void flush_grabber(){
        try {
            if (grabber!=null) grabber.flush();
        } catch (FrameGrabber.Exception e) {
            Logger.error("Error flushing grabber: "+e.getMessage());
        }
    }

    @Override
    public void run() {
        isGrabbing.set(true);
        Logger.info("Grabbing Task started");
        double fps = grabber.getFrameRate();
        int skippedframes = (int)(fps / intendedFps);
        //Logger.info("Grabber framerate = {}, intendedFps = {}, Skipping frames = {}", fps, intendedFps, skippedframes);
        int framecount = 0;
        flush_grabber();
        long starttick = System.currentTimeMillis();
        while (isGrabbing.get()) {
            try{
                Thread.yield();
                Frame frame = grabber.grab();
                if (framecount<Integer.MAX_VALUE) framecount++; else framecount = 0;
                if (framecount % skippedframes == 0) processFrame(frame);
            } catch (Exception e){
                Logger.error("Error grabbing frame: "+e.getMessage());
            }

            long elapsed = System.currentTimeMillis() - starttick;
            starttick = System.currentTimeMillis();
            if (elapsed>0) {
                int xx = (int) (1000 / elapsed);
                if (xx<fps) CaptureFPS = xx;
            }
            //Logger.info("Elapsed time = {} ms, captureFPS = {}", elapsed, CaptureFPS);
        }
        Logger.info("Grabbing Task stopped");
    }
}
