//
// $Id$

package com.threerings.flash {

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;

import flash.events.StatusEvent;

import flash.geom.Matrix;

import flash.media.Camera;
import flash.media.Video;

import flash.text.TextField;

import com.threerings.util.MethodQueue;

// TODO: use PreferredCamera?
public class CameraSnapshotter extends Sprite
{
    /**
     * Static method to determine if the user even has a camera.
     */
    public static function hasCamera () :Boolean
    {
        var names :Array = Camera.names;
        return (names != null && names.length > 0);
    }

    /**
     * @see setCamera
     */
    public function CameraSnapshotter (cameraName :String = null)
    {
        // create a label behind the videos that only shows up if a camera doesn't work
        var tf :TextField = new TextField();
        tf.text = "Camera unavailable.";
        addChild(tf);

        setCameraName(cameraName);
    }

    override public function get width () :Number
    {
        return (_camera == null) ? 0 : _camera.width;
    }

    override public function get height () :Number
    {
        return (_camera == null) ? 0 : _camera.height;
    }

    /**
     * @param cameraName the actual name of the camera, not the index (as used to get it from
     * the Camera class).
     */
    public function setCameraName (cameraName :String = null) :void
    {
        // translate the real name of the camera into the "name" used to get it.
        var nameIdx :String = null;
        if (cameraName != null) {
            var names :Array = Camera.names;
            for (var ii :int = 0; ii < names.length; ii++) {
                if (cameraName === names[ii]) {
                    nameIdx = String(ii);
                    break;
                }
            }
        }

        setCamera(Camera.getCamera(nameIdx));
    }

    public function setCamera (camera :Camera) :void
    {
        if (_video != null) {
            if (_bitmap.parent != null) {
                removeChild(_bitmap);
            }
            if (_video.parent != null) {
                removeChild(_video);
            }
            _bitmap = null;
            _video = null;
        }

        _camera = camera;
        if (_camera == null) {
            return;
        }

        attachVideo();
    }

    public function getCameraName () :String
    {
        return (_camera == null) ? null : _camera.name;
    }

    /**
     * Just like Camera's setMode().
     * @see flash.media.Camera#setMode()
     */
    public function setMode (width :int, height :int, fps :Number, favorArea :Boolean = true) :void
    {
        clearSnapshot();
        removeChild(_video);
        _camera.setMode(width, height, fps, favorArea);

        attachVideo();
    }

    public function takeSnapshot () :void
    {
        if (_camera == null) {
            return; // throw exception?
        }

        // NOTE: we specify a matrix to scale the video, because no matter what size it really
        // is, it will usually snapshot as if it were at the smallest size. STRANGE.
        // Note that this stupid hack doesn't always work, but it mostly works, and it's better
        // than leaving everything alone, which mostly didn't work. But then again,
        // this code is specifically saying: "fuck up the scale", so if Adobe fixes their
        // bug in the future, this will cause broken behavior.
        _bitmap.bitmapData.draw(_video,
            new Matrix(_camera.width / 160, 0, 0, _camera.height / 120));

        if (_video.parent != null) {
            removeChild(_video);
            addChild(_bitmap);
        }
    }

    public function clearSnapshot () :void
    {
        if (_camera == null) {
            return;
        }
        if (_bitmap.parent != null) {
            removeChild(_bitmap);
        }
        if (_video.parent == null) {
            addChild(_video);
        }
    }

    public function getSnapshot () :BitmapData
    {
        if (_camera == null) {
            return null;
        }
        return _bitmap.bitmapData;
    }

    protected function attachVideo () :void
    {
        if (_video != null) {
            // shut down the old video
            _video.attachCamera(null);
        }

        _video = new Video(_camera.width, _camera.height);
        // the constructor args don't seem to do dick, so we set the values again...
        _video.width = _camera.width;
        _video.height = _camera.height;
        _video.attachCamera(_camera);
        addChild(_video);
        _bitmap = new Bitmap(new BitmapData(_camera.width, _camera.height, false));
    }

    protected var _camera :Camera;

    protected var _video :Video;

    protected var _bitmap :Bitmap;
}
}
