package net.sourceforge.opencamera.cameracontroller;

import net.sourceforge.opencamera.MyDebug;

import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import android.graphics.Rect;
import android.location.Location;
import android.media.MediaRecorder;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.TextureView;

import androidx.annotation.NonNull;

/** CameraController is an abstract class that wraps up the access/control to
 *  the Android camera, so that the rest of the application doesn't have to
 *  deal directly with the Android camera API. It also allows us to support
 *  more than one camera API through the same API (this is used to support both
 *  the original camera API, and Android 5's Camera2 API).
 *  The class is fairly low level wrapper about the APIs - there is some
 *  additional logical/workarounds where such things are API-specific, but
 *  otherwise the calling application still controls the behaviour of the
 *  camera.
 */
public abstract class CameraController {
    private static final String TAG = "CameraController";
    private final int cameraId;

    public static final String SCENE_MODE_DEFAULT = "auto"; // chosen to match Camera.Parameters.SCENE_MODE_AUTO, but we also use compatible values for Camera2 API
    public static final String COLOR_EFFECT_DEFAULT = "none"; // chosen to match Camera.Parameters.EFFECT_NONE, but we also use compatible values for Camera2 API
    public static final String WHITE_BALANCE_DEFAULT = "auto"; // chosen to match Camera.Parameters.WHITE_BALANCE_AUTO, but we also use compatible values for Camera2 API
    public static final String ANTIBANDING_DEFAULT = "auto"; // chosen to match Camera.Parameters.ANTIBANDING_AUTO, but we also use compatible values for Camera2 API
    public static final String EDGE_MODE_DEFAULT = "default";
    public static final String NOISE_REDUCTION_MODE_DEFAULT = "default";
    public static final String ISO_DEFAULT = "auto";
    public static final long EXPOSURE_TIME_DEFAULT = 1000000000L/30; // note, responsibility of callers to check that this is within the valid min/max range

    public static final int N_IMAGES_NR_DARK = 8;
    public static final int N_IMAGES_NR_DARK_LOW_LIGHT = 15;

    // for testing:
    public volatile int count_camera_parameters_exception;
    public volatile int count_precapture_timeout;
    public volatile boolean test_wait_capture_result; // whether to test delayed capture result in Camera2 API
    public volatile boolean test_release_during_photo; // for Camera2 API, will force takePictureAfterPrecapture() to call release() on UI thread
    public volatile int test_capture_results; // for Camera2 API, how many capture requests completed with RequestTagType.CAPTURE
    public volatile int test_fake_flash_focus; // for Camera2 API, records torch turning on for fake flash during autofocus
    public volatile int test_fake_flash_precapture; // for Camera2 API, records torch turning on for fake flash during precapture
    public volatile int test_fake_flash_photo; // for Camera2 API, records torch turning on for fake flash for photo capture
    public volatile int test_af_state_null_focus; // for Camera2 API, records af_state being null even when we've requested autofocus
    public volatile boolean test_used_tonemap_curve;
    public volatile int test_texture_view_buffer_w; // for TextureView, keep track of buffer size
    public volatile int test_texture_view_buffer_h;
    public volatile boolean test_force_run_post_capture; // for Camera2 API, test using adjustPreview() / RequestTagType.RUN_POST_CAPTURE
    public static volatile boolean test_force_slow_preview_start; // for Camera2 API, test waiting for 6s when starting preview

    /** Class for caching a subset of CameraFeatures, that are slow to read.
     *  For now only used for vendor extensions which are slow to read.
     */
    public static class CameraFeaturesCache {
        public List<Integer> supported_extensions;
        public List<Integer> supported_extensions_zoom;

        final Map<Integer, List<android.util.Size>> extension_picture_sizes_map; // key is extension
        final Map<Integer, List<android.util.Size>> extension_preview_sizes_map; // key is extension

        CameraFeaturesCache(CameraFeatures camera_features, Map<Integer, List<android.util.Size>> extension_picture_sizes_map, Map<Integer, List<android.util.Size>> extension_preview_sizes_map) {
            if( camera_features.supported_extensions != null )
                this.supported_extensions = new ArrayList<>(camera_features.supported_extensions);
            if( camera_features.supported_extensions_zoom != null )
                this.supported_extensions_zoom = new ArrayList<>(camera_features.supported_extensions_zoom);
            this.extension_picture_sizes_map = extension_picture_sizes_map;
            this.extension_preview_sizes_map = extension_preview_sizes_map;
        }
    }

