/*
 * Copyright 2007, 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.
 */
 
#include "SkImageDecoder.h"
#include "SkScaledBitmapSampler.h"
#include "SkStream.h"
#include "SkColorPriv.h"
#include "SkTDArray.h"

#include "fpdfemb.h"

class SkFPDFEMBImageDecoder : public SkImageDecoder {
public:
    SkFPDFEMBImageDecoder() {}
    
    virtual Format getFormat() const {
        return kBMP_Format;
    }

protected:
    virtual bool onDecode(SkStream* stream, SkBitmap* bm,
                          SkBitmap::Config pref, Mode mode);

private:
    bool render(FPDFEMB_PAGE page, const FPDFEMB_RECT& bounds, SkBitmap* bm,
                SkBitmap::Config prefConfig, SkImageDecoder::Mode mode);
};

SkImageDecoder* SkImageDecoder_FPDFEMB_Factory(SkStream*);
SkImageDecoder* SkImageDecoder_FPDFEMB_Factory(SkStream* stream) {
    static const char kPDFSig[] = { '%', 'P', 'D', 'F' };
    
    size_t len = stream->getLength();
    char buffer[sizeof(kPDFSig)];
    
    SkDebugf("---- SkImageDecoder_FPDFEMB_Factory len=%d\n", len);
    
    if (len != 12683) { return NULL; }

    if (len > sizeof(kPDFSig) &&
            stream->read(buffer, sizeof(kPDFSig)) == sizeof(kPDFSig) &&
            !memcmp(buffer, kPDFSig, sizeof(kPDFSig))) {
        return SkNEW(SkFPDFEMBImageDecoder);
    }
    return NULL;
}

///////////////////////////////////////////////////////////////////////////////

extern "C" {
    static void* pdf_alloc(FPDFEMB_MEMMGR* pMgr, unsigned int size) {
        void* addr = sk_malloc_throw(size);
 //       SkDebugf("---- pdf_alloc %d %p\n", size, addr);
        return addr;
    }

    static void* pdf_alloc_nl(FPDFEMB_MEMMGR* pMgr, unsigned int size) {
        void* addr = sk_malloc_flags(size, 0);
 //       SkDebugf("---- pdf_alloc_nl %d %p\n", size, addr);
        return addr;
    }

    static void* pdf_realloc(FPDFEMB_MEMMGR*, void* addr, unsigned int size) {
        void* newaddr = sk_realloc_throw(addr, size);
 //       SkDebugf("---- pdf_realloc %p %d %p\n", addr, size, newaddr);
        return newaddr;
    }

    static void pdf_free(FPDFEMB_MEMMGR* pMgr, void* pointer) {
 //       SkDebugf("---- pdf_free %p\n", pointer);
        sk_free(pointer);
    }

    void FX_OUTPUT_LOG_FUNC(const char* format, ...) {
        SkDebugf("---- LOG_FUNC %s\n", format);
    }
    
    static unsigned int file_getsize(FPDFEMB_FILE_ACCESS* file) {
        SkStream* stream = (SkStream*)file->user;
        return stream->getLength();
    }
    
    static FPDFEMB_RESULT file_readblock(FPDFEMB_FILE_ACCESS* file, void* dst,
                                    unsigned int offset, unsigned int size) {
        SkStream* stream = (SkStream*)file->user;
//        SkDebugf("---- readblock %p %p %d %d\n", stream, dst, offset, size);
        if (!stream->rewind()) {
            SkDebugf("---- rewind failed\n");
            return FPDFERR_ERROR;
        }
        if (stream->skip(offset) != offset) {
            SkDebugf("---- skip failed\n");
            return FPDFERR_ERROR;
        }
        if (stream->read(dst, size) != size) {
            SkDebugf("---- read failed\n");
            return FPDFERR_ERROR;
        }
        return FPDFERR_SUCCESS;
    }

    static void pdf_oom_handler(void* memory, int size) {
        SkDebugf("======== pdf OOM %p %d\n", memory, size);
    }
}

static inline int PDF2Pixels(int x) { return x / 100; }
static inline SkScalar PDF2Scalar(int x) {
    return SkScalarMulDiv(SK_Scalar1, x, 100);
}

