/**
 * @version $Id: JsonObjectsTracker.cpp 23039 2011-07-04 07:47:47Z nkrachkovsky $
 * ------------------------------------------------------------------------------
 * This class represents JsonObjectsTracker class for rendering analytics and
 * metadata from Json representation
 * ------------------------------------------------------------------------------
 * @author Nickolay Krachkovsky
 * @QA
 * @copyright videoNEXT Network Solutions LLC 2010
 * ------------------------------------------------------------------------------
 */

#include <boost/version.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include "JsonObjectsTracker.h"
#include "json/json.h"

#if BOOST_VERSION >= 103900
#include <boost/make_shared.hpp>
#else
// simple emulation of boost:make_shared for old boost versions (RH5 - 1.33)
namespace boost {
template< class T > shared_ptr< T > make_shared() {  return shared_ptr< T >( new T() ); }
}
#endif

const char* JsonObjectsTracker::objs_evts[OBJEVT_COUNT] = { "objs", "evts" };
#define OBJEVTS_OBJ 0
#define OBJEVTS_EVT 1
const char* JsonObjectsTracker::ml_pg[MLPG_COUNT] = { "mline", "polygon", "trail" };
#define MLPG_MLINE 0
#define MLPG_POLYGON 1
#define MLPG_TRAIL 2
std::set< std::string > JsonObjectsTracker::notObjIds = JsonObjectsTracker::genNotObjsIds();

#define VCA_MAX_COORD 65535
#define FONT_UNIT_SIZE 172
#define METADATA_SPACING 404
#define VCA_DOT_DELTA 716

std::set< std::string > JsonObjectsTracker::genNotObjsIds()
{
    std::set< std::string > res;
    res.insert( "c" );
    res.insert( "w" );
    res.insert( "t" );
    res.insert( "draw" );
    return res;
}

JsonObjectsTracker::JsonObjectsTracker()
    : calc_trail( false )
{
}

JsonObjectsTracker::~JsonObjectsTracker()
{
    clearObjects();
}

void JsonObjectsTracker::clearObjects()
{
    for( int bbi = 0; bbi < OBJEVT_COUNT; bbi++ ) {
        bboxes[bbi].clear();
    }
    objmap.clear();
    objects.clear();
    calc_trail = false;
}

void JsonObjectsTracker::updateTrails()
{
    //for( int bbi = 0; bbi < OBJEVT_COUNT; bbi++ )
    const int bbi = 0;
    {
        for( bboxes_map_t::iterator it = bboxes[bbi].begin(); it != bboxes[bbi].end(); ++it ) {
            if( it->second->isAlive( frameTime() ) ) {
                object_map_t::iterator tr2;
                object2_map_t::iterator tr;
                if( ( tr2 = objmap.find( it->first ) ) != objmap.end() && ( tr = tr2->second.find( ml_pg[ MLPG_TRAIL ] ) ) != tr2->second.end() ) {
                    // FIXME: make more efficient
                    // list?
                    // just copy? - similar to push+erase
                    MLinePtr ml = boost::dynamic_pointer_cast< MLine >( tr->second );
                    if( ml ) {
                        ml->points.push_back( Point2( it->second->x, it->second->y + it->second->h / 2 ) );
                        if( ml->points.size() > 20 ) {
                            ml->points.erase( ml->points.begin() );
                        }
                        std::string ll;
                        for( size_t i = 0; i < ml->points.size(); i++ ) {
                            ll += (boost::format("(%1% %2%)") % ml->points[i].x % ml->points[i].y ).str();
                        }
                        log_debug((boost::format("JsonOT::updateTrails %1%: %2%") % it->first % ll ).str());
                        ml->finish = it->second->finish;
                    }
                } else {
                    MLinePtr ml = boost::make_shared<MLine>();
                    ml->setObj( *it->second );
                    ml->is_native_draw = false;
                    ml->points.push_back( Point2( it->second->x, it->second->y + it->second->h / 2 ) );
                    objmap[ it->first ][ ml_pg[ MLPG_TRAIL ] ] = ml;
                    log_debug((boost::format("JsonOT::updateTrails %1%: %2%") % it->first % "first look" ).str());
                }
            }
        }
    }
}

const Json::Value JsonObjectsTracker::getProp( const char* prop, const Json::Value& js, const Json::Value& parent )
{
    const Json::Value& jsp = js[prop];
    const Json::Value& pp = parent.isNull() ? Json::nullValue : parent[prop];
    return jsp.isNull() ? pp : jsp;
}

