/**
 * Copyright (C) 2006 by Koos Vriezen <koos.vriezen@gmail.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License version 2 as published by the Free Software Foundation.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 **/

#include <config.h>

#include <tqcolor.h>
#include <tqimage.h>
#include <tqtimer.h>

#include <kdebug.h>

#include "kmplayer_rp.h"
#include "kmplayer_smil.h"

using namespace KMPlayer;


KDE_NO_CDTOR_EXPORT RP::Imfl::Imfl (NodePtr & d)
  : Mrl (d, id_node_imfl),
    fit (fit_hidden),
    duration (0),
    needs_scene_img (0) {}

KDE_NO_CDTOR_EXPORT RP::Imfl::~Imfl () {
}

KDE_NO_EXPORT void RP::Imfl::closed () {
    for (NodePtr n = firstChild (); n; n = n->nextSibling ())
        if (RP::id_node_head == n->id) {
            AttributePtr a = convertNode <Element> (n)->attributes ()->first ();
            for (; a; a = a->nextSibling ()) {
                if (StringPool::attr_width == a->name ()) {
                    width = a->value ().toInt ();
                } else if (StringPool::attr_height == a->name ()) {
                    height = a->value ().toInt ();
                } else if (a->name () == "duration") {
                    int dur;
                    parseTime (a->value ().lower (), dur);
                    duration = dur;
                }
            }
        }
}

KDE_NO_EXPORT void RP::Imfl::defer () {
    kdDebug () << "RP::Imfl::defer " << endl;
    setState (state_deferred);
    for (Node * n = firstChild ().ptr (); n; n = n->nextSibling ().ptr ())
        if (n->id == RP::id_node_image && !n->active ())
            n->activate ();
}

KDE_NO_EXPORT void RP::Imfl::activate () {
    kdDebug () << "RP::Imfl::activate " << endl;
    resolved = true;
    setState (state_activated);
    int timings_count = 0;
    for (NodePtr n = firstChild (); n; n = n->nextSibling ())
        switch (n->id) {
            case RP::id_node_crossfade:
            case RP::id_node_fadein:
            case RP::id_node_fadeout:
            case RP::id_node_fill:
            case RP::id_node_wipe:
            case RP::id_node_viewchange:
                n->activate (); // set their start timers
                timings_count++;
                break;
            case RP::id_node_image:
                if (!n->active ())
                    n->activate ();
                break;
        }
    if (duration > 0)
        duration_timer = document ()->setTimeout (this, duration * 100);
    else if (!timings_count)
        finish ();
}

KDE_NO_EXPORT void RP::Imfl::finish () {
    kdDebug () << "RP::Imfl::finish " << endl;
    Mrl::finish ();
    if (duration_timer) {
        document ()->cancelTimer (duration_timer);
        duration_timer = 0;
    }
    for (NodePtr n = firstChild (); n; n = n->nextSibling ())
        if (n->unfinished ())
            n->finish ();
}

KDE_NO_EXPORT void RP::Imfl::childDone (NodePtr) {
    if (unfinished () && !duration_timer) {
        for (NodePtr n = firstChild (); n; n = n->nextSibling ())
            switch (n->id) {
                case RP::id_node_crossfade:
                case RP::id_node_fadein:
                case RP::id_node_fadeout:
                case RP::id_node_fill:
                    if (n->unfinished ())
                        return;
            }
        finish ();
    }
}

KDE_NO_EXPORT void RP::Imfl::deactivate () {
    kdDebug () << "RP::Imfl::deactivate " << endl;
    if (unfinished ())
        finish ();
    if (!active ())
        return; // calling finish might call deactivate() as well
    setState (state_deactivated);
    for (NodePtr n = firstChild (); n; n = n->nextSibling ())
        if (n->active ())
            n->deactivate ();
    rp_surface = Mrl::getSurface (0L);
}