    public static class CameraFeatures {
        public Set<String> physical_camera_ids; // if non-null, this camera is part of a logical camera that exposes these physical camera IDs
        public boolean is_zoom_supported;
        public int max_zoom;
        public List<Integer> zoom_ratios; // list of supported zoom ratios; each value is the zoom multiplied by 100
        public boolean supports_face_detection;
        public List<CameraController.Size> picture_sizes;
        public List<CameraController.Size> video_sizes;
        public List<CameraController.Size> video_sizes_high_speed; // may be null if high speed not supported
        public List<CameraController.Size> preview_sizes;
        public List<Integer> supported_extensions; // if non-null, list of supported camera vendor extensions, see https://developer.android.com/reference/android/hardware/camera2/CameraExtensionCharacteristics
        public List<Integer> supported_extensions_zoom; // if non-null, list of camera vendor extensions that support zoom
        public List<String> supported_flash_values;
        public List<String> supported_focus_values;
        public float [] apertures; // may be null if not supported, else will have at least 2 values
        public int max_num_focus_areas;
        public float minimum_focus_distance;
        public boolean is_exposure_lock_supported;
        public boolean is_white_balance_lock_supported;
        public boolean is_optical_stabilization_supported;
        public boolean is_video_stabilization_supported;
        public boolean is_photo_video_recording_supported;
        public boolean supports_white_balance_temperature;
        public int min_temperature;
        public int max_temperature;
        public boolean supports_iso_range;
        public int min_iso;
        public int max_iso;
        public boolean supports_exposure_time;
        public long min_exposure_time;
        public long max_exposure_time;
        public int min_exposure;
        public int max_exposure;
        public float exposure_step;
        public boolean can_disable_shutter_sound;
        public int tonemap_max_curve_points;
        public boolean supports_tonemap_curve;
        public boolean supports_expo_bracketing; // whether setBurstTye(BURSTTYPE_EXPO) can be used
        public int max_expo_bracketing_n_images;
        public boolean supports_focus_bracketing; // whether setBurstTye(BURSTTYPE_FOCUS) can be used
        public boolean supports_burst; // whether setBurstTye(BURSTTYPE_NORMAL) can be used
        public boolean supports_jpeg_r; // whether supports JPEG_R (Ultra HDR)
        public boolean supports_raw;
        public float view_angle_x; // horizontal angle of view in degrees (when unzoomed)
        public float view_angle_y; // vertical angle of view in degrees (when unzoomed)

        /** Returns whether any of the supplied sizes support the requested fps.
         */
        public static boolean supportsFrameRate(List<Size> sizes, int fps) {
            if( MyDebug.LOG )
                Log.d(TAG, "supportsFrameRate: " + fps);
            if( sizes == null )
                return false;
            for(Size size : sizes) {
                if( size.supportsFrameRate(fps) ) {
                    if( MyDebug.LOG )
                        Log.d(TAG, "fps is supported");
                    return true;
                }
            }
            if( MyDebug.LOG )
                Log.d(TAG, "fps is NOT supported");
            return false;
        }

        /**
         * @param return_closest If true, return a match for the width/height, even if the fps doesn't
         *                       match.
         */
        public static Size findSize(List<Size> sizes, Size size, double fps, boolean return_closest) {
            Size last_s = null;
            for(Size s : sizes) {
                if (size.equals(s)) {
                    last_s = s;
                    if (fps > 0) {
                        if (s.supportsFrameRate(fps)) {
                            return s;
                        }
                    } else {
                        return s;
                    }
                }
            }
            return return_closest ? last_s : null;
        }
    }

    // Android docs and FindBugs recommend that Comparators also be Serializable
    static class RangeSorter implements Comparator<int[]>, Serializable {
        @Serial
        private static final long serialVersionUID = 5802214721073728212L;
        @Override
        public int compare(int[] o1, int[] o2) {
            if (o1[0] == o2[0]) return o1[1] - o2[1];
            return o1[0] - o2[0];
        }
    }

    /* Sorts resolutions from highest to lowest, by area.
     * Android docs and FindBugs recommend that Comparators also be Serializable
     */
    static class SizeSorter implements Comparator<Size>, Serializable {
        @Serial
        private static final long serialVersionUID = 5802214721073718212L;

        @Override
        public int compare(final CameraController.Size a, final CameraController.Size b) {
            return b.width * b.height - a.width * a.height;
        }
    }

    public static class Size {
        public final int width;
        public final int height;
        public boolean supports_burst; // for photo
        public List<Integer> supported_extensions; // for photo and preview: if non-null, list of supported camera vendor extensions
        final List<int[]> fps_ranges; // for video
        public final boolean high_speed; // for video

        Size(int width, int height, List<int[]> fps_ranges, boolean high_speed) {
            this.width = width;
            this.height = height;
            this.supports_burst = true;
            this.fps_ranges = fps_ranges;
            this.high_speed = high_speed;
            Collections.sort(this.fps_ranges, new RangeSorter());
        }

        public Size(int width, int height) {
            this(width, height, new ArrayList<>(), false);
        }

        /** Whether this size supports the requested burst and/or extension
         */
        public boolean supportsRequirements(boolean want_burst, boolean want_extension, int extension) {
            return (!want_burst || this.supports_burst) && (!want_extension || this.supportsExtension(extension));
        }

        public boolean supportsExtension(int extension) {
            return supported_extensions != null && supported_extensions.contains(extension);
        }

        public boolean supportsFrameRate(double fps) {
            for (int[] f : this.fps_ranges) {
                if (f[0] <= fps && fps <= f[1])
                    return true;
            }
            return false;
        }

        public int closestFrameRate(double fps) {
            int closest_fps = -1;
            int closest_dist = -1;
            for (int[] f : this.fps_ranges) {
                if (f[0] <= fps && fps <= f[1])
                    return (int)fps;
                int this_fps;
                if( fps < f[0] )
                    this_fps = f[0];
                else
                    this_fps = f[1];
                int dist = Math.abs(this_fps - (int)fps);
                if( closest_dist == -1 || dist < closest_dist ) {
                    closest_fps = this_fps;
                    closest_dist = dist;
                }
            }
            return closest_fps;
        }

        @Override
        public boolean equals(Object o) {
            if( !(o instanceof Size) )
                return false;
            Size that = (Size)o;
            return this.width == that.width && this.height == that.height;
        }

        @Override
        public int hashCode() {
            // must override this, as we override equals()
            // can't use:
            //return Objects.hash(width, height);
            // as this requires API level 19
            // so use this from http://stackoverflow.com/questions/11742593/what-is-the-hashcode-for-a-custom-class-having-just-two-int-properties
            return width*41 + height;
        }

        @NonNull
        public String toString() {
            StringBuilder s = new StringBuilder();
            for (int[] f : this.fps_ranges) {
                s.append(" [").append(f[0]).append("-").append(f[1]).append("]");
            }
            return this.width + "x" + this.height + " " + s + (this.high_speed ? "-hs" : "");
        }
    }

    /** An area has values from [-1000,-1000] (for top-left) to [1000,1000] (for bottom-right) for whatever is
     * the current field of view (i.e., taking zoom into account).
     */
    public static class Area {
        final Rect rect;
        final int weight;

        public Area(Rect rect, int weight) {
            this.rect = rect;
            this.weight = weight;
        }
    }

    public interface FaceDetectionListener {
        void onFaceDetection(Face[] faces);
    }

    /** Interface to define callbacks related to taking photos. These callbacks are all called on the UI thread.
     */
    public interface PictureCallback {
        void onStarted(); // called immediately before we start capturing the picture

        void onCompleted(); // called after all relevant on*PictureTaken() callbacks have been called and returned

        void onPictureTaken(byte[] data);

        /** Only called if RAW is requested.
         *  Caller should call raw_image.close() when done with the image.
         */
        void onRawPictureTaken(RawImage raw_image);

        /** Only called if burst is requested.
         */
        void onBurstPictureTaken(List<byte[]> images);

        /** Only called if burst is requested.
         */
        void onRawBurstPictureTaken(List<RawImage> raw_images);

        /** Reports percentage progress for vendor camera extensions. Note that not all devices support this being called.
         */
        void onExtensionProgress(int progress);

        /* This is called for when burst mode is BURSTTYPE_FOCUS or BURSTTYPE_CONTINUOUS, to ask whether it's safe to take
         * n_raw extra RAW images and n_jpegs extra JPEG images, or whether to wait.
         */
        boolean imageQueueWouldBlock(int n_raw, int n_jpegs);

