/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "../../../juce_core/basics/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE


#include "juce_RepaintManager.h"
#include "../graphics/imaging/juce_Image.h"
#include "../graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h"
#include "../../../juce_core/basics/juce_Logger.h"
#include "../../application/juce_Application.h"

static const int timerPeriod = 1000 / 100;  // 100 fps maximum

//==============================================================================
RepaintManager::RepaintManager (Component* const component_,
                                const int timeBeforeReleasingImage_)
    : component (component_),
      image (0),
      originX (0),
      originY (0),
      timeBeforeReleasingImage (timeBeforeReleasingImage_),
      lastPaintTime (0)
{
    jassert (component != 0);
}

RepaintManager::~RepaintManager()
{
    delete image;
}

//==============================================================================
Image* RepaintManager::createNewImage (int w, int h)
{
    return new Image (component->isOpaque() ? Image::RGB : Image::ARGB,
                      w, h, ! component->isOpaque());
}

void RepaintManager::invalidateCache (const int x, const int y,
                                      const int w, const int h)
{
    if (image != 0)
    {
        validRegions.subtract (Rectangle (x, y, w, h));

        if (validRegions.getNumRectangles() > 100)
            validRegions.clear();
    }
}

void RepaintManager::repaint (int x, int y, int w, int h)
{
    if (Rectangle::intersectRectangles (x, y, w, h,
                                        0, 0,
                                        component->getWidth(),
                                        component->getHeight()))
    {
        if (! isTimerRunning())
            startTimer (timerPeriod);

        regionsNeedingRepaint.add (x, y, w, h);
    }
}

void RepaintManager::clearPendingRepaints()
{
    regionsNeedingRepaint.clear();
    startTimer (timerPeriod);
}

void RepaintManager::ensureImageContains (int x, int y, int w, int h)
{
    if (image == 0 || image->hasAlphaChannel() == component->isOpaque())
    {
        // scrap any previous image and get a new clear one..
        delete image;

        image = createNewImage ((w + 63) & ~7, (h + 63) & ~7);
        originX = x;
        originY = y;
        validRegions.clear();
    }
    else if (x < originX || y < originY
             || x + w > originX + image->getWidth()
             || y + h > originY + image->getHeight())
    {
        if (w > image->getWidth() || h > image->getHeight())
        {
            // need a bigger image, so get a new one and copy across any valid bits..
            Image* const newImage = createNewImage ((w + 31) & ~7, (h + 31) & ~7);

            int newOriginX = x;
            int newOriginY = y;

            Graphics g (*newImage);

            RectangleList::Iterator i (validRegions);

            while (i.next())
            {
                const Rectangle& r = i.getRectangle();

                g.drawImage (image,
                             r.getX() - newOriginX,
                             r.getY() - newOriginY,
                             r.getWidth(),
                             r.getHeight(),
                             r.getX() - originX,
                             r.getY() - originY,
                             r.getWidth(),
                             r.getHeight(),
                             false);
            }

            originX = newOriginX;
            originY = newOriginY;

            validRegions.clipTo (Rectangle (newOriginX, newOriginY,
                                            newImage->getWidth(),
                                            newImage->getHeight()));

            delete image;
            image = newImage;
        }
        else
        {
            // need a different area but the image is big enough, so shift around its
            // current contents..

            const int newOriginX = x;
            const int newOriginY = y;

            const Rectangle r (validRegions.getBounds());

            image->moveImageSection (r.getX() - newOriginX,
                                     r.getY() - newOriginY,
                                     r.getX() - originX,
                                     r.getY() - originY,
                                     r.getWidth(),
                                     r.getHeight());

            validRegions.clipTo (Rectangle (newOriginX, newOriginY,
                                            image->getWidth(),
                                            image->getHeight()));

            originX = newOriginX;
            originY = newOriginY;
        }
    }
}

void RepaintManager::renderCacheAreasNeedingRepaint()
{
    const Rectangle totalArea (regionsNeedingRepaint.getBounds());

    if (! totalArea.isEmpty())
    {
        ensureImageContains (totalArea.getX(), totalArea.getY(),
                             totalArea.getWidth(), totalArea.getHeight());

        RectangleList dirty (regionsNeedingRepaint);
        dirty.subtract (validRegions);

        if (image->hasAlphaChannel())
        {
            RectangleList::Iterator i (dirty);

            while (i.next())
            {
                const Rectangle r (i.getRectangle());
                validRegions.add (r);

                image->clear (r.getX() - originX,
                              r.getY() - originY,
                              r.getWidth(),
                              r.getHeight());
            }
        }

        startTimer (timerPeriod);

        if (! dirty.isEmpty())
        {
            Graphics g (*image);
            g.setOrigin (-originX, -originY);
            g.setClipRegion (dirty);

            JUCE_TRY
            {
                component->paintEntireComponent (g);
            }
            JUCE_CATCH_EXCEPTION

#if JUCE_ENABLE_REPAINT_DEBUGGING
            // enabling this code will draw coloured outlines around all areas that
            // get repainted - for debugging.
            {
                static unsigned int nextRepaintColour = 0xffff0000;

                nextRepaintColour = 0xff000000 | (nextRepaintColour ^ ((nextRepaintColour & 0xffffff) >> 1));

                g.setColour (Colour (nextRepaintColour));

                RectangleList::Iterator i (dirty);

                while (i.next())
                {
                    const Rectangle& r = i.getRectangle();
                    g.drawRect (r.getX(), r.getY(), r.getWidth(), r.getHeight());
                }
            }
#endif
        }
    }

    lastTimeImageUsed = Time::getApproximateMillisecondCounter();
}

void RepaintManager::repaintNow (const RectangleList& /*areasToPaint*/)
{
}

void RepaintManager::performPendingRepaints()
{
    repaintNow (regionsNeedingRepaint);

    regionsNeedingRepaint.clear();
    startTimer (timerPeriod);
}

Image* RepaintManager::getImage (int& x, int& y) const
{
    x = originX;
    y = originY;
    return image;
}

void RepaintManager::releaseImage()
{
    deleteAndZero (image);
    validRegions.clear();
}

void RepaintManager::timerCallback()
{
    stopTimer();

    if (! regionsNeedingRepaint.isEmpty())
    {
        performPendingRepaints();
    }
    else if (Time::getApproximateMillisecondCounter() > lastTimeImageUsed + timeBeforeReleasingImage)
    {
        releaseImage();
    }
}


END_JUCE_NAMESPACE