KDE_NO_EXPORT bool RP::Imfl::handleEvent (EventPtr event) {
    if (event->id () == event_timer) {
        TimerEvent * te = static_cast <TimerEvent *> (event.ptr ());
        if (te->timer_info == duration_timer) {
            kdDebug () << "RP::Imfl timer " << duration << endl;
            duration_timer = 0;
            if (unfinished ())
                finish ();
        }
    }
    return true;
}

KDE_NO_EXPORT void RP::Imfl::accept (Visitor * v) {
    v->visit (this);
}

KDE_NO_EXPORT Surface *RP::Imfl::surface () {
    if (!rp_surface) {
        rp_surface = Mrl::getSurface (this);
        if (rp_surface) {
            if (width <= 0 || width > 32000)
                width = rp_surface->bounds.width ();
            if (height <= 0 || height > 32000)
                height = rp_surface->bounds.height ();
        }
    }
    return rp_surface.ptr ();
}

KDE_NO_EXPORT NodePtr RP::Imfl::childFromTag (const TQString & tag) {
    const char * ctag = tag.latin1 ();
    if (!strcmp (ctag, "head"))
        return new DarkNode (m_doc, "head", RP::id_node_head);
    else if (!strcmp (ctag, "image"))
        return new RP::Image (m_doc);
    else if (!strcmp (ctag, "fill"))
        return new RP::Fill (m_doc);
    else if (!strcmp (ctag, "wipe"))
        return new RP::Wipe (m_doc);
    else if (!strcmp (ctag, "viewchange"))
        return new RP::ViewChange (m_doc);
    else if (!strcmp (ctag, "crossfade"))
        return new RP::Crossfade (m_doc);
    else if (!strcmp (ctag, "fadein"))
        return new RP::Fadein (m_doc);
    else if (!strcmp (ctag, "fadeout"))
        return new RP::Fadeout (m_doc);
    return 0L;
}

KDE_NO_EXPORT void RP::Imfl::repaint () {
    if (!active ())
        kdWarning () << "Spurious Imfl repaint" << endl;
    else if (surface () && width > 0 && height > 0)
        rp_surface->repaint (SRect (0, 0, width, height));
}

KDE_NO_CDTOR_EXPORT RP::Image::Image (NodePtr & doc)
 : Mrl (doc, id_node_image)
{}

KDE_NO_CDTOR_EXPORT RP::Image::~Image () {
}

KDE_NO_EXPORT void RP::Image::closed () {
    src = getAttribute (StringPool::attr_name);
}

KDE_NO_EXPORT void RP::Image::activate () {
    kdDebug () << "RP::Image::activate" << endl;
    setState (state_activated);
    isPlayable (); // update src attribute
    cached_img.setUrl (absolutePath ());
    if (cached_img.isEmpty ()) {
        wget (absolutePath ());
    } else {
        width = cached_img.data->image->width ();
        height = cached_img.data->image->height ();
    }
}

KDE_NO_EXPORT void RP::Image::begin () {
    Node::begin ();
}

KDE_NO_EXPORT void RP::Image::deactivate () {
    cached_img.setUrl (TQString ());
    if (img_surface) {
        img_surface->remove ();
        img_surface = NULL;
    }
    setState (state_deactivated);
    postpone_lock = 0L;
}


KDE_NO_EXPORT void RP::Image::remoteReady (TQByteArray & data) {
    kdDebug () << "RP::Image::remoteReady" << endl;
    if (!data.isEmpty () && cached_img.isEmpty ()) {
        TQImage * img = new TQImage (data);
        if (!img->isNull ()) {
            cached_img.data->image = img;
            width = img->width ();
            height = img->height ();
        } else {
            delete img;
        }
    }
    postpone_lock = 0L;
}

KDE_NO_EXPORT bool RP::Image::isReady (bool postpone_if_not) {
    if (downloading () && postpone_if_not)
        postpone_lock = document ()->postpone ();
    return !downloading ();
}

KDE_NO_EXPORT Surface *RP::Image::surface () {
    if (!img_surface && !cached_img.isEmpty ()) {
        Node * p = parentNode ().ptr ();
        if (p && p->id == RP::id_node_imfl) {
            Surface *ps = static_cast <RP::Imfl *> (p)->surface ();
            if (ps)
                img_surface = ps->createSurface (this,
                        SRect (0, 0, width, height));
        }
    }
    return img_surface;
}

