/*
 * 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"

namespace android {

namespace camera3 {

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

    /**
     * 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
    /**
     * Update lens calibration from capture results or equivalent
     */
    status_t updateCalibration(const CameraMetadata &result);

    /**
     * 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
     */
    status_t mapRawToCorrected(int32_t *coordPairs, int coordCount);

    /**
     * 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
     */
    status_t mapRawRectToCorrected(int32_t *rects, int rectCount);

    /**
     * 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
     */
    template<typename T>
    status_t mapCorrectedToRaw(T* coordPairs, int coordCount) 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
     */
    status_t mapCorrectedRectToRaw(int32_t *rects, int rectCount) const;

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

    // 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);

  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;

    // Metadata key lists to correct

    // Both capture request and result
    static const std::array<uint32_t, 3> kMeteringRegionsToCorrect;

    // Only capture request
    static const std::array<uint32_t, 1> kRequestRectsToCorrect;

    // Only capture result
    static const std::array<uint32_t, 1> kResultRectsToCorrect;

    // Only for capture results
    static const std::array<uint32_t, 2> kResultPointsToCorrect;

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


    bool mValidMapping;
    bool mValidGrids;

    // intrisic parameters, in pixels
    float mFx, mFy, mCx, mCy, mS;
    // pre-calculated inverses for speed
    float mInvFx, mInvFy;
    // radial/tangential distortion parameters
    float mK[5];

    // pre-correction active array dimensions
    int mArrayWidth, mArrayHeight;
    // active array dimensions
    int mActiveWidth, mActiveHeight;

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

}; // class DistortionMapper

} // namespace camera3

} // namespace android

#endif