bool JsonObjectsTracker::parseMLPVertecies( MLinePtr ml, const Json::Value& jsPoints )
{
    bool okpts = true;
    for( Json::Value::const_iterator it = jsPoints.begin(); it != jsPoints.end(); ++it ) {
        const Json::Value& pt = *it;
        if( pt.isNull() || !pt.isObject() ) {
            okpts = false;
            break;
        }
        const Json::Value
                &jsX = pt["x"],
                &jsY = pt["y"];
        if( !( jsX.isNumeric() && jsY.isNumeric() ) ) {
            log_debug((boost::format("JsonObjectsTracker::setObjectsData vertex array element has no in pt x and/or y")).str());
            okpts = false;
            break;
        }
        ml->points.push_back( Point2( jsX.asInt(), jsY.asInt() ) );
    }
    return okpts;
}

std::list< JsonObjectsTracker::RenderObjectPtr > JsonObjectsTracker::parseMLinePolygon( int mp, const Json::Value& js, const Json::Value& parent, const std::string& extraInfo )
{
    if( js.isNull() || !js.isObject() ) {
        log_debug((boost::format("JsonObjectsTracker::setObjectsData %s part (info:'%s') not object") % ml_pg[mp] % extraInfo).str());
        return std::list< RenderObjectPtr >();
    }
    const Json::Value
            &jsLineWidth = getProp( "w", js, parent ),
            &jsColor = getProp( "c", js, parent ),
            &jsLifetime = getProp( "t", js, parent ),
            &jsDraw = getProp( "draw", js, parent ),
            &jsPoints = js["pt"];
    if( !( jsLineWidth.isNumeric()
          && jsColor.isIntegral()
          && ( jsLifetime.isNull() || jsLifetime.isNumeric() )
          && !jsPoints.isNull() && jsPoints.isArray()
          && ( jsDraw.isNull() || jsDraw.isString() ) ) ) {
        log_debug((boost::format("JsonObjectsTracker::setObjectsData %s part (info:'%s') has no needed attributes")%ml_pg[mp] % extraInfo).str());
        return std::list< JsonObjectsTracker::RenderObjectPtr >();
    }
    int lineWidth = jsLineWidth.asInt();
    double lifetime = jsLifetime.isNull() ? -1 : jsLifetime.asDouble();
    bool isNativeDraw = jsDraw.isNull() ? false : jsDraw.asString() == "native";
    Color color( jsColor.asInt() );
    color.swapRB();
    color.a = (int) ( 255 * 0.7 );
    std::list< RenderObjectPtr > mll;
    if( jsPoints.size() > 0 ) {
        // don't change (*xx). to x->
        // jsoncpp has no operator ->
        if( !(*jsPoints.begin()).isNull() && (*jsPoints.begin()).isArray() ) {
            // array of array of objects :)
            for( Json::Value::const_iterator it = jsPoints.begin(); it != jsPoints.end(); ++it ) {
                MLinePtr ml = mp == 1 ? boost::static_pointer_cast< MLine >( boost::make_shared< Polygon >() ) : boost::make_shared< MLine >();
                ml->setObj( frameTime(),  lifetime, lineWidth, color );
                ml->is_native_draw = isNativeDraw;
                bool okpts = parseMLPVertecies( ml, *it );
                if( !okpts ) {
                    log_debug((boost::format("JsonObjectsTracker::setObjectsData %s part (info:'%s') has some ooops")%ml_pg[mp] % extraInfo).str());
                    continue;
                }
                mll.push_back( ml );
            }
        } else {
            // array of objects
            MLinePtr ml = mp == 1 ? boost::static_pointer_cast< MLine >( boost::make_shared< Polygon >() ) : boost::make_shared< MLine >();
            ml->setObj( frameTime(), lifetime, lineWidth, color );
            ml->is_native_draw = isNativeDraw;
            bool okpts = parseMLPVertecies( ml, jsPoints );
            if( !okpts ) {
                log_debug((boost::format("JsonObjectsTracker::setObjectsData %s part (info:'%s') has some ooops")%ml_pg[mp] % extraInfo).str());
                return mll;
            }
            mll.push_back( ml );
        }
    }
    return mll;
}