        /* This is called for flash_frontscreen_auto or flash_frontscreen_on mode to indicate the caller should light up the screen
         * (for flash_frontscreen_auto it will only be called if the scene is considered dark enough to require the screen flash).
         * The screen flash can be removed when or after onCompleted() is called.
         */
        void onFrontScreenTurnOn();
    }

    /** Interface to define callback for autofocus completing. This callback may be called on the UI thread (CameraController1)
     *  or a background thread (CameraController2).
     */
    public interface AutoFocusCallback {
        void onAutoFocus(boolean success);
    }

    /** Interface to define callback for continuous focus starting/stopping. This callback may be called on the
     *  UI thread (CameraController1) or a background thread (CameraController2).
     */
    public interface ContinuousFocusMoveCallback {
        void onContinuousFocusMove(boolean start);
    }

    public interface ErrorCallback {
        void onError();
    }

    public static class Face {
        public final int score;
        /* The rect has values from [-1000,-1000] (for top-left) to [1000,1000] (for bottom-right) for whatever is
         * the current field of view (i.e., taking zoom into account).
         */
        public final Rect rect;
        /** The temp rect is temporary storage that can be used by callers.
         */
        public final Rect temp = new Rect();

        Face(int score, Rect rect) {
            this.score = score;
            this.rect = rect;
        }
    }

    public static class SupportedValues {
        public final List<String> values;
        public final String selected_value;
        SupportedValues(List<String> values, String selected_value) {
            this.values = values;
            this.selected_value = selected_value;
        }
    }

    public abstract void release();
    public abstract void onError(); // triggers error mechanism - should only be called externally for testing purposes

    CameraController(int cameraId) {
        this.cameraId = cameraId;
    }
    public abstract String getAPI();
    public abstract CameraFeatures getCameraFeatures() throws CameraControllerException;
    public int getCameraId() {
        return cameraId;
    }

    /** For CameraController2 only. Applications should cover the preview textureview if since last resuming, camera_controller
     *  has never been non-null or this method has never returned false.
     *  Otherwise there is a risk when opening the camera that the textureview still shows an image from when
     *  the camera was previously opened (e.g., from pausing and resuming the application). This returns false (for CameraController2)
     *  when the camera has received its first frame.
     *  Update: on more recent Android versions this didn't work very well, possibly due to a screenshot being used for "recent apps"
     *  view; on Android 13+, the activity can make use of shouldCoverPreview(false) for this.
     */
    public boolean shouldCoverPreview() {
        return false;
    }
    /** For CameraController2 only. After calling this, shouldCoverPreview() will return true, until a new
     *  frame from the camera has been received.
     */
    public void resetCoverPreview() {
    }
    public abstract SupportedValues setSceneMode(String value);
    /**
     * @return The current scene mode. Will be null if scene mode not supported.
     */
    public abstract String getSceneMode();
    /**
     * @return Returns true iff changing the scene mode can affect the available camera functionality
     *         (e.g., changing to Night scene mode might mean flash modes are no longer available).
     */
    public abstract boolean sceneModeAffectsFunctionality();
    public abstract SupportedValues setColorEffect(String value);
    public abstract String getColorEffect();
    public abstract SupportedValues setWhiteBalance(String value);
    public abstract String getWhiteBalance();
    public abstract boolean setWhiteBalanceTemperature(int temperature);
    public abstract int getWhiteBalanceTemperature();
    public abstract SupportedValues setAntiBanding(String value);
    public abstract String getAntiBanding();
    public abstract SupportedValues setEdgeMode(String value);
    public abstract String getEdgeMode();
    public abstract SupportedValues setNoiseReductionMode(String value);
    public abstract String getNoiseReductionMode();
    /** Set an ISO value. Only supported if supports_iso_range is false.
     */
    public abstract SupportedValues setISO(String value);
    /** Switch between auto and manual ISO mode. Only supported if supports_iso_range is true.
     * @param manual_iso Whether to switch to manual mode or back to auto.
     * @param iso If manual_iso is true, this specifies the desired ISO value. If this is outside
     *            the min_iso/max_iso, the value will be snapped so it does lie within that range.
     *            If manual_iso i false, this value is ignored.
     */
    public abstract void setManualISO(boolean manual_iso, int iso);

