package net.heartshield.sensors;

import net.heartshield.util.JsonUtil;

import org.json.JSONObject;

import java.util.List;
import java.util.Map;

/**
 * @author David Madl (git@abanbytes.eu)
 * @date 2017-02-15
 *
 * IntensityDetector wraps the Android camera as a light intensity detector.
 * Output: timestamped average pixel intensity values: {{t_0, r_0, g_0, b_0}, {t_1, r_1, g_1, b_1}, ...}
 * Timestamps should come from the OS, i.e. SurfaceTexture.getTimestamp() or
 * It should run in a separate thread from the UI if possible, to maximize FPS.
 *
 * States:
 * - STOPPED ... flash and camera off
 * - WAITING ... flash on, waiting for auto-exposure to be in the correct range, for baseline drift to be stable
 * - LOCKED  ... locked A3
 *
 * You may need another state for camera initialized or not.
 *
 * Initially, start() initializes the camera (STOPPED -> WAITING), switches the flash on and then starts providing
 * frames (r,g,b returned are averages of all pixels of the respective color channel).
 * When the whitebalance has settled, lock() will lock A3 (AE,AWB,AF) and bring camera from WAITING -> LOCKED.
 *
 * Android legacy Camera API
 * - SurfaceTexture / OnFrameAvailableListener.onFrameAvailable()
 * Android Camera2 API
 * - ImageReader / OnImageAvailableListener.onImageAvailable()
 * - use acquireNextImage() to avoid skipping frames
 *
 * The class should either fall back if Camera2 API is not available, or you should provide both implementations with an identical interface.
 *
 * It should also handle UI Activity events like onPause()/onResume() and correctly release and re-initialize the camera.
 * Likely this needs some extensions to the interface.
 *
 * Please provide a demo app where integration shown, including Activity events.
 *
 */
public interface IntensityDetector {
    // may be called in either STOPPED or FAILED, other calls will throw
    void start() throws IllegalStateException;
    void stop();

    // may be called in WAITING only, other calls will throw
    void lock() throws IllegalStateException;

    // may be called in either WAITING or LOCKED, other calls will throw
    void unlock() throws IllegalStateException;

    State getState();

    // returns the number of frames dropped since start() or lock(), whichever was most recent
    int getDroppedCount();

    /** may only be available after start() has been called */
    CameraFeatures getFeatures();

    /** only available after stop() has been called */
    JSONObject getCameraMeta();

    // optimistic (maximum) FPS. Note that some frames might be dropped, see getDroppedCount()
    int getFps();

    /**
     * Time series of each dimension: e.g. {{t1, t2, ...}, {x1, x2, ...}}
     * Deep copies are returned.
     *
     * @return sensor data recorded since {@link ISensor#start} was called
     */
    List<double[]> getSamples();

    enum State {
        /** flash and camera off */
        STOPPED,
        /** flash on, waiting for auto-exposure to be in the correct range, for baseline drift to be stable */
        WAITING,
        /** locked A3 */
        LOCKED,
        /** if a Camera error happens */
        FAILED
    }

    class CameraFeatures {
        public boolean flash;
        public boolean aeLockAvailable;
        public boolean awbLockAvailable;
        public boolean afModeOffAvailable;

        public List<String> afModes;
        public List<String> aeModes;
        public List<String> controlModes;

        public String timestampSource;
    }

    class ImageSummary {
        public double[][] meanRow;
        public double[][] meanCol;
        public double[][] stdRow;
        public double[][] stdCol;
        public double fractionRed;

        public String serialize() {
            return JsonUtil.getGson(JsonUtil.Target.REMOTE).toJson(this);
        }
    }

    interface IntensityListener {
        void onState(State newState);

        /**
         * note: summary is only available in State.WAITING
         *
         * @param timestamp  sourced from {@link android.media.Image#getTimestamp()},
         *                   and only in practice it has only coincidental relation
         *                   only on SOME PHONES to nanoTime timebase of {@link System#nanoTime()}
         *
         * @param cpuTimeNanos  rough timestamp that is in nanoTime timebase. Acquired in the image callback on CPU.
         *
         */
        void onFrame(long timestamp, long cpuTimeNanos, double[] rgb, ImageSummary summary);
    }
    void setIntensityListener(IntensityListener listener);

    void setDebugMode(boolean debugMode);
}