JsonObjectsTracker::TextPtr JsonObjectsTracker::parseTextMetadata( int tm, const Json::Value& js, const Json::Value& parent, const std::string& extraInfo )
{
    if( js.isNull() || !js.isObject() )
        return boost::shared_ptr< Text >();
    const Json::Value
            &jsId = js["id"],
            &jsColor = getProp( "c", js, parent ),
            &jsLifetime = getProp( "t", js, parent ),
            &jsMessage = js["msg"],
            &jsX = js["x"],
            &jsY = js["y"],
            &jsH = js["h"],
            &jsStyle = js["s"],
            &jsAlign = js["a"],
            &jsFont = js["f"];
    if( !( jsColor.isIntegral()
           && ( jsLifetime.isNull() || jsLifetime.isNumeric() )
           && jsMessage.isString()
           && ( (jsX.isNumeric() && jsY.isNumeric() && jsId.isNull())
                || (jsX.isNull() && jsY.isNull() && ( jsId.isNull() || jsId.isString() ) ) )
           && jsH.isNumeric()
           && ( jsStyle.isNull() || jsStyle.isIntegral() )
           && ( jsFont.isNull() || jsFont.isString() )
           && (jsAlign.isNull() || jsAlign.isIntegral()) ) ) {
        log_debug((boost::format("JsonObjectsTracker::parseTextMetadata (info:'%s') has no needed attributes") % extraInfo).str());
        return boost::shared_ptr< Text >();
    }
    double lifetime = jsLifetime.isNull() ? -1 : jsLifetime.asDouble();
    Color color( jsColor.asInt() );
    color.swapRB();
    color.a = (int) ( 255 * 0.7 );
    TextPtr t;
    if( jsX.isNull() || jsY.isNull() ) { // metadata text
        std::string mid;
        if( jsId.isString() ) {
            mid = jsId.asString();
        }
        MetadataPtr m = boost::make_shared< Metadata >();
        m->meta_id = mid;
        t = m;
    } else { // simple positioned text
        t = boost::make_shared< Text >();
        t->x = jsX.asInt();
        t->y = jsY.asInt();
    }
    if (t) {
        t->setObj( frameTime(), lifetime, 0, color );
        t->h = jsH.asDouble();
        t->msg = jsMessage.asString();
        if (jsStyle.isIntegral()) {
            int style = jsStyle.asInt();
            if (style == 1)
                t->style = Graphics::Bold;
            else if (style == 2)
                t->style = Graphics::Italic;
            else if (style == 3)
                t->style = Graphics::BoldItalic;
        }
        if (jsAlign.isIntegral()) {
            int align = jsAlign.asInt();
            if (align < 0 || align >= 9)
                align = 0;
            t->horz_align = static_cast<Graphics::HORZ_ALIGN>(align % 3);
            t->vert_align = static_cast<Graphics::VERT_ALIGN>(align / 3);
            if (t->vert_align == Graphics::BaseLine)
                t->vert_align = Graphics::Bottom;
        }
        if (jsFont.isString()) {
            t->font = jsFont.asString();
        }
    }
    return t;
}