    /**
     * @return Whether in manual ISO mode (as opposed to auto).
     */
    public abstract boolean isManualISO();
    /** Specify a specific ISO value. Only supported if supports_iso_range is true. Callers should
     *  first switch to manual ISO mode using setManualISO().
     */
    public abstract boolean setISO(int iso);
    public abstract String getISOKey();
    /** Returns the manual ISO value. Only supported if supports_iso_range is true.
     */
    public abstract int getISO();
    public abstract long getExposureTime();
    public abstract boolean setExposureTime(long exposure_time);
    public abstract void setAperture(float aperture);
    public abstract CameraController.Size getPictureSize();
    public abstract void setPictureSize(int width, int height);
    public abstract CameraController.Size getPreviewSize();
    public abstract void setPreviewSize(int width, int height);

    public abstract void setCameraExtension(boolean enabled, int extension);
    public abstract boolean isCameraExtension();
    public abstract int getCameraExtension();
    // whether to take a burst of images, and if so, what type
    public enum BurstType {
        BURSTTYPE_NONE, // no burst
        BURSTTYPE_EXPO, // enable expo bracketing mode
        BURSTTYPE_FOCUS, // enable focus bracketing mode;
        BURSTTYPE_NORMAL, // take a regular burst
        BURSTTYPE_CONTINUOUS // as BURSTTYPE_NORMAL, but bursts will fire continually until stopContinuousBurst() is called.
    }
    public abstract void setBurstType(BurstType new_burst_type);
    public abstract BurstType getBurstType();
    /** Only relevant if setBurstType() is also called with BURSTTYPE_NORMAL. Sets the number of
     *  images to take in the burst.
     */
    public abstract void setBurstNImages(int burst_requested_n_images);
    /** Only relevant if setBurstType() is also called with BURSTTYPE_NORMAL. If this method is
     *  called with burst_for_noise_reduction, then the number of burst images, and other settings,
     *  will be set for noise reduction mode (and setBurstNImages() is ignored).
     */
    public abstract void setBurstForNoiseReduction(boolean burst_for_noise_reduction, boolean noise_reduction_low_light);
    public abstract boolean isContinuousBurstInProgress();
    public abstract void stopContinuousBurst();
    public abstract void stopFocusBracketingBurst();
    /** Only relevant if setBurstType() is also called with BURSTTYPE_EXPO. Sets the number of
     *  images to take in the expo burst.
     * @param n_images Must be an odd number greater than 1.
     */
    public abstract void setExpoBracketingNImages(int n_images);
    /** Only relevant if setBurstType() is also called with BURSTTYPE_EXPO.
     */
    public abstract void setExpoBracketingStops(double stops);
    public abstract void setUseExpoFastBurst(boolean use_expo_fast_burst);
    /** Whether to enable a workaround hack for some Galaxy devices - take an additional dummy photo
     *  when taking an expo/HDR burst, to avoid problem where manual exposure is ignored for the
     *  first image.
     */
    public abstract void setDummyCaptureHack(boolean dummy_capture_hack);

    /** Whether the current BurstType is one that requires the camera driver to capture the images
     *  as a burst at a fast rate. If true, we should not use high resolutions that don't support a
     *  capture burst (for Camera2 API, see StreamConfigurationMap.getHighResolutionOutputSizes()).
     */
    public abstract boolean isCaptureFastBurst();
    /** If true, then the camera controller is currently capturing a burst of images.
     */
    public abstract boolean isCapturingBurst();
    /** If isCapturingBurst() is true, then this returns the number of images in the current burst
     *  captured so far.
     */
    public abstract int getNBurstTaken();
    /** If isCapturingBurst() is true, then this returns the total number of images in the current
     *  burst if known. If not known (e.g., for continuous burst mode), returns 0.
     */
    public abstract int getBurstTotal();

    /**
     * @param want_jpeg_r Whether to enable taking photos in JPEG_R (Ultra HDR) format.
     */
    public abstract void setJpegR(boolean want_jpeg_r);

    /**
     * @param want_raw       Whether to enable taking photos in RAW (DNG) format.
     * @param max_raw_images The maximum number of unclosed DNG images that may be held in memory at any one
     *                       time. Trying to take a photo, when the number of unclosed DNG images is already
     *                       equal to this number, will result in an exception (java.lang.IllegalStateException
     *                       - note, the exception will come from a CameraController2 callback, so can't be
     *                       caught by the callera).
     */
    public abstract void setRaw(boolean want_raw, int max_raw_images);

