//#define LOG_NDEBUG 0
#define LOG_TAG "DngCreator_JNI"
#include <inttypes.h>
#include <string.h>
#include <algorithm>
#include <array>
#include <memory>
#include <vector>
#include <cmath>
#include <algorithm>

#include <camera/NdkCameraMetadata.h>
#include <img_utils/DngUtils.h>
#include <img_utils/TagDefinitions.h>
#include <img_utils/TiffIfd.h>
#include <img_utils/TiffWriter.h>
#include <img_utils/Output.h>
#include <img_utils/Input.h>
#include <img_utils/StripSource.h>
#include <sys/system_properties.h>

// #include "core_jni_helpers.h"

// #include "android_runtime/AndroidRuntime.h"
// #include "android_runtime/android_hardware_camera2_CameraMetadata.h"

#include <jni.h>
// #include <nativehelper/JNIHelp.h>

using namespace android;
using namespace img_utils;
// using android::base::GetProperty;


/**
     * Max width or height dimension for thumbnails.
     */
// max pixel dimension for TIFF/EP
#define MAX_THUMBNAIL_DIMENSION 256


// bytes per sample
#define DEFAULT_PIXEL_STRIDE 2
// byts per pixel
#define BYTES_PER_RGB_PIX 3


#define GPS_LAT_REF_NORTH "N"
#define GPS_LAT_REF_SOUTH "S"
#define GPS_LONG_REF_EAST "E"
#define GPS_LONG_REF_WEST "W"

#define GPS_DATE_FORMAT_STR "yyyy:MM:dd"
#define TIFF_DATETIME_FORMAT "yyyy:MM:dd kk:mm:ss"

class ByteVectorOutput : public Output {
public:
    ByteVectorOutput(std::vector<uint8_t>& buf);
    virtual ~ByteVectorOutput();

    virtual status_t open();

    virtual status_t write(const uint8_t* buf, size_t offset, size_t count);

    virtual status_t close();

protected:
    std::vector<uint8_t>& m_buf;
};

class ByteVectorInput : public Input {
public:
    ByteVectorInput(const std::vector<uint8_t>& buf);
    virtual ~ByteVectorInput();

    /**
     * Open this Input.
     *
     * Returns OK on success, or a negative error code.
     */
    status_t open();

    /**
     * Read bytes into the given buffer.  At most, the number of bytes given in the
     * count argument will be read.  Bytes will be written into the given buffer starting
     * at the index given in the offset argument.
     *
     * Returns the number of bytes read, or NOT_ENOUGH_DATA if at the end of the file.  If an
     * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA.
     */
    ssize_t read(uint8_t* buf, size_t offset, size_t count);

    /**
     * Skips bytes in the input.
     *
     * Returns the number of bytes skipped, or NOT_ENOUGH_DATA if at the end of the file.  If an
     * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA.
     */
    ssize_t skip(size_t count);

    /**
     * Close the Input.  It is not valid to call open on a previously closed Input.
     *
     * Returns OK on success, or a negative error code.
     */
    status_t close();

protected:
    const std::vector<uint8_t>& m_buf;
    size_t m_offset;
};

class ByteBufferInput : public Input {
public:
    ByteBufferInput(const uint8_t* buf, size_t len);
    virtual ~ByteBufferInput();

    /**
     * Open this Input.
     *
     * Returns OK on success, or a negative error code.
     */
    status_t open();

    /**
     * Read bytes into the given buffer.  At most, the number of bytes given in the
     * count argument will be read.  Bytes will be written into the given buffer starting
     * at the index given in the offset argument.
     *
     * Returns the number of bytes read, or NOT_ENOUGH_DATA if at the end of the file.  If an
     * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA.
     */
    ssize_t read(uint8_t* buf, size_t offset, size_t count);

    /**
     * Skips bytes in the input.
     *
     * Returns the number of bytes skipped, or NOT_ENOUGH_DATA if at the end of the file.  If an
     * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA.
     */
    ssize_t skip(size_t count);

    /**
     * Close the Input.  It is not valid to call open on a previously closed Input.
     *
     * Returns OK on success, or a negative error code.
     */
    status_t close();

protected:
    const uint8_t* m_buf;
    size_t m_len;
    size_t m_offset;
};

struct SIZE
{
    int width;
    int height;
};

#define BAIL_IF_INVALID_RET_BOOL(expr, jnienv, tagId, writer) \
    if ((expr) != OK) { \
        return false; \
    }

#define BAIL_IF_INVALID_RET_NULL_SP(expr, jnienv, tagId, writer) \
    if ((expr) != OK) {                                          \
        return nullptr; \
    }

#define BAIL_IF_INVALID_R(expr, jnienv, tagId, writer) \
    if ((expr) != OK) {                                \
        return -1; \
    }

#define BAIL_IF_EMPTY_RET_NULL_SP(entry, jnienv, tagId, writer) \
    if ((entry).count == 0) { \
        return nullptr; \
    }

#define BAIL_IF_EXPR_RET_NULL_SP(expr, jnienv, tagId, writer) \
    if (expr) { \
        return nullptr; \
    }

#define ANDROID_DNGCREATOR_CTX_JNI_ID     "mNativeContext"