void JsonObjectsTracker::parseRootObject( const Json::Value& js )
{
    do {
        if( !js.isObject() ) {
            log_debug("JsonObjectsTracker::setObjectsData root is not an object");
            break;
        }
        const Json::Value& cmd = js["cmd"];
        if( cmd.isString() ) {
            std::string command = cmd.asString();
            if( command == "clear" ) {
                clearObjects();
            }
        } else if( !cmd.isNull() && cmd.isObject() ) {
            const Json::Value& jsCalcTrail = cmd["calc_trail"];
            if( !jsCalcTrail.isNull() && jsCalcTrail.isObject() ) {
                const Json::Value& jsT = jsCalcTrail["t"];
                if( jsT.isIntegral() ) {
                    calc_trail = !!jsT.asInt();
                }
            }
        }
        for( int bbi = 0; bbi < OBJEVT_COUNT; bbi++ ) {
            const Json::Value& objs = js[objs_evts[bbi]];
            if( objs.isNull() )
                continue;
            if( objs.isObject() ) {
                const Json::Value
                &jsLineWidth = objs["w"],
                &jsColor = objs["c"],
                &jsLifetime = objs["t"];
                if( !( jsLineWidth.isNumeric() && jsColor.isIntegral() && ( jsLifetime.isNull() || jsLifetime.isNumeric() ) ) )
                    continue;
                int lineWidth = jsLineWidth.asInt();
                double lifetime = jsLifetime.isNull() ? -1 : jsLifetime.asDouble();
                Color color( jsColor.asInt() );
                color.swapRB();
                color.a = (int) ( 255 * 0.7 );

                Json::Value::Members m = objs.getMemberNames();

                for( Json::Value::Members::const_iterator i = m.begin(); i != m.end(); ++i ) {
                    std::string id = *i;
                    if( notObjIds.count( id ) )
                        continue;
                    const Json::Value& b = objs[id];
                    if( b.isNull() || !b.isObject() ) {
                        log_debug((boost::format("JsonObjectsTracker::setObjectsData %s part %s is not an object") % objs_evts[bbi]%id).str());
                        continue;
                    }
                    const Json::Value
                            &jsX = b["x"],
                            &jsY = b["y"],
                            &jsW = b["w"],
                            &jsH = b["h"];
                    int x = 0, y = 0, w = 0, h = 0, bbok = false;
                    if( jsX.isNumeric() && jsY.isNumeric() && jsW.isNumeric() && jsH.isNumeric() ) {
                        x = jsX.asInt(); y = jsY.asInt(); w = jsW.asInt(); h = jsH.asInt();
                        bbok = true;
                    } else if (bbi != OBJEVTS_EVT) {
                        continue;
                    }
                    for( int bbj = 0; bbj <= bbi; bbj++ ) {
                        bboxes_map_t::iterator curBB;
                        if( ( curBB = bboxes[bbj].find( id ) ) == bboxes[bbj].end() ) {
                            bboxes[bbj][id] = boost::make_shared< BBObject >();
                            curBB = bboxes[bbj].find( id );
                            bbok = true; // to make x,y,w,h=0 when they were absent for the first event
                        }
                        BBObjectPtr cur = curBB->second;
                        cur->setObj( frameTime(), lifetime, lineWidth, color );
                        if (bbok) {
                            cur->x = x;
                            cur->y = y;
                            cur->w = w;
                            cur->h = h;
                        }
                    }
                }
            } else {
                log_debug((boost::format("JsonObjectsTracker::setObjectsData %s is not an object") % objs_evts[bbi]).str());
            }
        }
        for( int mp = 0; mp < MLPG_COUNT; mp++ ) {
            const Json::Value& mline = js[ml_pg[mp]];
            if( !mline.isNull() ) {
                if( mline.isArray() ) {
                    for( Json::Value::const_iterator i = mline.begin(); i != mline.end(); ++i ) {
                        std::list< RenderObjectPtr > ml = parseMLinePolygon( mp, *i, Json::Value::null );
                        objects.insert( objects.end(), ml.begin(), ml.end() );
                        ml.clear();
                    }
                } else if( mline.isObject() ) {
                    Json::Value::Members m = mline.getMemberNames();
                    for( Json::Value::Members::const_iterator i = m.begin(); i != m.end(); ++i ) {
                        std::string id = *i;
                        if( notObjIds.count( id ) )
                            continue;
                        std::list< RenderObjectPtr > ml = parseMLinePolygon( mp, mline[id], mline, id );
                        std::list< RenderObjectPtr >::iterator it;
                        int j;
                        for( it = ml.begin(), j = 0; it != ml.end(); ++it, j++ ) {
                            objmap[ id ][ ( boost::format( "%1%/%2%" ) % ml_pg[ mp ] % j ).str() ] = *it;
                        }
                    }
                } else {
                    log_debug((boost::format("JsonObjectsTracker::setObjectsData %s is invalid")%ml_pg[mp]).str());
                }
            }
        }
        const Json::Value& text = js["text"];
        if( !text.isNull() ) {
            if( text.isArray() ) {
                for( Json::Value::const_iterator i = text.begin(); i != text.end(); ++i ) {
                    TextPtr t = parseTextMetadata( 0, *i, Json::Value::null );
                    if( t ) {
                        objects.push_back( t );
                    }
                }
            } else if( text.isObject() ) {
                Json::Value::Members m = text.getMemberNames();
                for( Json::Value::Members::const_iterator i = m.begin(); i != m.end(); ++i ) {
                    std::string id = *i;
                    if( notObjIds.count( id ) )
                        continue;
                    TextPtr t = parseTextMetadata( 0, text[id], text, id );
                    if( t ) {
                        objmap[id][ "text" ] = t;
                    }
                }
            } else {
                log_debug("JsonObjectsTracker::setObjectsData 'text' is invalid");
            }
        }
        const Json::Value& lpr = js["lpr"];
        if (lpr.isString()) {
            lprOutNumbers.push_front(LinkEntry(lpr.asString()));
            while (lprOutNumbers.size() > 4) {
                lprOutNumbers.pop_back();
            }
        } else if (!lpr.isNull()) {
            log_debug("JsonObjectsTracker::parseRootObject 'lpr' is not a string");
        }
    } while( 0 );
}