    /** Request a capture session compatible with high speed frame rates.
     *  This should be called only when the preview is paused or not yet started.
     */
    public abstract void setVideoHighSpeed(boolean setVideoHighSpeed);
    /**
     * setUseCamera2FakeFlash() should be called after creating the CameraController, and before calling getCameraFeatures() or
     * starting the preview (as it changes the available flash modes).
     * "Fake flash" is an alternative mode for handling flash, for devices that have poor Camera2 support - typical symptoms
     * include precapture never starting, flash not firing, photos being over or under exposed.
     * Instead, we fake the precapture and flash simply by turning on the torch. After turning on torch, we wait for ae to stop
     * scanning (and af too, as it can start scanning in continuous mode) - this is effectively the equivalent of precapture -
     * before taking the photo.
     * In auto-focus mode, we make the decision ourselves based on the current ISO.
     * We also handle the flash firing for autofocus by turning the torch on and off too. Advantages are:
     *   - The flash tends to be brighter, and the photo can end up overexposed as a result if capture follows the autofocus.
     *   - Some devices also don't seem to fire flash for autofocus in Camera2 mode (e.g., Samsung S7)
     *   - When capture follows autofocus, we need to make the same decision for firing flash for both the autofocus and the capture.
     */
    public void setUseCamera2FakeFlash(boolean use_fake_precapture) {
    }
    public boolean getUseCamera2FakeFlash() {
        return false;
    }
    public abstract boolean getOpticalStabilization();
    /** Whether to enable digital video stabilization. Should only be set to true when intending to
     *  capture video.
     */
    public abstract void setVideoStabilization(boolean enabled);
    public abstract boolean getVideoStabilization();
    public enum TonemapProfile {
        TONEMAPPROFILE_OFF,
        TONEMAPPROFILE_REC709,
        TONEMAPPROFILE_SRGB,
        TONEMAPPROFILE_LOG,
        TONEMAPPROFILE_GAMMA,
        TONEMAPPROFILE_JTVIDEO,
        TONEMAPPROFILE_JTLOG,
        TONEMAPPROFILE_JTLOG2
    }

    /** Sets a tonemap profile.
     * @param tonemap_profile The type of the tonemap profile.
     * @param log_profile_strength Only relevant if tonemap_profile set to TONEMAPPROFILE_LOG.
     * @param gamma Only relevant if tonemap_profile set to TONEMAPPROFILE_GAMMA
     */
    public abstract void setTonemapProfile(TonemapProfile tonemap_profile, float log_profile_strength, float gamma);
    public abstract TonemapProfile getTonemapProfile();
    public abstract int getJpegQuality();
    public abstract void setJpegQuality(int quality);
    /** Returns the current zoom. The returned value is an index into the CameraFeatures.zoom_ratios
     *  array.
     */
    public abstract int getZoom();
    /** Set the zoom.
     * @param value The index into the CameraFeatures.zoom_ratios array.
     */
    public abstract void setZoom(int value);
    /** Set the zoom. Unlike setZoom(value), this allows specifying any zoom level within the
     *  supported range.
     * @param value The index into the CameraFeatures.zoom_ratios array.
     * @param smooth_zoom The desired zoom. With CameraController1 (old Camera API), this is ignored.
     *                    With CameraController2 (Camera2 API), this is used instead of the zoom_ratios
     *                    value. Note that getZoom() will return the value passed to this method, so
     *                    passing an appropriate value (e.g., whatever zoom_ratio is closest to the
     *                    smooth_zoom) is still useful if you want to make use of getZoom().
     *                    smooth_zoom must still be within the supported range of zoom values.
     */
    public abstract void setZoom(int value, float smooth_zoom);
    public abstract void resetZoom(); // resets to zoom 1x
    public abstract int getExposureCompensation();
    public abstract boolean setExposureCompensation(int new_exposure);
    public abstract void setPreviewFpsRange(int min, int max);
    public abstract void clearPreviewFpsRange();
    public abstract List<int []> getSupportedPreviewFpsRange(); // result depends on setting of setVideoHighSpeed()

