// PlaceObject2Tag.cpp:  for Gnash.
//
//   Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012
//   Free Software Foundation, Inc.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h" // HAVE_ZLIB_H, USE_SWFTREE
#endif

#include "PlaceObject2Tag.h"

#include <string>

#include "TypesParser.h"
#include "RunResources.h"
#include "DisplayObject.h"
#include "MovieClip.h"
#include "swf_event.h"
#include "log.h"
#include "SWFStream.h"
#include "Filters.h"
#include "filter_factory.h"
#include "GnashAlgorithm.h"
#include "action_buffer.h"
#include "utility.h"

namespace gnash {
namespace SWF {

PlaceObject2Tag::PlaceObject2Tag(const movie_definition& def)
    :
    DisplayListTag(0),
    m_has_flags2(0),
    m_has_flags3(0),
    _id(0),
    _ratio(0),
    m_clip_depth(0),
    _blendMode(0),
    _movie_def(def)
{
}

void
PlaceObject2Tag::readPlaceObject(SWFStream& in)
{
    // Original place_object tag; very simple.
    in.ensureBytes(2 + 2);
    _id = in.read_u16();
    _depth = in.read_u16() + DisplayObject::staticDepthOffset;

    // PlaceObject doesn't know about masks.
    m_clip_depth = DisplayObject::noClipDepthValue;

    // If these flags2 values aren't set here, nothing will
    // ever be displayed.
    m_has_flags2 = HAS_CHARACTER_MASK;

    if (in.tell() < in.get_tag_end_position())
    {
        m_matrix = readSWFMatrix(in);
        m_has_flags2 |= HAS_MATRIX_MASK;
        if (in.tell() < in.get_tag_end_position())
        {
            m_color_transform = readCxFormRGB(in);
            m_has_flags2 |= HAS_CXFORM_MASK;
        }
    }

    IF_VERBOSE_PARSE
    (
            log_parse(_("  PLACEOBJECT: depth=%d(%d) char=%d"),
            	_depth, _depth - DisplayObject::staticDepthOffset,
            	_id);
            if (hasMatrix()) log_parse("  SWFMatrix: %s", m_matrix);
            if (hasCxform()) log_parse(_("  SWFCxForm: %s"), m_color_transform);
    );

    
}

// read placeObject2 actions
void
PlaceObject2Tag::readPlaceActions(SWFStream& in)
{
    const int movie_version = _movie_def.get_version();

    in.ensureBytes(2);
    std::uint16_t reserved = in.read_u16();
    IF_VERBOSE_MALFORMED_SWF(
        if (reserved != 0) {
            log_swferror(_("Reserved field in PlaceObject actions == "
                    "%u (expected 0)"), reserved);
        }
    );
    
    std::uint32_t all_event_flags;

    // The logical 'or' of all the following handlers.
    if (movie_version >= 6) {
        in.ensureBytes(4);
        all_event_flags = in.read_u32();
    }
    else {
        in.ensureBytes(2);
        all_event_flags = in.read_u16();        
    }

    IF_VERBOSE_PARSE (
        log_parse(_("  actions: flags = 0x%X"), all_event_flags);
    );

    // Read swf_events.
    for (;;) {
        // Handle SWF malformations locally, by just prematurely interrupting
        // parsing of action events.
        // TODO: a possibly improvement would be using local code for the
        //       equivalent of ensureBytes which has the cost of a function
        //       call for itself plus a repeated useless function call for
        //       get_end_tag_position (which could be cached).
        //       
        try {
            // Read event.
            in.align();
    
            std::uint32_t flags;
            if (movie_version >= 6) {
                in.ensureBytes(4);
                flags = in.read_u32();
            }
            else {
                in.ensureBytes(2);
                flags = in.read_u16();        
            }
    
            // no other events
            if (flags == 0) {
                break;
            }
    
            in.ensureBytes(4);
            std::uint32_t event_length = in.read_u32();
            if (in.get_tag_end_position() - in.tell() <  event_length) {
                IF_VERBOSE_MALFORMED_SWF(
                log_swferror(_("swf_event::read(), "
                    "even_length = %u, but only %lu bytes left "
                    "to the end of current tag."
                    " Breaking for safety."),
                    event_length, in.get_tag_end_position() - in.tell());
                );
                break;
            }

            if ( event_length == 0 ) {
                IF_VERBOSE_MALFORMED_SWF(
                log_swferror(_("swf_event::read(), "
                    "event_length = 0 (empty). Skipping."));
                );
                continue;
            }
    
            key::code ch = key::INVALID;
            
            // has KeyPress event
            if (flags & (1 << 17)) {
                in.ensureBytes(1);
                ch = static_cast<key::code>(in.read_u8());
                --event_length;
            }
    
            // Read the actions for event(s)
            // unique_ptr here prevents leaks on malformed swf
            std::unique_ptr<action_buffer> action(new action_buffer(_movie_def));
            action->read(in, in.tell() + event_length);
            _actionBuffers.push_back(action.release()); 
    
            // If there is no end tag, action_buffer appends a null-terminator,
            // and fails this check. As action_buffer should check bounds, we
            // can just continue here.
            //assert (action->size() == event_length);
    
            // 13 bits reserved, 19 bits used
            static const event_id::EventCode s_code_bits[] = {
                event_id::LOAD,
                event_id::ENTER_FRAME,
                event_id::UNLOAD,
                event_id::MOUSE_MOVE,
                event_id::MOUSE_DOWN,
                event_id::MOUSE_UP,
                event_id::KEY_DOWN,
                event_id::KEY_UP,
    
                event_id::DATA,
                event_id::INITIALIZE,
                event_id::PRESS,
                event_id::RELEASE,
                event_id::RELEASE_OUTSIDE,
                event_id::ROLL_OVER,
                event_id::ROLL_OUT,
                event_id::DRAG_OVER,
    
                event_id::DRAG_OUT,
                event_id::KEY_PRESS,
                event_id::CONSTRUCT
            };
            const size_t total_known_events = arraySize(s_code_bits);
    
            // Let's see if the event flag we received is for an event
            // that we know of.
    
            // Integrity check: all reserved bits should be zero
            if (flags >> total_known_events) {
                IF_VERBOSE_MALFORMED_SWF(
                    log_swferror(_("swf_event::read() -- unknown / unhandled "
                            "event type received, flags = 0x%x"), flags);
                );
            }
    
            // Aah! same action for multiple events !
            for (size_t i = 0, mask = 1; i < total_known_events; ++i, mask <<= 1) {

                if (flags & mask) {
                    // Yes, swf_event stores a reference to an element in
                    // _actionBuffers. A case of remote ownership, but both
                    // swf_event and the actions are owned by this class,
                    // so shouldn't be a problem.
                    action_buffer& thisAction = _actionBuffers.back();

                    const event_id id(s_code_bits[i], (i == 17 ? ch : key::INVALID));

                    std::unique_ptr<swf_event> ev(new swf_event(id, thisAction));

                    IF_VERBOSE_PARSE(
                        log_parse("---- actions for event %s", ev->event());
                    );
    
                    _eventHandlers.push_back(ev.release());
                }
            }
        }
        catch (const ParserException& what) {
            IF_VERBOSE_MALFORMED_SWF(
                log_swferror(_("Unexpected end of tag while parsing "
                        "PlaceObject tag events"));
            );
            break;
        }
    }
}

// read SWF::PLACEOBJECT2
void
PlaceObject2Tag::readPlaceObject2(SWFStream& in)
{
    in.align();

    in.ensureBytes(1 + 2); // PlaceObject2, depth

    // PlaceObject2 specific flags
    m_has_flags2 = in.read_u8();

    _depth = in.read_u16()+DisplayObject::staticDepthOffset;

    if (hasCharacter()) {
        in.ensureBytes(2);
        _id = in.read_u16();
    }

    if (hasMatrix()) {
        m_matrix = readSWFMatrix(in);
    }

    if (hasCxform()) {
        m_color_transform = readCxFormRGBA(in);
    }

    if (hasRatio()) {
        in.ensureBytes(2);
        _ratio = in.read_u16();
    }

    if (hasName()) {
        in.read_string(m_name);
    }

    if (hasClipDepth()) {
        in.ensureBytes(2);
        m_clip_depth = in.read_u16() + DisplayObject::staticDepthOffset;
    }
    else {
        m_clip_depth = DisplayObject::noClipDepthValue;
    }

    if (hasClipActions()) {
        readPlaceActions(in);
    }

    IF_VERBOSE_PARSE(
        log_parse(_("  PLACEOBJECT2: depth = %d (%d)"),
            _depth, _depth-DisplayObject::staticDepthOffset);
        if (hasCharacter()) log_parse(_("  char id = %d"), _id);
        if (hasMatrix()) {
            log_parse(_("  SWFMatrix: %s"), m_matrix);
        }
        if (hasCxform()) {
            log_parse(_("  SWFCxForm: %s"), m_color_transform);
        }
        if (hasRatio()) log_parse(_("  ratio: %d"), _ratio);
        if (hasName()) log_parse(_("  name = %s"), m_name.c_str());
        if (hasClipDepth()) {
            log_parse(_("  clip_depth = %d (%d)"), m_clip_depth,
                m_clip_depth-DisplayObject::staticDepthOffset);
        }
        log_parse(_(" m_place_type: %d"), getPlaceType());
    );

}

// read SWF::PLACEOBJECT3
void
PlaceObject2Tag::readPlaceObject3(SWFStream& in)
{
    in.align();

    in.ensureBytes(1 + 1 + 2); // PlaceObject2, PlaceObject3, depth

    // PlaceObject2 specific flags
    m_has_flags2 = in.read_u8();

    // PlaceObject3 speckfic flags, first 3 bits are unused
    m_has_flags3 = in.read_u8();
    
    _depth = in.read_u16() + DisplayObject::staticDepthOffset;

    // This is documented to be here, but real instances of 
    // tags with either className or hasImage defined are rare to
    // non-existent. Alexis' SWF reference has neither of them,
    // instead specifying 5 reserved bits in the PlaceObject3 flags.
    std::string className;
    if (hasClassName() || (hasImage() && hasCharacter())) {
        log_unimpl("PLACEOBJECT3 with associated class name");
        in.read_string(className);
    }

    if (hasCharacter()) {
        in.ensureBytes(2);
        _id = in.read_u16();
    }

    if (hasMatrix()) {
        m_matrix = readSWFMatrix(in);
    }

    if (hasCxform()) {
        m_color_transform = readCxFormRGBA(in);
    }

    if (hasRatio()) {
        in.ensureBytes(2);
        _ratio = in.read_u16();
    }
    
    if (hasName()) {
        in.read_string(m_name);
    }

    if (hasClipDepth()) {
        in.ensureBytes(2);
        m_clip_depth = in.read_u16()+DisplayObject::staticDepthOffset;
    }
    else {
        m_clip_depth = DisplayObject::noClipDepthValue;
    }

    if (hasFilters()) {
        Filters v; // TODO: Attach the filters to the display object.
        filter_factory::read(in, true, &v);
	    // at time of writing no renderer supports bitmap filters
	    LOG_ONCE( log_unimpl("Bitmap filters") );
    }

    if (hasBlendMode()) {
        in.ensureBytes(1);
        _blendMode = in.read_u8();
        LOG_ONCE(log_unimpl("Blend mode in PlaceObject tag"));
    }

    std::uint8_t bitmask = 0;
    if (hasBitmapCaching()) {
        // cacheAsBitmap is a boolean value, so the flag itself ought to be
        // enough. Alexis' SWF reference is unsure about this, but suggests
        // reading a byte here. The official SWF format spec doesn't mention
        // it.
        //
        // However, the movie the-last-stand.swf has one PlaceObject3 tag
        // with both PlaceActions and bitmap caching, and the reserved bytes
        // of the PlaceActions (see readPlaceActions) are not 0 if this byte
        // isn't read.
        // TODO: set object property
        in.ensureBytes(1);
        bitmask = in.read_u8();
	    LOG_ONCE(log_unimpl("Bitmap caching"));
        UNUSED(bitmask);
    }

    if (hasClipActions()) {
        readPlaceActions(in);
    }

    IF_VERBOSE_PARSE (

        log_parse(_("  PLACEOBJECT3: depth = %d (%d)"), _depth,
            _depth - DisplayObject::staticDepthOffset);
        if (hasCharacter()) log_parse(_("  char id = %d"), _id);
        if (hasMatrix()) log_parse(_("  SWFMatrix: %s"), m_matrix);
        if (hasCxform()) log_parse(_("  SWFCxForm: %d"), m_color_transform);
        if (hasRatio()) log_parse(_("  ratio: %d"), _ratio);
        if (hasName()) log_parse(_("  name = %s"), m_name);
        if (hasClassName()) log_parse(_("  class name = %s"), className);
        if (hasClipDepth()) log_parse(_("  clip_depth = %d (%d)"),
                    m_clip_depth, m_clip_depth-DisplayObject::staticDepthOffset);
        if (hasBitmapCaching()) log_parse(_("   bitmapCaching enabled"));
        log_parse(_(" m_place_type: %d"), getPlaceType());
    );

}

void
PlaceObject2Tag::read(SWFStream& in, TagType tag)
{
    if (tag == SWF::PLACEOBJECT) {
        readPlaceObject(in);
    }
    else if (tag == SWF::PLACEOBJECT2) {
        readPlaceObject2(in);
    }
    else {
        readPlaceObject3(in);
    }
}

/// Place/move/whatever our object in the given movie.
void
PlaceObject2Tag::executeState(MovieClip* m, DisplayList& dlist) const
{
    switch (getPlaceType()) {
      case PLACE:
          m->add_display_object(this, dlist);
          break;

      case MOVE:
          m->move_display_object(this, dlist);
          break;

      case REPLACE:
          m->replace_display_object(this, dlist);
          break;

      case REMOVE:
		  m->remove_display_object(this, dlist);
          break;
    }
}


PlaceObject2Tag::~PlaceObject2Tag()
{
}

void
PlaceObject2Tag::loader(SWFStream& in, TagType tag, movie_definition& m,
        const RunResources& /*r*/)
{
    assert(tag == SWF::PLACEOBJECT || tag == SWF::PLACEOBJECT2 ||
            tag == SWF::PLACEOBJECT3);

    boost::intrusive_ptr<PlaceObject2Tag> ch(new PlaceObject2Tag(m));
    ch->read(in, tag);

    m.addControlTag(ch);
}

} // namespace gnash::SWF
} // namespace gnash

// Local Variables:
// mode: C++
// indent-tabs-mode: t
// End:
