/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANDROID_SERVERS_DISTORTIONMAPPER_H
#define ANDROID_SERVERS_DISTORTIONMAPPER_H

#include <utils/Errors.h>
#include <array>
#include <mutex>

#include "camera/CameraMetadata.h"
#include "device3/CoordinateMapper.h"

namespace android {

namespace camera3 {

/**
 * Utilities to transform between raw (distorted) and warped (corrected) coordinate systems
 * for cameras that support geometric distortion
 */
class DistortionMapper : public CoordinateMapper {
  public:
    DistortionMapper();

    DistortionMapper(const DistortionMapper& other) :
            mDistortionMapperInfo(other.mDistortionMapperInfo),
            mDistortionMapperInfoMaximumResolution(other.mDistortionMapperInfoMaximumResolution) {
            initRemappedKeys(); }

    void initRemappedKeys() override;

    /**
     * Check whether distortion correction is supported by the camera HAL
     */
    static bool isDistortionSupported(const CameraMetadata &deviceInfo);

    /**
     * Update static lens calibration info from camera characteristics
     */
    status_t setupStaticInfo(const CameraMetadata &deviceInfo);

    /**
     * Return whether distortion correction can be applied currently
     */
    bool calibrationValid() const;

    /**
     * Correct capture request if distortion correction is enabled
     */
    status_t correctCaptureRequest(CameraMetadata *request);

    /**
     * Correct capture result if distortion correction is enabled
     */
    status_t correctCaptureResult(CameraMetadata *request);


  public: // Visible for testing. Not guarded by mutex; do not use concurrently

    struct DistortionMapperInfo;

    /**
     * Update lens calibration from capture results or equivalent
     */
    status_t updateCalibration(const CameraMetadata &result, bool isStatic = false,
            bool maxResolution = false);

    /**
     * Transform from distorted (original) to corrected (warped) coordinates.
     * Coordinates are transformed in-place
     *
     *   coordPairs: A pointer to an array of consecutive (x,y) points
     *   coordCount: Number of (x,y) pairs to transform
     *   clamp: Whether to clamp the result to the bounds of the active array
     *   simple: Whether to do complex correction or just a simple linear map
     */
    status_t mapRawToCorrected(int32_t *coordPairs, int coordCount,
            DistortionMapperInfo *mapperInfo, bool clamp, bool simple = true);

    /**
     * Transform from distorted (original) to corrected (warped) coordinates.
     * Coordinates are transformed in-place
     *
     *   rects: A pointer to an array of consecutive (x,y, w, h) rectangles
     *   rectCount: Number of rectangles to transform
     *   clamp: Whether to clamp the result to the bounds of the active array
     *   simple: Whether to do complex correction or just a simple linear map
     */
    status_t mapRawRectToCorrected(int32_t *rects, int rectCount,
          DistortionMapperInfo *mapperInfo, bool clamp, bool simple = true);

    /**
     * Transform from corrected (warped) to distorted (original) coordinates.
     * Coordinates are transformed in-place
     *
     *   coordPairs: A pointer to an array of consecutive (x,y) points
     *   coordCount: Number of (x,y) pairs to transform
     *   clamp: Whether to clamp the result to the bounds of the precorrection active array
     *   simple: Whether to do complex correction or just a simple linear map
     */
    status_t mapCorrectedToRaw(int32_t* coordPairs, int coordCount,
            const DistortionMapperInfo *mapperInfo, bool clamp, bool simple = true) const;

    /**
     * Transform from corrected (warped) to distorted (original) coordinates.
     * Coordinates are transformed in-place
     *
     *   rects: A pointer to an array of consecutive (x,y, w, h) rectangles
     *   rectCount: Number of rectangles to transform
     *   clamp: Whether to clamp the result to the bounds of the precorrection active array
     *   simple: Whether to do complex correction or just a simple linear map
     */
    status_t mapCorrectedRectToRaw(int32_t *rects, int rectCount,
           const DistortionMapperInfo *mapperInfo, bool clamp, bool simple = true) const;

    struct GridQuad {
        // Source grid quad, or null
        const GridQuad *src;
        // x,y coordinates of corners, in
        // clockwise order
        std::array<float, 8> coords;
    };

    struct DistortionMapperInfo {
        bool mValidMapping = false;
        bool mValidGrids = false;

        // intrisic parameters, in pixels
        float mFx, mFy, mCx, mCy, mS;
        // pre-calculated inverses for speed
        float mInvFx, mInvFy;
        // radial/tangential distortion parameters
        std::array<float, 5> mK;

        // pre-correction active array dimensions
        float mArrayWidth, mArrayHeight;
        // active array dimensions
        float mActiveWidth, mActiveHeight;
        // corner offsets between pre-correction and active arrays
        float mArrayDiffX, mArrayDiffY;

        std::vector<GridQuad> mCorrectedGrid;
        std::vector<GridQuad> mDistortedGrid;
    };

    // Find which grid quad encloses the point; returns null if none do
    static const GridQuad* findEnclosingQuad(
            const int32_t pt[2], const std::vector<GridQuad>& grid);

    // Calculate 'horizontal' interpolation coordinate for the point and the quad
    // Assumes the point P is within the quad Q.
    // Given quad with points P1-P4, and edges E12-E41, and considering the edge segments as
    // functions of U: E12(u), where E12(0) = P1 and E12(1) = P2, then we want to find a u
    // such that the edge E12(u) -> E43(u) contains point P.
    // This can be determined by checking if the cross product of vector [E12(u)-E43(u)] and
    // vector [E12(u)-P] is zero. Solving the equation
    // [E12(u)-E43(u)] x [E12(u)-P] = 0 gives a quadratic equation in u; the solution in the range
    // 0 to 1 is the one chosen.
    // If calculateU is true, then an interpolation coordinate for edges E12 and E43 is found;
    // if it is false, then an interpolation coordinate for edges E14 and E23 is found.
    static float calculateUorV(const int32_t pt[2], const GridQuad& quad, bool calculateU);

    DistortionMapperInfo *getMapperInfo(bool maxResolution = false) {
          return maxResolution ? &mDistortionMapperInfoMaximumResolution :
                  &mDistortionMapperInfo;
    };

  private:
    mutable std::mutex mMutex;

    // Number of quads in each dimension of the mapping grids
    constexpr static size_t kGridSize = 15;
    // Margin to expand the grid by to ensure it doesn't clip the domain
    constexpr static float kGridMargin = 0.05f;
    // Fuzziness for float inequality tests
    constexpr static float kFloatFuzz = 1e-4;

    bool mMaxResolution = false;

    status_t setupStaticInfoLocked(const CameraMetadata &deviceInfo, bool maxResolution);

    // Single implementation for various mapCorrectedToRaw methods
    template<typename T>
    status_t mapCorrectedToRawImpl(T* coordPairs, int coordCount,
            const DistortionMapperInfo *mapperInfo, bool clamp, bool simple) const;

    // Simple linear interpolation option
    template<typename T>
    status_t mapCorrectedToRawImplSimple(T* coordPairs, int coordCount,
            const DistortionMapperInfo *mapperInfo, bool clamp) const;

    status_t mapRawToCorrectedSimple(int32_t *coordPairs, int coordCount,
            const DistortionMapperInfo *mapperInfo, bool clamp) const;

    // Utility to create reverse mapping grids
    status_t buildGrids(DistortionMapperInfo *mapperInfo);

    DistortionMapperInfo mDistortionMapperInfo;
    DistortionMapperInfo mDistortionMapperInfoMaximumResolution;

}; // class DistortionMapper

} // namespace camera3

} // namespace android

#endif
