#include <tqcolor.h>
#include <tqfontmetrics.h>
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqtimer.h>
#include <tqwidget.h>

#include <tdeglobalsettings.h>

#include "screen.h"
#include "sprite.h"
#include "aasaver.h"

Screen::Screen(AASaver* widget): m_widget(widget)
{
    TQFontMetrics fm(TDEGlobalSettings::fixedFont());

    // Compute cell geometries.
    m_cellW  = fm.maxWidth();
    m_cellH  = fm.lineSpacing();

    // Computer number of full cells that will fit.
    m_width  = widget->width()  / m_cellW;
    m_height = widget->height() / m_cellH;
    
    // Calculate offset needed to evenly distribute excess screen space.
    m_offX = (widget->width() - m_width * m_cellW) / 2;
    m_offY = (widget->height() - m_height * m_cellH) / 2;

    // Create double buffer.
    m_backBuffer = TQPixmap(m_widget->size());
    m_backBuffer.fill(black);

    // FIXME: handle resizing!

    // Setup animation timer.
    TQTimer* timer = new TQTimer(this);
    connect(timer, TQ_SIGNAL(timeout()), TQ_SLOT(doAnimate()));

    timer->start(msPerTick());
}

int Screen::msPerTick() const
{
    return 50;
}

Screen::~Screen()
{
}

void Screen::updateSpan(int x, int y, const TQPixmap &updatePixmap)
{
    if (y < 0 || y >= m_height) return;

    TQPoint upperLeft(m_offX + x * m_cellW, m_offY + y * m_cellH);
    bitBlt(&m_backBuffer, upperLeft, &updatePixmap, updatePixmap.rect(), TQt::CopyROP); 
    m_widget->update(TQRect(upperLeft, updatePixmap.size()));
}

void Screen::clearSpan(int x, int y, const TQPixmap &clearPixmap)
{
    if (y < 0 || y >= m_height) return;
    
    TQPoint upperLeft(m_offX + x * m_cellW, m_offY + y * m_cellH);
    bitBlt(&m_backBuffer, upperLeft, &clearPixmap, clearPixmap.rect(), TQt::CopyROP); 
    m_widget->update(TQRect(upperLeft, clearPixmap.size()));
}

//Actually paints the region on the widget.
void Screen::paint(TQRegion r)
{
    TQPainter p(m_widget);
    TQMemArray<TQRect> rects = r.rects();

    for (int r = 0; r < rects.size(); ++r)
    {
        //Determine the grid locations described by the rect
        TQRect bound = rects[r];

        bitBlt(m_widget, bound.topLeft(), &m_backBuffer, bound, TQt::CopyROP);
    } //for rect in region
};

/**
 * Utility type used to faciliate sorting of the Sprite list in order to
 * implement the Painter's Algorithm when painting the back buffer.
 */
struct ZKey
{
    /**
     * Logical depth of sprite.  Now 0 is farthest away from the eyes, unlike
     * with Sprite::depth().
     */
    int      z;    

    Sprite*  addr;

    ZKey(): z(0), addr(0)
    {}

    ZKey(Sprite* spr): z(1000 - spr->depth()), addr(spr)
    {}

    bool operator<(const ZKey& other) const
    {
        if (z < other.z) return true;
        if (z > other.z) return false;

        return addr < other.addr;
    }
};

void Screen::doAnimate()
{
    //First, rebuild a new list of sprites, and build a dirty region
    TQRegion dirtyRegion;

    TQValueVector<Sprite*> sprites;
    TQValueVector<Sprite*> colliders;

    // Look for sprites that can suffer a collision.
    for (unsigned pos = 0; pos < m_sprites.size(); ++pos)
    {
        if(m_sprites[pos]->canCollide())
            colliders.append(m_sprites[pos]);
    }

    // Find collisions.
    // FIXME: Use transparent regions for accuracy.
    for (unsigned pos = 0; pos < colliders.size(); ++pos)
        for (unsigned sprite = 0; sprite < m_sprites.size(); ++sprite)
        {
            if(m_sprites[sprite] == colliders[pos])
                continue;

            if(colliders[pos]->geom().intersects(m_sprites[sprite]->geom()))
                colliders[pos]->collision(m_sprites[sprite]);
        }

    //Retain all live existing sprites
    for (int pos = 0; pos < m_sprites.size(); ++pos)
    {
        Sprite* sprite  = m_sprites[pos];
        TQRect   oldRect = sprite->geom();
        if (!sprite->isKilled()) {
            bool dirty = sprite->tickUpdate();

            if (dirty)
                dirtyRegion |= oldRect | sprite->geom();

            if (!sprite->isKilled())
                sprites.append(sprite);
        }

        if (sprite->isKilled()) //note:may be made true by updateTick!
        {
            dirtyRegion |= oldRect;
            delete sprite;
        }
    }

    //Add new sprites.
    for (int pos = 0; pos < m_addedSprites.size(); ++pos)
    {
        dirtyRegion |= m_addedSprites[pos]->geom();
        sprites.append(m_addedSprites[pos]);
    }
    
    m_addedSprites.clear();
    m_sprites = sprites;
    
    //Compute the list of sprites affected. Note that this is 
    //done iteratively until fixed point.
    TQValueVector<Sprite*> paintSprites;
    TQValueVector<Sprite*> remSprites;
    
    bool changed;
    do
    {
        changed = false;
        remSprites.clear();
        
        for (int c = 0; c < sprites.size(); ++c)
        {
            Sprite* sprite = sprites[c];
            
            if (dirtyRegion.intersect(sprite->geom()).isEmpty())
                remSprites.append(sprite); //not to be painted thus far
            else
            {
                //This sprite is to be painted
                paintSprites.append(sprite);
                
                //make sure we repaint everything overlapping it
                dirtyRegion |= sprite->geom();
                changed = true;
            }
        }
        sprites = remSprites;
    }
    while (changed);

    //Z-sort the items.
    TQMap<ZKey, Sprite* > sorted;
    for (int pos = 0; pos < paintSprites.size(); ++pos)
        sorted[ZKey(paintSprites[pos])] = paintSprites[pos];

    //Paint, in Z-order
    for (TQMapIterator<ZKey, Sprite*> i = sorted.begin();
                                     i != sorted.end(); ++i)
        i.data()->paint();

    // Make sure black strip at edge is still present.
    if(!paintSprites.isEmpty())
    {
        TQPainter p(&m_backBuffer);
        p.fillRect(m_backBuffer.width() - m_offX, 0, m_offX, m_backBuffer.height(), TQt::black);
    }
}

#include "screen.moc"