void JsonObjectsTracker::setObjectsData(const std::string& Bitstream)
{
//    log_debug((boost::format("JsonObjectsTracker::setObjectsData Bitstream='%s'") % Bitstream ).str());
    Json::Reader jr;
    Json::Value js;
    jr.parse( Bitstream, js );
    if( !js.isNull() ) {
        if( js.isObject() ) {
            parseRootObject( js );
        }
        if( js.isArray() ) {
            for( Json::Value::iterator it = js.begin(); it != js.end(); ++it ) {
                parseRootObject( *it );
            }
        }
    }
    if( calc_trail ) {
        updateTrails();
    }
}

void JsonObjectsTracker::setMetadataInfo(const std::map<std::string, std::vector<std::string> > &VisibleMetadata)
{
    visibleMetadata = VisibleMetadata;
}

void JsonObjectsTracker::parseAndDraw(Graphics* g2, const Rectangle2& DestinationRect, const Rectangle2& SourceRect, float ZoomFactor)
{
//    log_debug("parseAndDraw called");
    graph = g2;
    dstRect = DestinationRect;
    srcRect = SourceRect;
    zoomFactor = ZoomFactor;

    kW = dstRect.width  / (float)srcRect.width;
    kH = dstRect.height / (float)srcRect.height;

    // top
    metaY[0] = metaY[1] = metaY[2] = METADATA_SPACING;
    // middle
    metaY[3] = metaY[4] = metaY[5] = VCA_MAX_COORD / 2;
    // bottom
    metaY[6] = metaY[7] = metaY[8] = VCA_MAX_COORD - METADATA_SPACING;

    drawTextLinks();

    float oldStroke = g2->getStrokeWidth();
    // check events first to remove dead events
    for( int bbi = OBJEVT_COUNT - 1; bbi >= 0; bbi-- ) {
        std::list< bboxes_map_t::iterator > torm_bb;
        for( bboxes_map_t::iterator it = bboxes[bbi].begin(); it != bboxes[bbi].end(); ++it ) {
            if( !it->second->isAlive( frameTime() ) ) {
                torm_bb.push_back( it );
            } else if( bbi == 0 ) {
                bboxes_map_t::iterator evt;
                if( ( evt = bboxes[1].find( it->first ) ) != bboxes[1].end() ) {
                    // copy style&lifetime from event while it alive
                    it->second->finish = evt->second->finish;
                    it->second->lineWidth = evt->second->lineWidth;
                    it->second->color = evt->second->color;
                }
                it->second->draw( this );
            }
        }
        for( std::list< bboxes_map_t::iterator >::iterator rm = torm_bb.begin(); rm != torm_bb.end(); ++rm ) {
            bboxes[bbi].erase( *rm );
        }
    }

    for( object_map_t::iterator it2 = objmap.begin(); it2 != objmap.end(); ++it2 ) {
        std::list< object2_map_t::iterator > torm_om;
        for( object2_map_t::iterator it = it2->second.begin(); it != it2->second.end(); ++it ) {
            if( !it->second->isAlive( frameTime() ) ) {
                torm_om.push_back( it );
            } else {
                bboxes_map_t::iterator evt;
                for( int bbi = OBJEVT_COUNT - 1; bbi >= 0; bbi-- ) {
                    if( ( evt = bboxes[bbi].find( it2->first ) ) != bboxes[bbi].end() ) {
                        // copy color from event while it alive
                        it->second->color = evt->second->color;
                        PolygonPtr poly = boost::dynamic_pointer_cast< Polygon >( it->second );
                        if( poly && poly->isNativeDraw() ) {
                            poly->color.a = (int) ( 255 * 0.25 );
                        }
                        break;
                    }
                }
                it->second->draw( this );
            }
        }
        for( std::list< object2_map_t::iterator >::iterator rm = torm_om.begin(); rm != torm_om.end(); ++rm ) {
            it2->second.erase( *rm );
        }
    }

    std::list< objects_list_t::iterator > torm_ob;
    for( objects_list_t::iterator it = objects.begin(); it != objects.end(); ++it ) {
        if( !(*it)->isAlive( frameTime() ) ) {
            torm_ob.push_back( it );
        } else {
            (*it)->draw( this );
        }
    }
    for( std::list< objects_list_t::iterator >::iterator rm = torm_ob.begin(); rm != torm_ob.end(); ++rm ) {
        objects.erase( *rm );
    }
    g2->setStrokeWidth( oldStroke );
}

