
function createPluginReplacement(root, parent, host, attributeNames, attributeValues)
{
    return new Replacement(root, parent, host, attributeNames, attributeValues);
};

function Replacement(root, parent, host, attributeNames, attributeValues)
{
    this.root = root;
    this.parent = parent;
    this.host = host;
    this.listeners = {};
    this.scriptObject = {};

    this.autoExitFullScreen = true;
    this.postEvents = false;
    this.height = 0;
    this.width = 0;
    this.src = "";
    this.autohref = false;
    this.href = "";
    this.qtsrc = "";
    this.baseUrl = "";
    this.target = "";

    this.createVideoElement(attributeNames, attributeValues);
    this.createScriptInterface();
};

Replacement.prototype = {

    HandledVideoEvents: {
        loadstart: 'handleLoadStart',
        error: 'handleError',
        loadedmetadata: 'handleLoadedMetaData',
        canplay: 'qt_canplay',
        canplaythrough: 'qt_canplaythrough',
        play: 'qt_play',
        pause: 'qt_pause',
        ended: 'handleEnded',
        webkitfullscreenchange: 'handleFullscreenChange',
    },

    AttributeMap: {
        autoexitfullscreen: 'autoExitFullScreen',
        postdomevents: 'postEvents',
        height: 'height',
        width: 'width',
        qtsrc: 'qtsrc',
        src: 'src',
        airplay: 'x-webkit-airplay=',
        href: 'href',
        target: 'target',
    },
    
    MethodMap: {
        SetURL : 'setURL',
        GetURL : 'url',
        Play : 'play',
        Stop : 'pause',
        GetRate : 'rate',
        SetRate : 'setRate',
        IsFullScreen : 'isFullScreen',
        ExitFullScreen : 'exitFullScreen',
        GetPluginStatus : 'pluginStatus',
        GetTime : 'currentTime',
        SetTime : 'setCurrentTime',
        SeekToDate : 'seekToDate',
        GetDate : 'date',
        GetDuration : 'duration',
        GetTimeScale : 'timeScale',
        GetMaxTimeLoaded : 'maxTimeLoaded',
        GetMaxBytesLoaded : 'maxBytesLoaded',
        GetMovieSize : 'movieSize',
        GetTimedMetadataUpdates : 'timedMetadataUpdates',
        GetAccessLog : 'accessLog',
        GetErrorLog : 'errorLog',
    },

    TimeScale: 30000,

    createVideoElement: function(attributeNames, attributeValues)
    {
        var video = this.video = document.createElement('video');

        for (name in this.HandledVideoEvents)
            video.addEventListener(name, this, false);
        
        for (i = 0; i < attributeNames.length; i++) {
            var property = this.AttributeMap[attributeNames[i]];
            if (this[property] != undefined)
                this[property] = attributeValues[i];
        }

        video.setAttribute('pseudo', '-webkit-plugin-replacement');
        video.setAttribute('controls', 'controls');
        this.setStatus('Waiting');

        var src = this.resolveRelativeToUrl(this.src, "");
        this.baseUrl = src;

        // The 'qtsrc' attribute is used when a page author wanted to always use the QuickTime plug-in
        // to load a media type even if another plug-in was registered for that type. It tells the
        // plug-in to ignore the 'src' url, and to load the 'qtsrc' url instead.
        if (this.qtsrc)
            src = this.resolveRelativeToUrl(this.qtsrc, this.src);
        if (this.href && this.target) {
            src = this.resolveRelativeToUrl(this.href, this.src);
            video.poster = this.src;
            video.setAttribute('preload', 'none');
        }
        
        if (src.length) {
            this.setStatus('Validating');
            this.video.src = src;
        }

        this.root.appendChild(video);
    },

    resolveRelativeToUrl: function(url, baseUrl)
    {
        if (url.indexOf('://') != -1)
            return url;
        if (baseUrl.indexOf('://') == -1)
            baseUrl = this.resolveRelativeToUrl(baseUrl, document.baseURI);

        var base = document.createElement('base');
        base.href = baseUrl;
        document.head.appendChild(base);

        var resolver = document.createElement('a');
        resolver.href = url;
        url = resolver.href;

        document.head.removeChild(base);
        base = null;

        return url;
    },
 
    createScriptInterface: function()
    {
        for (name in this.MethodMap) {
            var methodName = this.MethodMap[name];
            this.scriptObject[name] = this[methodName].bind(this);
        }
    },

    handleEvent: function(event)
    {
        if (event.target !== this.video)
            return;

        try {
            var eventData = this.HandledVideoEvents[event.type];
            if (!eventData)
                return;

            if (this[eventData] && typeof this[eventData] === "function")
                this[eventData].call(this, event);
            else
                this.postEvent(eventData);
        } catch(e) {
            if (window.console)
                console.error(e);
        }
    },

    postEvent: function(eventName)
    {
        try {
            if (this.postEvents)
                this.host.postEvent(eventName);
        } catch(e) { }
    },

    setStatus: function(status)
    {
        this.status = status;
    },

    handleLoadedMetaData: function(event)
    {
        this.setStatus('Playable');
        this.postEvent('qt_validated');
        this.postEvent('qt_loadedfirstframe');
        this.postEvent('qt_loadedmetadata');
    },

    handleFullscreenChange: function(event)
    {
        this.postEvent(this.isFullScreen() ? 'qt_enterfullscreen' : 'qt_exitfullscreen');
    },

    handleError: function(event)
    {
        this.setStatus('Error');
        this.postEvent('qt_error');
    },

    handleLoadStart:function(event)
    {
        if (this.video.poster)
            this.setStatus('Waiting');
        else
            this.setStatus('Loading');
        this.postEvent('qt_begin');
    },

    handleEnded: function(event)
    {
        this.postEvent('qt_ended');
        if (this.isFullScreen() && this.autoExitFullScreen)
            document.webkitExitFullscreen();
    },

    isFullScreen: function()
    {
        return document.webkitCurrentFullScreenElement === this.video;
    },

    setURL: function(url)
    {
        this.setStatus('Validating');
        if (url.length)
            url = this.resolveRelativeToUrl(url, this.baseUrl);
        this.video.src = url;
    },
    
    url: function()
    {
        return this.video.currentSrc;
    },
    
    play: function()
    {
        this.video.play();
    },
    
    pause: function()
    {
        this.video.playbackRate = 0;
        this.video.pause();
    },
    
    rate: function()
    {
        return this.video.paused ? 0 : 1;
    },
    
    setRate: function(rate)
    {
        if (rate)
            this.video.play();
        else
            this.video.pause();
    },
    
    exitFullScreen: function()
    {
        document.webkitExitFullscreen();
    },
    
    pluginStatus: function()
    {
        return this.status;
    },

    currentTime: function()
    {
        return this.video.currentTime * this.TimeScale;
    },

    setCurrentTime: function(time)
    {
        this.video.currentTime = time / this.TimeScale;
    },

    seekToDate: function()
    {
        // FIXME: not implemented yet.
    },
    
    date: function()
    {
        return new Date();
    },
    
    duration: function()
    {
        return this.video.duration * this.TimeScale;
    },
    
    timeScale: function()
    {
        // Note: QuickTime movies and MPEG-4 files have a timescale, but it is not exposed by all media engines.
        // 30000 works well with common frame rates, eg. 29.97 NTSC can be represented accurately as a time
        // scale of 30000 and frame duration of 1001.
        return 30000;
    },
    
    maxTimeLoaded: function()
    {
        return this.video.duration * this.TimeScale;
    },
    
    maxBytesLoaded: function()
    {
        var percentLoaded = this.video.buffered.end(0) / this.video.duration;
        return percentLoaded * this.movieSize();
    },
    
    movieSize: function()
    {
        try {
            return this.host.movieSize;
        } catch(e) { }

        return 0;
    },
    
    timedMetadataUpdates: function()
    {
        try {
            return this.host.timedMetaData;
        } catch(e) { }

        return null;
    },
    
    accessLog: function()
    {
        try {
            return this.host.accessLog;
        } catch(e) { }

        return null;
    },
    
    errorLog: function()
    {
        try {
            return this.host.errorLog;
        } catch(e) { }

        return null;
    },
};

