/*
 * Copyright 2014 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 IMG_UTILS_TIFF_ENTRY_IMPL
#define IMG_UTILS_TIFF_ENTRY_IMPL

#include <img_utils/TiffIfd.h>
#include <img_utils/TiffEntry.h>
#include <img_utils/TiffHelpers.h>
#include <img_utils/Output.h>
#include <img_utils/EndianUtils.h>

#include <utils/Log.h>
#include <utils/Errors.h>
// #include <utils/Vector.h>
#include <utils/StrongPointer.h>
#include <stdint.h>
#include <vector>

namespace android {
namespace img_utils {

template<typename T>
class TiffEntryImpl : public TiffEntry {
    public:
        TiffEntryImpl(uint16_t tag, TagType type, uint32_t count, Endianness end, const T* data);
        virtual ~TiffEntryImpl();

        status_t writeData(uint32_t offset, /*out*/EndianOutput* out) const;
        status_t writeTagInfo(uint32_t offset, /*out*/EndianOutput* out) const;

        uint32_t getCount() const;
        uint16_t getTag() const;
        TagType getType() const;
        Endianness getEndianness() const;
        size_t getSize() const;
        uint32_t getComparableValue() const;

    protected:
        const void* getDataHelper() const;
        uint32_t getActualSize() const;

        uint16_t mTag;
        uint16_t mType;
        uint32_t mCount;
        Endianness mEnd;
        std::vector<T> mData;

};

template<typename T>
TiffEntryImpl<T>::TiffEntryImpl(uint16_t tag, TagType type, uint32_t count, Endianness end,
        const T* data)
        : mTag(tag), mType(static_cast<uint16_t>(type)), mCount(count), mEnd(end) {
    count = (type == RATIONAL || type == SRATIONAL) ? count * 2 : count;
    auto it = mData.insert(mData.end(), data, data + count);
    // LOG_ALWAYS_FATAL_IF(index < 0, "%s: Could not allocate vector for data.", __FUNCTION__);
}

template<typename T>
TiffEntryImpl<T>::~TiffEntryImpl() {}

template<typename T>
uint32_t TiffEntryImpl<T>::getCount() const {
    return mCount;
}

template<typename T>
uint16_t TiffEntryImpl<T>::getTag() const {
    return mTag;
}

template<typename T>
TagType TiffEntryImpl<T>::getType() const {
    return static_cast<TagType>(mType);
}

template<typename T>
const void* TiffEntryImpl<T>::getDataHelper() const {
    return reinterpret_cast<const void*>(&mData[0]);
}

template<typename T>
size_t TiffEntryImpl<T>::getSize() const {
    uint32_t total = getActualSize();
    WORD_ALIGN(total)
    return (total <= OFFSET_SIZE) ? 0 : total;
}

template<typename T>
uint32_t TiffEntryImpl<T>::getActualSize() const {
    uint32_t total = sizeof(T) * mCount;
    if (getType() == RATIONAL || getType() == SRATIONAL) {
        // 2 ints stored for each rational, multiply by 2
        total <<= 1;
    }
    return total;
}

template<typename T>
Endianness TiffEntryImpl<T>::getEndianness() const {
    return mEnd;
}

template<typename T>
uint32_t TiffEntryImpl<T>::getComparableValue() const {
    return mTag;
}

template<typename T>
status_t TiffEntryImpl<T>::writeTagInfo(uint32_t offset, /*out*/EndianOutput* out) const {
    assert((offset % TIFF_WORD_SIZE) == 0);
    status_t ret = OK;
    BAIL_ON_FAIL(out->write(&mTag, 0, 1), ret);
    BAIL_ON_FAIL(out->write(&mType, 0, 1), ret);
    BAIL_ON_FAIL(out->write(&mCount, 0, 1), ret);

    uint32_t dataSize = getActualSize();
    if (dataSize > OFFSET_SIZE) {
        BAIL_ON_FAIL(out->write(&offset, 0, 1), ret);
    } else {
        uint32_t count = mCount;
        if (getType() == RATIONAL || getType() == SRATIONAL) {
            /**
             * Rationals are stored as an array of ints.  Each
             * rational is represented by 2 ints.  To recover the
             * size of the array here, multiply the count by 2.
             */
            count <<= 1;
        }
        BAIL_ON_FAIL(out->write(&mData[0], 0, count), ret);
        ZERO_TILL_WORD(out, dataSize, ret);
    }
    return ret;
}

template<typename T>
status_t TiffEntryImpl<T>::writeData(uint32_t /*offset*/, EndianOutput* out) const {
    status_t ret = OK;

    // Some tags have fixed-endian value output
    Endianness tmp = UNDEFINED_ENDIAN;
    if (mEnd != UNDEFINED_ENDIAN) {
        tmp = out->getEndianness();
        out->setEndianness(mEnd);
    }

    uint32_t count = mCount;
    if (getType() == RATIONAL || getType() == SRATIONAL) {
        /**
         * Rationals are stored as an array of ints.  Each
         * rational is represented by 2 ints.  To recover the
         * size of the array here, multiply the count by 2.
         */
        count <<= 1;
    }

    BAIL_ON_FAIL(out->write(&mData[0], 0, count), ret);

    if (mEnd != UNDEFINED_ENDIAN) {
        out->setEndianness(tmp);
    }

    // Write to next word alignment
    ZERO_TILL_WORD(out, sizeof(T) * count, ret);
    return ret;
}

template<>
inline status_t TiffEntryImpl<sp<TiffIfd> >::writeTagInfo(uint32_t offset,
        /*out*/EndianOutput* out) const {
    assert((offset % TIFF_WORD_SIZE) == 0);
    status_t ret = OK;
    BAIL_ON_FAIL(out->write(&mTag, 0, 1), ret);
    BAIL_ON_FAIL(out->write(&mType, 0, 1), ret);
    BAIL_ON_FAIL(out->write(&mCount, 0, 1), ret);

    BAIL_ON_FAIL(out->write(&offset, 0, 1), ret);
    return ret;
}

template<>
inline uint32_t TiffEntryImpl<sp<TiffIfd> >::getActualSize() const {
    uint32_t total = 0;
    for (size_t i = 0; i < mData.size(); ++i) {
        total += mData[i]->getSize();
    }
    return total;
}

template<>
inline status_t TiffEntryImpl<sp<TiffIfd> >::writeData(uint32_t offset, EndianOutput* out) const {
    status_t ret = OK;
    for (uint32_t i = 0; i < mCount; ++i) {
        BAIL_ON_FAIL(mData[i]->writeData(offset, out), ret);
        offset += mData[i]->getSize();
    }
    return ret;
}

} /*namespace img_utils*/
} /*namespace android*/

#endif /*IMG_UTILS_TIFF_ENTRY_IMPL*/