std::list<JsonObjectsTracker::LinkEntry> JsonObjectsTracker::linksList()
{
    return lprOutNumbers;
}

JsonObjectsTracker::RenderObject::~RenderObject()
{
}

void JsonObjectsTracker::RenderObject::setObj( long long now, double Lifetime, int LineWidth, Color ObjectColor )
{
    start = now;
    if( Lifetime >= 0 ) {
        finish = start + /*static_cast<long long>*/( MILLION * Lifetime );
    } else {
        finish = -1;
    }
    lineWidth = LineWidth;
    color = ObjectColor;
}

void JsonObjectsTracker::RenderObject::setObj( const JsonObjectsTracker::RenderObject& ro )
{
    start = ro.start;
    finish = ro.finish;
    lineWidth = ro.lineWidth;
    color = ro.color;
}

bool JsonObjectsTracker::RenderObject::isAlive(long long now) const
{
    if( finish < 0 )
        return true;
    return ( now >= start ) // jump back in archive
            && ( now < finish );
}

void JsonObjectsTracker::RenderObject::draw( JsonObjectsTracker* jsot )
{
    float stroke = jsot->zoomFactor * lineWidth;
    jsot->graph->setStrokeWidth((stroke < 1) ? 1 : stroke);
    jsot->graph->setColor( color );
}

void JsonObjectsTracker::BBObject::draw( JsonObjectsTracker* jsot )
{
    RenderObject::draw( jsot );
    float xx = x * (float) jsot->srcRect.width / VCA_MAX_COORD;
    float yy = y * (float) jsot->srcRect.height / VCA_MAX_COORD;
    float ww = w * (float) jsot->srcRect.width / VCA_MAX_COORD;
    float hh = h * (float) jsot->srcRect.height / VCA_MAX_COORD;
    jsot->graph->drawRect((int)(jsot->dstRect.x + jsot->kW*(xx - jsot->srcRect.x - ww/2.f) + 0.5),
                (int)(jsot->dstRect.y + jsot->kH*(yy - jsot->srcRect.y - hh/2.f) + 0.5),
                (int)(jsot->kW*ww + 0.5), (int)(jsot->kH*hh + 0.5));
}

void JsonObjectsTracker::MLine::preparePointData( JsonObjectsTracker* jsot )
{
    RenderObject::draw( jsot );
    if( jsot->xPoints.size() < points.size() ) {
        jsot->xPoints.resize( points.size() );
        jsot->yPoints.resize( points.size() );
    }
    for (size_t i = 0; i < points.size(); i++) {
        jsot->xPoints[i] = (int)(jsot->dstRect.x + jsot->kW*(points[i].x * (long long) jsot->srcRect.width / VCA_MAX_COORD - jsot->srcRect.x) + 0.5);
        jsot->yPoints[i] = (int)(jsot->dstRect.y + jsot->kH*(points[i].y * (long long) jsot->srcRect.height / VCA_MAX_COORD - jsot->srcRect.y) + 0.5);
    }
}

void JsonObjectsTracker::MLine::draw( JsonObjectsTracker* jsot )
{
    if( points.size() < 2 )
        return;
    RenderObject::draw( jsot );
    preparePointData( jsot );
    jsot->graph->drawPolyline(jsot->xPoints, jsot->yPoints, points.size());
    if( is_native_draw ) {
        float dx = jsot->kW * VCA_DOT_DELTA / 2 * (long long) jsot->srcRect.width / VCA_MAX_COORD;
        float dy = dx;//jsot->kH * VCA_DOT_DELTA / 2 * (long long) jsot->srcRect.height / VCA_MAX_COORD;
        for (size_t i = 0; i < points.size(); i++) {
            jsot->graph->drawEllipse(jsot->xPoints[i] - dx, jsot->yPoints[i] - dy, 2 * dx, 2 * dy);
        }
    }
}