    public abstract void setFocusValue(String focus_value);
    public abstract String getFocusValue();
    public abstract float getFocusDistance();
    public abstract boolean setFocusDistance(float focus_distance);
    /** Only relevant if setBurstType() is also called with BURSTTYPE_FOCUS. Sets the number of
     *  images to take in the focus burst.
     */
    public abstract void setFocusBracketingNImages(int n_images);
    /** Only relevant if setBurstType() is also called with BURSTTYPE_FOCUS. If set to true, an
     *  additional image will be included at infinite distance.
     */
    public abstract void setFocusBracketingAddInfinity(boolean focus_bracketing_add_infinity);
    /** Only relevant if setBurstType() is also called with BURSTTYPE_FOCUS. Sets the source focus
     *  distance for focus bracketing.
     */
    public abstract void setFocusBracketingSourceDistance(float focus_bracketing_source_distance);
    public abstract float getFocusBracketingSourceDistance();
    /** Only relevant if setBurstType() is also called with BURSTTYPE_FOCUS. Sets the source focus
     *  distance to match the camera's current focus distance (typically useful if running in a
     *  non-manual focus mode).
     */
    public abstract void setFocusBracketingSourceDistanceFromCurrent();
    /** Only relevant if setBurstType() is also called with BURSTTYPE_FOCUS. Sets the target focus
     *  distance for focus bracketing.
     */
    public abstract void setFocusBracketingTargetDistance(float focus_bracketing_target_distance);
    public abstract float getFocusBracketingTargetDistance();
    public abstract void setFlashValue(String flash_value);
    public abstract String getFlashValue();
    public abstract void setRecordingHint(boolean hint);
    public abstract void setAutoExposureLock(boolean enabled);
    public abstract boolean getAutoExposureLock();
    public abstract void setAutoWhiteBalanceLock(boolean enabled);
    public abstract boolean getAutoWhiteBalanceLock();
    public abstract void setRotation(int rotation);
    public abstract void setLocationInfo(Location location);
    public abstract void removeLocationInfo();
    public abstract void enableShutterSound(boolean enabled);
    public abstract boolean setFocusAndMeteringArea(List<CameraController.Area> areas);
    public abstract void clearFocusAndMetering();
    public abstract List<CameraController.Area> getFocusAreas();
    public abstract List<CameraController.Area> getMeteringAreas();
    public abstract boolean supportsAutoFocus();
    public abstract boolean supportsMetering();
    public abstract boolean focusIsContinuous();
    public abstract boolean focusIsVideo();
    public abstract void reconnect() throws CameraControllerException;
    public abstract void setPreviewDisplay(SurfaceHolder holder) throws CameraControllerException;
    public abstract void setPreviewTexture(TextureView texture) throws CameraControllerException;
    /** This should be called when using a TextureView, and the texture view has reported a change
     *  in size via onSurfaceTextureSizeChanged.
     */
    public void updatePreviewTexture() {
        // dummy implementation
    }
    /** Starts the camera preview.
     *  @throws CameraControllerException if the camera preview fails to start.
     */
    /** Starts the camera preview.
     * @param wait_until_started Whether to wait until the preview is started. Only relevant for
     *                           CameraController2; CameraController1 will always wait.
     * @param runnable           If non-null, a runnable to be called once preview is started. If
     *                           wait_until_started==true, or using CameraController1, this will be
     *                           called on the current thread, before this method exits. Otherwise,
     *                           this will be called on the UI thread, after this method exits (once
     *                           the preview has started).
     * @param on_failed          If non-null, a runnable to be called if the preview fails to start.
     *                           Only relevant for wait_until_started==false and when using
     *                           CameraController2. In such cases, failing to start the camera preview
     *                           may result in either CameraControllerException being thrown, or
     *                           on_failed being called on the UI thread after this method exits
     *                           (depending on when the failure occurs). If either of these happens,
     *                           the "runnable" runnable will not be called.
     * @throws CameraControllerException Failed to start preview. In this case, the runnable will not
     *                                   be called.
     */
    public abstract void startPreview(boolean wait_until_started, Runnable runnable, Runnable on_failed) throws CameraControllerException;
    /** Only relevant for CameraController2: stops the repeating burst for the previous (so effectively
     *  stops the preview), but does not close the capture session for the preview (for that, using
     *  stopPreview() instead of stopRepeating()).
     */
    public abstract void stopRepeating();
    public abstract void stopPreview();
    public abstract boolean startFaceDetection();
    public abstract void setFaceDetectionListener(final CameraController.FaceDetectionListener listener);

