/*
 * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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 APPLE COMPUTER, INC. ``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 "BitmapImage.h"
#include "FloatRect.h"
#include "ImageObserver.h"
#include "IntRect.h"
#include "PlatformString.h"
#include "Timer.h"
#include <wtf/Vector.h>
#include <wtf/HashMap.h>
#include "GraphicsContext.h"
#include "WebCoreGraphicsContext.h"
#include "bitmap/MaskedBitmap.h"
#include "bitmap/AnimationDecoder.h"
#include "String.h"
#include "StringHash.h"
#include "DebugStream.h"
#include "ZoomFactor.h"

#include <data_caging_path_literals.hrh>

// work around bidi.h conflict
#include <../bidi.h>
#include <AknsConstants.h>
#include <AknsUtils.h>
#include <AknsItemID.h>
#include <AknsConstants.h>
#include <eikenv.h>

#ifdef __GCCE__
#include <WebKitIcons_gcce.mbg>
#else
#include <WebKitIcons.mbg>
#endif

namespace WebCore {

CMaskedBitmap* loadAknIcon(int id, float sizeAdjust);

void FrameData::clear()
{
    if (m_frame) {
        // FIXME: should come up with a better mechanism to handle the lifetime
        // of native bitmaps.  Maybe ref-counting?
        // Caching frames anyway is a bad-idea for Symbian OS, because it consumes
        // a lot of memory for long gif animations.

        delete m_frame;
        m_frame = 0;
        m_duration = 0.;
        m_hasAlpha = true;
    }
}

BitmapImage::BitmapImage(ImageObserver* observer)
    : Image(observer)
    , m_currentFrame(0)
    , m_frames(0)
    , m_frameTimer(0)
    , m_repetitionCount(0)
    , m_repetitionsComplete(0)
    , m_maskedBitmap( 0 )
    , m_isSolidColor(false)
    , m_animatingImageType(true)
    , m_animationFinished(false)
    , m_allDataReceived(false)
    , m_haveSize(false)
    , m_sizeAvailable(false)
    , m_decodedSize(0)
{
    initPlatformData();

    m_frames.clear();
    m_source.setDecoderListener( observer );
}

BitmapImage::BitmapImage(CMaskedBitmap* bitmap)
    : m_currentFrame(0) 
    , m_frameTimer(0)
    , m_repetitionCount(0)
    , m_repetitionsComplete(0)
    , m_maskedBitmap( bitmap )
    , m_imageObserver(0)
    , m_isSolidColor(false)
    , m_animatingImageType(true)
    , m_animationFinished(false)
    , m_allDataReceived(true)
    , m_haveSize(false)
    , m_sizeAvailable(false)
    , m_decodedSize(0)
{
    initPlatformData();

    // setup the image frames
    m_frames.resize(1);
    m_frames[0].m_frame = bitmap;
    m_haveSize = true;
    m_sizeAvailable = true;
    m_size = IntSize(bitmap->SizeInPixels());
}

BitmapImage::~BitmapImage()
{
    destroyDecodedData();
    stopAnimation();
    invalidatePlatformData();
}

void BitmapImage::destroyDecodedData(bool)
{
    // Destroy the cached images and release them, 
    // only cache one image in Symbian port.
    if (m_frames.size()) {
        m_frames.last().clear();
        invalidatePlatformData();
    }
}

IntSize BitmapImage::size() const
{
    if (m_sizeAvailable && !m_haveSize) {
        m_size = m_source.size();
        m_haveSize = true;
    }
    return m_size;
}

void BitmapImage::setMimeType(const String& mimeType)
{
    m_source.setMimeType(mimeType);    
}

const String& BitmapImage::getMimeType()
{
    return m_source.getMimeType();    
}

bool BitmapImage::dataChanged(bool allDataReceived)
{
    destroyDecodedData(true);
    
    // Feed all the data we've seen so far to the image decoder.
    m_allDataReceived = allDataReceived;
    m_source.setData(m_data.get(), allDataReceived);
    
    // Image properties will not be available until the first frame of the file
    // reaches kCGImageStatusIncomplete.
    return isSizeAvailable();
}

size_t BitmapImage::frameCount()
{
    if (m_frames.size()==1 && !m_source.initialized())
        return 1;
    return m_source.frameCount();
}

bool BitmapImage::isSizeAvailable()
{
    if (m_sizeAvailable)
        return true;

    m_sizeAvailable = m_source.isSizeAvailable();

    return m_sizeAvailable;

}

// don't cache the frame
NativeImagePtr BitmapImage::frameAtIndex(size_t index)
{
    if (index >= frameCount())
        return 0;

    if (index < m_frames.size())
        return m_frames[index].m_frame;

    return m_source.createFrameAtIndex(index);
}

float BitmapImage::frameDurationAtIndex(size_t index)
{
    if (index >= frameCount())
        return 0;

    // in millseconds
    return m_source.frameDurationAtIndex(index);
}

bool BitmapImage::frameHasAlphaAtIndex(size_t index)
{
    if (index >= frameCount())
        return 0;

    return m_source.frameHasAlphaAtIndex(index);
}

bool BitmapImage::shouldAnimate()
{
    return (m_animatingImageType && frameCount() > 1 && !m_animationFinished && m_imageObserver);
}

void BitmapImage::startAnimation()
{
    if (m_frameTimer || !shouldAnimate() || frameCount() <= 1)
        return;

    m_frameTimer = new Timer<BitmapImage>(this, &BitmapImage::advanceAnimation);
    m_frameTimer->startOneShot(frameDurationAtIndex(m_currentFrame));
}

void BitmapImage::stopAnimation()
{
    // This timer is used to animate all occurrences of this BitmapImage.  Don't invalidate
    // the timer unless all renderers have stopped drawing.
    delete m_frameTimer;
    m_frameTimer = 0;
}

void BitmapImage::resetAnimation()
{
    stopAnimation();
    m_currentFrame = 0;
    m_repetitionsComplete = 0;
    m_animationFinished = false;
}

void BitmapImage::advanceAnimation(Timer<BitmapImage>* timer)
{
    // Stop the animation.
    stopAnimation();

    // See if anyone is still paying attention to this animation.  If not, we don't
    // advance and will simply pause the animation.
    if (imageObserver() && imageObserver()->shouldPauseAnimation(this))
        return;

    m_currentFrame++;
	m_repetitionCount =  m_source.decoder()->getLoopCount(); 
    if(m_repetitionCount == -1) m_repetitionCount++;
    if (m_currentFrame >= frameCount()) {
        m_repetitionsComplete += 1;
        if (m_repetitionCount && m_repetitionsComplete >= m_repetitionCount) {
            m_animationFinished = false;
            m_currentFrame--;
            return;
        }
        m_currentFrame = 0;
    }

    m_source.decoder()->LoadFrame( m_currentFrame );
}

void BitmapImage::initPlatformData()
{
}

void BitmapImage::invalidatePlatformData()
{
    // tot fixme: we need to flash the image data here.
}

struct IconData {
    int m_id;
    float m_sizeAdjust;
};

const IconData missingImageData = { EMbmWebkiticonsQgn_indi_wml_image_not_shown, 1.2 };
const IconData missingObjectData = { EMbmWebkiticonsQgn_prop_wml_object, 1.2 };
const IconData selectArrowData = { EMbmWebkiticonsQgn_prop_wml_selectarrow, 0.9 };
const IconData selectFileData = { EMbmWebkiticonsQgn_prop_wml_selectfile, 0.9 };
const IconData radioButtonOnData = { EMbmWebkiticonsQgn_prop_wml_radiobutt_on, 0.9 };
const IconData radioButtonOffData = { EMbmWebkiticonsQgn_prop_wml_radiobutt_off, 0.9 };
const IconData checkBoxOnData = { EMbmWebkiticonsQgn_prop_wml_checkbox_on, 0.9 };
const IconData checkBoxOffData = { EMbmWebkiticonsQgn_prop_wml_checkbox_off, 0.9 };

Image* Image::loadPlatformResource(const char *name)
{
    static HashMap<String,const IconData*> resourceIds;
    if (resourceIds.isEmpty()) {
        resourceIds.set("missingImage", &missingImageData);
        resourceIds.set("missingObject", &missingObjectData );
        resourceIds.set("selectArrow", &selectArrowData);
        resourceIds.set("selectFile", &selectFileData);
        resourceIds.set("radioButtonOn", &radioButtonOnData);
        resourceIds.set("radioButtonOff", &radioButtonOffData);
        resourceIds.set("checkBoxOn", &checkBoxOnData);
        resourceIds.set("checkBoxOff", &checkBoxOffData);
    }
    if (const IconData* data = resourceIds.get(name)) {
        CMaskedBitmap* bm = loadAknIcon(data->m_id,data->m_sizeAdjust);
        if (bm) {
            BitmapImage* im = new BitmapImage(bm);
            return im;
        }
    }
    return 0;
}

void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& dst, const FloatRect& src, CompositeOperator op)
{
    WebCoreGraphicsContext* context = ctxt->platformContext();
	if (!context) {
          return;
	}

    CMaskedBitmap* bm = frameAtIndex(m_currentFrame);
    if (!bm) // If it's too early we won't have an BitmapImage yet.
        return;

    TRect target(enclosingIntRect(dst));
    TRect source(enclosingIntRect(src));

    bm->DrawBitmap(context->gc(), xForm(target), source);

    startAnimation();

}

#ifdef __GCCE__
_LIT( KBrowserSvgFile, "Webkiticons_gcce.mif" );
#else
_LIT( KBrowserSvgFile, "webkiticons.mif" );
#endif

TPtrC iconFileName()
{
    static HBufC* filename = 0;
    if (!filename) {
        TFileName mbmDrive;
        TParse parse;
        Dll::FileName( mbmDrive );
        Dll::FileName( mbmDrive );
        parse.Set( mbmDrive, NULL, NULL );
        mbmDrive = parse.Drive();
        filename = HBufC::New( KMaxFileName );
        filename->Des().Append( mbmDrive );
        filename->Des().Append( KDC_APP_BITMAP_DIR );
        filename->Des().Append( KBrowserSvgFile );
    }
    return *filename;
}

CMaskedBitmap* loadAknIcon(int id, float sizeAdjust)
{
    CEikonEnv* eikEnv = CEikonEnv::Static();
    CWsScreenDevice& screenDev = *eikEnv->ScreenDevice();

    int dpi = screenDev.VerticalTwipsToPixels(KTwipsPerInch);
    int px = (int)10*sizeAdjust;
    // dpi adjustments
    if ((200 > dpi) && (dpi >= 120))
        px *= 1.7;
    else if ((210 > dpi) && (dpi >= 200))
        px *= 2.1;
    else if ((400 > dpi) && (dpi >= 210))
        px *= 2.2;
    else if (dpi >= 400)
        px *= 4.2;

    CFbsBitmap* bitmap=0;
    CFbsBitmap* mask=0;
    MAknsSkinInstance* skin = AknsUtils::SkinInstance();
    TRAPD(error, AknsUtils::CreateIconL( skin,
                                   KAknsIIDDefault,
                                   bitmap,
                                   mask,
                                   iconFileName(),
                                   id,
                                   id+1));
    if (error!=KErrNone)
        return 0;

    AknIconUtils::SetSize(bitmap,TSize(px,px)); //rect.Rect().Size());
    AknIconUtils::SetSize(mask,TSize(px,px)); //rect.Rect().Size());
    return new CMaskedBitmap(bitmap,mask);
}

CMaskedBitmap* BitmapImage::getMaskedBitmap()
{
    if (m_maskedBitmap)
        return m_maskedBitmap;

    m_maskedBitmap = m_source.createFrameAtIndex(0);
    return m_maskedBitmap;
}

void BitmapImage::drawPattern(GraphicsContext* ctxt, const FloatRect& srcRect, const AffineTransform& patternTransform,
                             const FloatPoint& phase, CompositeOperator, const FloatRect& dstRect)
{
    WebCoreGraphicsContext* context = ctxt->platformContext();
    
    CMaskedBitmap* bm = frameAtIndex(m_currentFrame);
    if(!bm || bm->IsFullyTransparent()) 
        return;

    IntSize intrinsicImageSize = size();
    TSize scaledSize( xForm(TSize(srcRect.width(), srcRect.height())) );
    
    scaledSize.iWidth = scaledSize.iWidth < 1 ? 1 : scaledSize.iWidth;
    scaledSize.iHeight = scaledSize.iHeight < 1 ? 1 : scaledSize.iHeight;

    if( scaledSize != TSize(intrinsicImageSize) ) {
        bm = bm->ScaleImageToSize(scaledSize);
    }

    if ( bm->SizeInPixels() == TSize(1,1) && !bm->HasMask() ) {
        checkForSolidColor();
    }
        
    CFbsBitmap* dstBmp = context->contentBuffer();

    if (dstBmp != NULL) {
        TSize sz = dstBmp->SizeInPixels();

        // calculate target rect
        TRect trgRect( xForm(dstRect) );
        TRect clip(trgRect);
        if (context->hasClipping())
            {
            clip.Intersection(context->clippingRect());
            }

        // start point in source image
        TPoint point( xForm(TPoint(phase.x(), phase.y())) );
        TPoint off( point.iX%scaledSize.iWidth, point.iY%scaledSize.iHeight );
        off = TPoint( off.iX < 0 ? off.iX + scaledSize.iWidth : off.iX, 
                off.iY < 0 ? off.iY + scaledSize.iHeight : off.iY );
    
        // transform coordinates to bitmap space
        TRect bmpRect( clip );
        TPoint orig( context->origin() );

        TWebCoreSavedContext savedContext = context->save();
        context->setClippingRect(bmpRect);
        bm->TileInBitmapRect(context->gc(), bmpRect, point);
        context->restore(savedContext);

        /*bmpRect.Move( orig );
        if( bmpRect.Intersects( TRect( TPoint(0,0), sz ) ) ) {
            // offscreen bitmap space to aRect space
            TPoint so( bmpRect.iTl );
            so -= (trgRect.iTl + orig);
            so += off;

            // tiling the image
            //bm->TileInBitmapRect( dstBmp, bmpRect, so );
            bm->TileInBitmapRect(context->gc(), bmpRect, point);
        }*/
    }
}

void Image::drawPattern(GraphicsContext* ctxt, const FloatRect& tileRect, const AffineTransform& patternTransform,
                        const FloatPoint& phase, CompositeOperator op, const FloatRect& destRect)
{
    // this function is not really needed for Symbian
    notImplemented();
}

void BitmapImage::checkForSolidColor()
{
    if ( m_source.decoder()->decodeInProgress() || m_isSolidColor ) return;
    
    CMaskedBitmap* bm = frameAtIndex(m_currentFrame);
    
    if(!bm) 
        return;
    
    if ( frameCount() == 1 ) {
        TRgb bgColor;
        bm->Bitmap().GetPixel(bgColor, bm->SizeInPixels().AsPoint() );
        m_isSolidColor = true;
        m_solidColor = Color(bgColor.Red(), bgColor.Green(), bgColor.Blue(), bgColor.Alpha());
    }
}

}
