/*
 * Copyright 2009, The Android Open Source Project
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include "EmojiFactory.h"
#include "EmojiFont.h"
#include "SkCanvas.h"
#include "SkImageDecoder.h"
#include "SkPaint.h"
#include "SkTSearch.h"
#include "SkUtils.h"

#include "gmoji_pua_table.h"

namespace android {

// lazily allocate the factory
static EmojiFactory* get_emoji_factory() {
    static EmojiFactory* gEmojiFactory;
    if (NULL == gEmojiFactory) {
        gEmojiFactory = EmojiFactory::GetAvailableImplementation();
        // we may still be NULL, if there is no impl.
    }
    return gEmojiFactory;
}

#define UNINITIALIZED_ENCODE_SIZE   0   // our array is initialzed with 0s
#define NOT_AVAILABLE_ENCODE_SIZE   -1  // never a legal length for data

struct EncodeDataRec {
    SkBitmap*   fBitmap;
    const void* fData;
    int         fSize;
};

static EncodeDataRec gGmojiEncodeData[GMOJI_PUA_COUNT] = {};

/*  Given a local index, return (initialized if needed) a rec containing the
    encoded data and length. The bitmap field is initialized to 0, and is not
    filled in by this routine per-se.
 */
static EncodeDataRec* get_encoderec(int index) {
    if ((unsigned)index >= GMOJI_PUA_COUNT) {
        SkDebugf("bad index passed to EncodeDataRec& get_encode_data %d\n",
                 index);
        return NULL;
    }

    // lazily fill in the data
    EncodeDataRec* rec = &gGmojiEncodeData[index];

    if (NOT_AVAILABLE_ENCODE_SIZE == rec->fSize) {
        return NULL;
    }
    if (UNINITIALIZED_ENCODE_SIZE == rec->fSize) {
        EmojiFactory* fact = get_emoji_factory();
        if (NULL == fact) {
            return NULL;
        }

        int32_t pua = GMOJI_PUA_MIN + gGmojiPUA[index];
        rec->fData = fact->GetImageBinaryFromAndroidPua(pua, &rec->fSize);
        if (NULL == rec->fData) {
            // flag this entry is not available, so we won't ask again
            rec->fSize = NOT_AVAILABLE_ENCODE_SIZE;
            return NULL;
        }
    }
    return rec;
}

/*  Return the bitmap associated with the local index, or NULL if none is
    available. Note that this will try to cache the bitmap the first time it
    creates it.
 */
static const SkBitmap* get_bitmap(int index) {
    EncodeDataRec* rec = get_encoderec(index);
    SkBitmap* bitmap = NULL;
    if (rec) {
        bitmap = rec->fBitmap;
        if (NULL == bitmap) {
            bitmap = new SkBitmap;
            if (!SkImageDecoder::DecodeMemory(rec->fData, rec->fSize, bitmap)) {
                delete bitmap;
                // we failed, so mark us to not try again
                rec->fSize = NOT_AVAILABLE_ENCODE_SIZE;
                return NULL;
            }
            // cache the answer
            rec->fBitmap = bitmap;
            // todo: we never know if/when to let go of this cached bitmap
            // tho, since the pixels are managed separately, and are purged,
            // the "leak" may not be too important
        }
    }
    return bitmap;
}

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

bool EmojiFont::IsAvailable() {
    return get_emoji_factory() != NULL;
}

uint16_t EmojiFont::UnicharToGlyph(int32_t unichar) {
    // do a quick range check before calling the search routine
    if (unichar >= GMOJI_PUA_MIN && unichar <= GMOJI_PUA_MAX) {
        // our table is stored relative to GMOJI_PUA_MIN to save space (16bits)
        uint16_t relative = unichar - GMOJI_PUA_MIN;
        int index = SkTSearch<uint16_t>(gGmojiPUA, GMOJI_PUA_COUNT, relative,
                                        sizeof(uint16_t));
        // a negative value means it was not found
        if (index >= 0) {
            return index + kGlyphBase;
        }
        // fall through to return 0
    }
    // not a supported emoji pua
    return 0;
}

SkScalar EmojiFont::GetAdvanceWidth(uint16_t glyphID, const SkPaint& paint) {
    if (glyphID < kGlyphBase) {
        SkDebugf("-------- bad glyph passed to EmojiFont::GetAdvanceWidth %d\n",
                 glyphID);
        return 0;
    }

    const SkBitmap* bitmap = get_bitmap(glyphID - kGlyphBase);
    if (NULL == bitmap) {
        return 0;
    }

    // assume that our advance width is always the pointsize
    return paint.getTextSize();
}

/*  This tells us to shift the emoji bounds down by 20% below the baseline,
    to better align with the Kanji characters' placement in the line.
 */
static const SkScalar gBaselinePercentDrop = SkFloatToScalar(0.2f);
    
void EmojiFont::Draw(SkCanvas* canvas, uint16_t glyphID,
                     SkScalar x, SkScalar y, const SkPaint& paint) {
    if (glyphID < kGlyphBase) {
        SkDebugf("-------- bad glyph passed to EmojiFont::Draw %d\n", glyphID);
    }

    const SkBitmap* bitmap = get_bitmap(glyphID - kGlyphBase);
    if (bitmap && !bitmap->empty()) {
        SkRect dst;
        SkScalar size = paint.getTextSize();
        y += SkScalarMul(size, gBaselinePercentDrop);
        dst.set(x, y - size, x + size, y);
        canvas->drawBitmapRect(*bitmap, NULL, dst, &paint);
    }
}

}