    /**
     * @param cb Callback to be called when autofocus completes.
     * @param capture_follows_autofocus_hint Set to true if you intend to take a photo immediately after autofocus. If the
     *                                       decision changes after autofocus has started (e.g., user initiates autofocus,
     *                                       then takes photo before autofocus has completed), use setCaptureFollowAutofocusHint().
     */
    public abstract void autoFocus(final CameraController.AutoFocusCallback cb, boolean capture_follows_autofocus_hint);
    /** See autoFocus() for details - used to update the capture_follows_autofocus_hint setting.
     */
    public abstract void setCaptureFollowAutofocusHint(boolean capture_follows_autofocus_hint);
    public abstract void cancelAutoFocus();
    public abstract void setContinuousFocusMoveCallback(ContinuousFocusMoveCallback cb);
    public abstract void takePicture(final CameraController.PictureCallback picture, final ErrorCallback error);
    public abstract void setDisplayOrientation(int degrees);
    public abstract int getDisplayOrientation();
    public abstract int getCameraOrientation();
    public enum Facing {
        FACING_BACK,
        FACING_FRONT,
        FACING_EXTERNAL,
        FACING_UNKNOWN // returned if the Camera API returned an error or an unknown type
    }
    /** Returns whether the camera is front, back or external.
     */
    public abstract Facing getFacing();
    public abstract void unlock();
    /** Call to initialise video recording, should call before MediaRecorder.prepare().
     * @param video_recorder The media recorder object.
     */
    public abstract void initVideoRecorderPrePrepare(MediaRecorder video_recorder);
    /** Call to initialise video recording, should call after MediaRecorder.prepare(), but before MediaRecorder.start().
     * @param video_recorder The media recorder object.
     * @param want_photo_video_recording Whether support for taking photos whilst video recording is required. If this feature isn't supported, the option has no effect.
     */
    public abstract void initVideoRecorderPostPrepare(MediaRecorder video_recorder, boolean want_photo_video_recording) throws CameraControllerException;
    public abstract String getParametersString();
    public boolean captureResultIsAEScanning() {
        return false;
    }
    /**
     * @return whether flash will fire; returns false if not known
     */
    public boolean needsFlash() {
        return false;
    }
    /**
     * @return whether front screen "flash" will fire; returns false if not known
     */
    public boolean needsFrontScreenFlash() {
        return false;
    }
    public boolean captureResultHasWhiteBalanceTemperature() {
        return false;
    }
    public int captureResultWhiteBalanceTemperature() {
        return 0;
    }
    public boolean captureResultHasIso() {
        return false;
    }
    public int captureResultIso() {
        return 0;
    }
    public boolean captureResultHasExposureTime() {
        return false;
    }
    public long captureResultExposureTime() {
        return 0;
    }
    public boolean captureResultHasFrameDuration() {
        return false;
    }
    public long captureResultFrameDuration() {
        return 0;
    }
    public boolean captureResultHasFocusDistance() {
        return false;
    }
    public float captureResultFocusDistance() {
        return 0.0f;
    }
    public boolean captureResultHasAperture() {
        return false;
    }
    public float captureResultAperture() {
        return 0.0f;
    }
	/*public boolean captureResultHasFocusDistance() {
		return false;
	}*/
	/*public float captureResultFocusDistanceMin() {
		return 0.0f;
	}*/
	/*public float captureResultFocusDistanceMax() {
		return 0.0f;
	}*/

    // gets the available values of a generic mode, e.g., scene, color etc, and makes sure the requested mode is available
    SupportedValues checkModeIsSupported(List<String> values, String value, String default_value) {
        if( values != null && values.size() > 1 ) { // n.b., if there is only 1 supported value, we also return null, as no point offering the choice to the user (there are some devices, e.g., Samsung, that only have a scene mode of "auto")
            if( MyDebug.LOG ) {
                for(int i=0;i<values.size();i++) {
                    Log.d(TAG, "supported value: " + values.get(i));
                }
            }
            // make sure result is valid
            if( !values.contains(value) ) {
                if( MyDebug.LOG )
                    Log.d(TAG, "value not valid!");
                if( values.contains(default_value) )
                    value = default_value;
                else
                    value = values.get(0);
                if( MyDebug.LOG )
                    Log.d(TAG, "value is now: " + value);
            }
            return new SupportedValues(values, value);
        }
        return null;
    }
}