bool SkFPDFEMBImageDecoder::render(FPDFEMB_PAGE page, const FPDFEMB_RECT& bounds, SkBitmap* bm,
                   SkBitmap::Config prefConfig, SkImageDecoder::Mode mode) {
    int width = PDF2Pixels(bounds.right - bounds.left);
    int height = PDF2Pixels(bounds.top - bounds.bottom);

    SkDebugf("----- bitmap size [%d %d], mode=%d\n", width, height, mode);
    bm->setConfig(SkBitmap::kARGB_8888_Config, width, height);
    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
        return true;
    }
    
    // USE THE CODEC TO ALLOCATE THE PIXELS!!!!
    if (!this->allocPixelRef(bm, NULL)) {
        SkDebugf("----- failed to alloc pixels\n");
        return false;
    }

    bm->eraseColor(0);
    
    FPDFEMB_RESULT result;
    FPDFEMB_BITMAP dib;

    result = FPDFEMB_CreateDIB(width, height, FPDFDIB_BGRA, bm->getPixels(),
                               bm->rowBytes(), &dib);
    SkDebugf("---- createdib %d\n", result);

    result = FPDFEMB_StartRender(dib, page, 0, 0, width, height, 0, 0, NULL, NULL);
    SkDebugf("---- render %d\n", result);
    
    result = FPDFEMB_DestroyDIB(dib);
    SkDebugf("---- destroydib %d\n", result);
    
    SkPMColor* dst = bm->getAddr32(0, 0);
    const uint8_t* src = (uint8_t*)dst;
    int n = bm->getSize() >> 2;
    for (int i = 0; i < n; i++) {
        int b = *src++;
        int g = *src++;
        int r = *src++;
        int a = *src++;
        *dst++ = SkPackARGB32(a, r, g, b);
    }

    return true;
}

#define USE_FIXED_MEM   (4 * 1024 * 1024)

bool SkFPDFEMBImageDecoder::onDecode(SkStream* stream, SkBitmap* bm,
                                 SkBitmap::Config prefConfig, Mode mode) {

    FPDFEMB_RESULT result;
#ifdef USE_FIXED_MEM
    SkAutoMalloc storage(USE_FIXED_MEM);
    result = FPDFEMB_InitFixedMemory(storage.get(), USE_FIXED_MEM,
                                     pdf_oom_handler);
#else
    FPDFEMB_MEMMGR  memmgr;
    memmgr.Alloc = pdf_alloc;
    memmgr.AllocNL = pdf_alloc_nl;
    memmgr.Realloc = pdf_realloc;
    memmgr.Free = pdf_free;

    result = FPDFEMB_Init(&memmgr);
#endif
    SkDebugf("----- SkImageDecoder_FPDFEMB_Factory init %d, streamLen = %d\n", result, stream->getLength());

    FPDFEMB_FILE_ACCESS file;
    file.GetSize = file_getsize;
    file.ReadBlock = file_readblock;
    file.user = stream;

    FPDFEMB_DOCUMENT document;
    result = FPDFEMB_StartLoadDocument(&file, NULL, &document, NULL);
    SkDebugf("----- SkImageDecoder_FPDFEMB_Factory open %d %p\n", result, document);

    int pageCount = FPDFEMB_GetPageCount(document);
    SkDebugf("----- SkImageDecoder_FPDFEMB_Factory pageCount %d\n", pageCount);

    if (pageCount > 0) {
        FPDFEMB_PAGE page;
        result = FPDFEMB_LoadPage(document, 0, &page);
        SkDebugf("----- SkImageDecoder_FPDFEMB_Factory load page %d\n", result);

        int width, height;
        result = FPDFEMB_GetPageSize(page, &width, &height);
        SkDebugf("----- SkImageDecoder_FPDFEMB_Factory page size %d [%d %d]\n", result, width, height);

        FPDFEMB_RECT rect;
        result = FPDFEMB_GetPageBBox(page, &rect);
        SkDebugf("----- SkImageDecoder_FPDFEMB_Factory page rect %d [%d %d %d %d]\n", result,
                 rect.left, rect.top, rect.right, rect.bottom);

        SkDebugf("----- SkImageDecoder_FPDFEMB_Factory begin page parse...\n");
        result = FPDFEMB_StartParse(page, false, NULL);
        SkDebugf("----- SkImageDecoder_FPDFEMB_Factory page parse %d\n", result);

        if (0 == result) {
            this->render(page, rect, bm, prefConfig, mode);
        }

        result = FPDFEMB_ClosePage(page);
        SkDebugf("----- SkImageDecoder_FPDFEMB_Factory close page %d\n", result);
    }
    
    result = FPDFEMB_CloseDocument(document);
    SkDebugf("----- SkImageDecoder_FPDFEMB_Factory close %d\n", result);

 //   FPDFEMB_Exit();

    return true;    
}