KDE_NO_CDTOR_EXPORT RP::TimingsBase::TimingsBase (NodePtr & d, const short i)
 : Element (d, i), x (0), y (0), w (0), h (0), start (0), duration (0) {}

KDE_NO_EXPORT void RP::TimingsBase::activate () {
    setState (state_activated);
    x = y = w = h = 0;
    srcx = srcy = srcw = srch = 0;
    for (Attribute * a= attributes ()->first ().ptr (); a; a = a->nextSibling ().ptr ()) {
        if (a->name () == StringPool::attr_target) {
            for (NodePtr n = parentNode()->firstChild(); n; n= n->nextSibling())
                if (convertNode <Element> (n)->
                        getAttribute ("handle") == a->value ())
                    target = n;
        } else if (a->name () == "start") {
            int dur;
            parseTime (a->value ().lower (), dur);
            start = dur;
        } else if (a->name () == "duration") {
            int dur;
            parseTime (a->value ().lower (), dur);
            duration = dur;
        } else if (a->name () == "dstx") {
            x = a->value ().toInt ();
        } else if (a->name () == "dsty") {
            y = a->value ().toInt ();
        } else if (a->name () == "dstw") {
            w = a->value ().toInt ();
        } else if (a->name () == "dsth") {
            h = a->value ().toInt ();
        } else if (a->name () == "srcx") {
            srcx = a->value ().toInt ();
        } else if (a->name () == "srcy") {
            srcy = a->value ().toInt ();
        } else if (a->name () == "srcw") {
            srcw = a->value ().toInt ();
        } else if (a->name () == "srch") {
            srch = a->value ().toInt ();
        }
    }
    start_timer = document ()->setTimeout (this, start *100);
}

KDE_NO_EXPORT void RP::TimingsBase::deactivate () {
    if (unfinished ())
        finish ();
    setState (state_deactivated);
}

KDE_NO_EXPORT bool RP::TimingsBase::handleEvent (EventPtr event) {
    if (event->id () == event_timer) {
        TimerEvent * te = static_cast <TimerEvent *> (event.ptr ());
        if (te->timer_info == update_timer && duration > 0) {
            update (100 * ++curr_step / duration);
            te->interval = true;
        } else if (te->timer_info == start_timer) {
            start_timer = 0;
            duration_timer = document ()->setTimeout (this, duration * 100);
            begin ();
        } else if (te->timer_info == duration_timer) {
            duration_timer = 0;
            update (100);
            finish ();
        } else
            return false;
        return true;
    } else if (event->id () == event_postponed) {
        if (!static_cast <PostponedEvent *> (event.ptr ())->is_postponed) {
            document_postponed = 0L; // disconnect
            update (duration > 0 ? 0 : 100);
        }
    }
    return false;
}

KDE_NO_EXPORT void RP::TimingsBase::begin () {
    progress = 0;
    setState (state_began);
    if (target)
        target->begin ();
    if (duration > 0) {
        steps = duration; // 10/s updates
        update_timer = document ()->setTimeout (this, 100); // 50ms
        curr_step = 1;
    }
}

KDE_NO_EXPORT void RP::TimingsBase::update (int percentage) {
    progress = percentage;
    Node * p = parentNode ().ptr ();
    if (p->id == RP::id_node_imfl)
        static_cast <RP::Imfl *> (p)->repaint ();
}

KDE_NO_EXPORT void RP::TimingsBase::finish () {
    progress = 100;
    if (start_timer) {
        document ()->cancelTimer (start_timer);
        start_timer = 0;
    } else if (duration_timer) {
        document ()->cancelTimer (duration_timer);
        duration_timer = 0;
    }
    if (update_timer) {
        document ()->cancelTimer (update_timer);
        update_timer = 0;
    }
    document_postponed = 0L; // disconnect
    Element::finish ();
}

KDE_NO_EXPORT void RP::Crossfade::activate () {
    TimingsBase::activate ();
}