enum {
    BITS_PER_SAMPLE = 16,
    BYTES_PER_SAMPLE = 2,
    BYTES_PER_RGB_PIXEL = 3,
    BITS_PER_RGB_SAMPLE = 8,
    BYTES_PER_RGB_SAMPLE = 1,
    SAMPLES_PER_RGB_PIXEL = 3,
    SAMPLES_PER_RAW_PIXEL = 1,
    TIFF_IFD_0 = 0,
    TIFF_IFD_SUB1 = 1,
    TIFF_IFD_GPSINFO = 2,
};

/**
 * POD container class for GPS tag data.
 */
class GpsData {
public:
    enum {
        GPS_VALUE_LENGTH = 6,
        GPS_REF_LENGTH = 2,
        GPS_DATE_LENGTH = 11,
    };

    uint32_t mLatitude[GPS_VALUE_LENGTH];
    uint32_t mLongitude[GPS_VALUE_LENGTH];
    uint32_t mTimestamp[GPS_VALUE_LENGTH];
    uint8_t mLatitudeRef[GPS_REF_LENGTH];
    uint8_t mLongitudeRef[GPS_REF_LENGTH];
    uint8_t mDate[GPS_DATE_LENGTH];
};

// ----------------------------------------------------------------------------

/**
 * Container class for the persistent native context.
 */

class NativeContext : public LightRefBase<NativeContext> {
public:
    enum {
        DATETIME_COUNT = 20,
    };

    NativeContext(ACameraMetadata* characteristics, ACameraMetadata* result);
    virtual ~NativeContext();

    TiffWriter* getWriter();

    ACameraMetadata* getCharacteristics() const;
    ACameraMetadata* getResult() const;

    uint32_t getThumbnailWidth() const;
    uint32_t getThumbnailHeight() const;
    const uint8_t* getThumbnail() const;
    bool hasThumbnail() const;

    bool setThumbnail(const std::vector<uint8_t>& buffer, uint32_t width, uint32_t height);

    void setOrientation(uint16_t orientation);
    uint16_t getOrientation() const;

    void setDescription(const std::string& desc);
    std::string getDescription() const;
    bool hasDescription() const;

    void setGpsData(const GpsData& data);
    GpsData getGpsData() const;
    bool hasGpsData() const;

    void setCaptureTime(const std::string& formattedCaptureTime);
    std::string getCaptureTime() const;
    bool hasCaptureTime() const;

protected:
    std::vector<uint8_t> mCurrentThumbnail;
    TiffWriter mWriter;
    ACameraMetadata* mCharacteristics;
    ACameraMetadata* mResult;
    uint32_t mThumbnailWidth;
    uint32_t mThumbnailHeight;
    uint16_t mOrientation;
    bool mThumbnailSet;
    bool mGpsSet;
    bool mDescriptionSet;
    bool mCaptureTimeSet;
    std::string mDescription;
    GpsData mGpsData;
    std::string mFormattedCaptureTime;
};

class DngCreator : public NativeContext
{

public:
    DngCreator(ACameraMetadata* characteristics, ACameraMetadata* result);

#if 0
    void setLocation(Location location);
#endif

    void writeInputStream(std::vector<uint8_t>& dngOutput, SIZE size, const std::vector<uint8_t>& pixels, long offset);
    void writeByteBuffer(std::vector<uint8_t>& dngOutput, SIZE size, const std::vector<uint8_t>& pixels, long offset);

#if 0
    void writeImage(OutputStream& dngOutput, AImage& pixels);
#endif

    void close();

    // private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
    // private static final DateFormat sDateTimeStampFormat = new SimpleDateFormat(TIFF_DATETIME_FORMAT);
#if 0
    static {
        sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
        sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC"));
    }
#endif

    /**
     * Offset, rowStride, and pixelStride are given in bytes.  Height and width are given in pixels.
     */
    void writeByteBuffer(int width, int height, const std::vector<uint8_t>& pixels, std::vector<uint8_t>& dngOutput, int pixelStride, int rowStride, long offset);

    /**
     * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}.
     */

    /**
     * Convert coordinate to EXIF GPS tag format.
     */
    void toExifLatLong(double value, int data[6]);

    void init(ACameraMetadata* characteristics, ACameraMetadata* result, const std::string& captureTime);
    sp<TiffWriter> setup(uint32_t imageWidth, uint32_t imageHeight);
    void destroy();
    void setGpsTags(const std::vector<int>& latTag, const std::string& latRef, const std::vector<int>& longTag, const std::string& longRef, const std::string& dateTag, const std::vector<int>& timeTag);
    void writeImage(std::vector<uint8_t>& out, uint32_t width, uint32_t height, const std::vector<uint8_t>& rawBuffer, int rowStride, int pixStride, uint64_t offset, bool isDirect);

    void writeInputStream(std::vector<uint8_t>& out, const std::vector<uint8_t>& rawStream, uint32_t width, uint32_t height, long offset);

    void writeInputBuffer(std::vector<uint8_t>& out, const uint8_t* rawBuffer, size_t bufferLen, uint32_t width, uint32_t height, long offset);

};