bool JsonObjectsTracker::MLine::isNativeDraw()
{
    return is_native_draw;
}

void JsonObjectsTracker::Polygon::draw( JsonObjectsTracker* jsot )
{
    RenderObject::draw( jsot );
    preparePointData( jsot );
    jsot->graph->fillPolygon(jsot->xPoints, jsot->yPoints, points.size());
    if( is_native_draw ) {
        float dx = jsot->kW * VCA_DOT_DELTA / 2 * (long long) jsot->srcRect.width / VCA_MAX_COORD;
        float dy = dx;//jsot->kH * VCA_DOT_DELTA / 2 * (long long) jsot->srcRect.height / VCA_MAX_COORD;
        for (size_t i = 0; i < points.size(); i++) {
            jsot->graph->drawEllipse(jsot->xPoints[i] - dx, jsot->yPoints[i] - dy, 2 * dx, 2 * dy);
        }
    }
}

void JsonObjectsTracker::Text::draw( JsonObjectsTracker* jsot )
{
    RenderObject::draw( jsot );
    jsot->graph->setFont(font.empty() ? "Verdana" : font.c_str(),
                         h * jsot->dstRect.height * FONT_UNIT_SIZE / VCA_MAX_COORD,
                         style);
    jsot->graph->drawString(msg,
                            (int)(jsot->dstRect.x
                                  + jsot->kW * (x * (long long) jsot->srcRect.width / VCA_MAX_COORD
                                                - jsot->srcRect.x)
                                  + 0.5),
                            (int)(jsot->dstRect.y
                                  + jsot->kH * (y * (long long) jsot->srcRect.height / VCA_MAX_COORD
                                                - jsot->srcRect.y)
                                  + 0.5),
                            horz_align, vert_align);
}

void JsonObjectsTracker::Metadata::draw( JsonObjectsTracker* jsot )
{
    //log_debug((boost::format("metadata: '%1%' size: %2%, font: '%3%'") % msg % h % font).str());
    if (!meta_id.empty()) {
        std::map< std::string, std::vector< std::string > >::const_iterator vv = jsot->visibleMetadata.find( meta_id );
        if( vv == jsot->visibleMetadata.end() )
            return;
        if( !vv->second.size() )
            return;
    }
    int align = static_cast<int>(horz_align) + 3 * static_cast<int>(vert_align);
    if (align >= 9)
        align -= 3;
    switch (horz_align) {
    case Graphics::Left:
        x = METADATA_SPACING;
        break;
    case Graphics::Center:
        x = VCA_MAX_COORD / 2;
        break;
    case Graphics::Right:
        x = VCA_MAX_COORD - METADATA_SPACING;
        break;
    }

    y = jsot->metaY[align];
    Text::draw( jsot );
    switch (vert_align) {
    case Graphics::Top:
    case Graphics::Middle: // FIXME:
        jsot->metaY[align] += h * FONT_UNIT_SIZE + METADATA_SPACING;
        break;
    case Graphics::Bottom:
    case Graphics::BaseLine:
        jsot->metaY[align] -= h * FONT_UNIT_SIZE + METADATA_SPACING;
        break;
    }
}

void JsonObjectsTracker::drawTextLinks()
{
    const int h = 18 * FONT_UNIT_SIZE;
    const int x = VCA_MAX_COORD - METADATA_SPACING;
    float& y = metaY[2]; // top-right

    graph->setFont("Verdana",
                   h * dstRect.height / VCA_MAX_COORD,
                   Graphics::Regular);

    for (std::list<LinkEntry>::iterator it = lprOutNumbers.begin(); it != lprOutNumbers.end(); ++it) {
        it->rect = graph->drawString(it->text,
                                     (int)(dstRect.x
                                           + x * (long long) dstRect.width / VCA_MAX_COORD
                                           + 0.5),
                                     (int)(dstRect.y
                                           + y * (long long) dstRect.height / VCA_MAX_COORD
                                           + 0.5),
                                     Graphics::Right, Graphics::Top);
        //graph->drawRect(it->rect.x, it->rect.y, it->rect.width, it->rect.height);
        y += h + METADATA_SPACING;
    }
}