KDE_NO_EXPORT void RP::Crossfade::begin () {
    //kdDebug () << "RP::Crossfade::begin" << endl;
    TimingsBase::begin ();
    if (target && target->id == id_node_image) {
        RP::Image * img = static_cast <RP::Image *> (target.ptr ());
        if (!img->isReady (true))
            document_postponed = document()->connectTo (this, event_postponed);
        else
            update (duration > 0 ? 0 : 100);
    }
}

KDE_NO_EXPORT void RP::Crossfade::accept (Visitor * v) {
    v->visit (this);
}

KDE_NO_EXPORT void RP::Fadein::activate () {
    // pickup color from Fill that should be declared before this node
    from_color = 0;
    TimingsBase::activate ();
}

KDE_NO_EXPORT void RP::Fadein::begin () {
    //kdDebug () << "RP::Fadein::begin" << endl;
    TimingsBase::begin ();
    if (target && target->id == id_node_image) {
        RP::Image * img = static_cast <RP::Image *> (target.ptr ());
        if (!img->isReady (true))
            document_postponed = document()->connectTo (this, event_postponed);
        else
            update (duration > 0 ? 0 : 100);
    }
}

KDE_NO_EXPORT void RP::Fadein::accept (Visitor * v) {
    v->visit (this);
}

KDE_NO_EXPORT void RP::Fadeout::activate () {
    to_color = TQColor (getAttribute ("color")).rgb ();
    TimingsBase::activate ();
}

KDE_NO_EXPORT void RP::Fadeout::begin () {
    //kdDebug () << "RP::Fadeout::begin" << endl;
    TimingsBase::begin ();
}

KDE_NO_EXPORT void RP::Fadeout::accept (Visitor * v) {
    v->visit (this);
}

KDE_NO_EXPORT void RP::Fill::activate () {
    color = TQColor (getAttribute ("color")).rgb ();
    TimingsBase::activate ();
}

KDE_NO_EXPORT void RP::Fill::begin () {
    setState (state_began);
    update (0);
}

KDE_NO_EXPORT void RP::Fill::accept (Visitor * v) {
    v->visit (this);
}

KDE_NO_EXPORT void RP::Wipe::activate () {
    //TODO implement 'type="push"'
    TQString dir = getAttribute ("direction").lower ();
    direction = dir_right;
    if (dir == TQString::fromLatin1 ("left"))
        direction = dir_left;
    else if (dir == TQString::fromLatin1 ("up"))
        direction = dir_up;
    else if (dir == TQString::fromLatin1 ("down"))
        direction = dir_down;
    TimingsBase::activate ();
}

KDE_NO_EXPORT void RP::Wipe::begin () {
    //kdDebug () << "RP::Wipe::begin" << endl;
    TimingsBase::begin ();
    if (target && target->id == id_node_image) {
        RP::Image * img = static_cast <RP::Image *> (target.ptr ());
        if (!img->isReady (true))
            document_postponed = document()->connectTo (this, event_postponed);
        else
            update (duration > 0 ? 0 : 100);
    }
}

KDE_NO_EXPORT void RP::Wipe::accept (Visitor * v) {
    v->visit (this);
}

KDE_NO_EXPORT void RP::ViewChange::activate () {
    TimingsBase::activate ();
}

KDE_NO_EXPORT void RP::ViewChange::begin () {
    kdDebug () << "RP::ViewChange::begin" << endl;
    setState (state_began);
    Node * p = parentNode ().ptr ();
    if (p->id == RP::id_node_imfl)
        static_cast <RP::Imfl *> (p)->needs_scene_img++;
    update (0);
}

KDE_NO_EXPORT void RP::ViewChange::finish () {
    Node * p = parentNode ().ptr ();
    if (p && p->id == RP::id_node_imfl)
        static_cast <RP::Imfl *> (p)->needs_scene_img--;
    TimingsBase::finish ();
}

KDE_NO_EXPORT void RP::ViewChange::accept (Visitor * v) {
    v->visit (this);
}
