package {
    import flash.display.*;
    import flash.events.*;
    import flash.media.*;
    import flash.text.*;
    import flash.geom.*
    import flash.errors.*;
    import flash.filters.*;
    import flash.utils.*;
    import flash.net.*;

    [SWF(width="360", height="320", backgroundColor="#0099E5", frameRate="30")]

    public class AsciiArt extends Sprite {

        public static const
            APPMODE_NONE:int = 0,           ///< アプリケーション初期状態
            APPMODE_MONO:int = 1,           ///< 白黒でのアスキー表示
            APPMODE_GREEN:int = 2,          ///< 緑黒でのアスキー表示
            APPMODE_NOEFFECT:int = 3;       ///< エフェクト無し。ビデオそのまま。

        private var appMode:int = APPMODE_NONE;     ///< アプリケーションモード
        private var ascii:TextField;                ///< ASCIIアート描画用のテキスト
        private var asciiFormat:TextFormat;         ///< ASCIIのフォーマット
        private var label:TextField = new TextField();              ///< ラベル
        private var camera:Camera = null;           ///< カメラ
        private var bd:BitmapData;                  ///< BMPデータ
        private var bdTmp:BitmapData;               ///< テンポラリビットマップデータ
        private var video:Video = null;             ///< ビデオ
        private var asciiArray:Array = new Array(); ///< ASCII文字のテーブル
        private var bgColor:uint = 0xffffff;        ///< ビデオの背景カラー
        private var bgShape:Shape = new Shape();    ///< 外枠用

        public static const
            FONT_SIZE:int = 6,              ///< フォントサイズ
            VIDEO_WIDTH:int = 320,          ///< ビデオの幅
            VIDEO_HEIGHT:int = 240,         ///< ビデオの高さ
            SCREEN_WIDTH:int = 360,         ///< 画面の幅
            SCREEN_HEIGHT:int = 320;        ///< 画面の高さ

        /// コンストラクタ
        public function AsciiArt() {

            // filterの作成
            var filter:Array = new Array;
            filter.push( new DropShadowFilter() );

            // 背景色用オブジェクト作成
            addChild(bgShape);

            // BMPデータの作成
            var bmp:Bitmap
            bd = new BitmapData( VIDEO_WIDTH, VIDEO_HEIGHT, false, 0xffffff);
            bmp = new Bitmap(bd);
            bmp.filters = filter;
            bmp.x = (SCREEN_WIDTH - VIDEO_WIDTH)/2;
            bmp.y = (SCREEN_HEIGHT - VIDEO_HEIGHT)/2;
            addChild(bmp);

            bdTmp = new BitmapData( VIDEO_WIDTH, VIDEO_HEIGHT, false, 0xffffff);

            // ラベル
            ascii = new TextField();
            ascii.autoSize = TextFieldAutoSize.LEFT;

            // フォーマット
            asciiFormat = new TextFormat();
            asciiFormat.font = "_等幅";
            asciiFormat.size = FONT_SIZE+1;
            setFontSize(FONT_SIZE);
            ascii.defaultTextFormat = asciiFormat;

            // ASCII文字のテーブルを作成する
            for( var asCode:int = 0x20; asCode < 0x7f; ++ asCode ){
                bd.fillRect(new Rectangle(0,0,bd.width,bd.height),0xffffff);
                var s:String = String.fromCharCode(asCode);
                drawAscii(bd, s, 0, 0);
                var dot:uint = getBlackVal(bd);
                var data:AsciiData = new AsciiData(s, dot, FONT_SIZE*FONT_SIZE);
                // 同じドット数のものがあったら省く
                if( isSameDot(dot) == false ){
                    asciiArray.push(data);
                }
            }
            //  ASCII文字テーブルをドット数順序にソート
            asciiArray.sortOn("dot", Array.NUMERIC | Array.DESCENDING);

            var maxper:Number = asciiArray[0].per;
            for( var i:int = 0; i < asciiArray.length; ++i ){
                // %を最大のものを1にするため、逆数を掛ける
                asciiArray[i].per *= 1/maxper;
                // ASCII文字テーブルの状態デバッグアウト
                asciiArray[i].traceOut();
            }

            //ラベル
            label.autoSize=TextFieldAutoSize.LEFT;
            label.text = "AUTO ASCIIART GENERATOR\nBy S_Ishimaru";
            label.filters = filter;
            label.textColor = 0xffffff;
            label.x = 2;
            label.y = SCREEN_HEIGHT - label.textHeight - 10;
            addChild(label);

            //カメラの取得
            camera=Camera.getCamera();

            if (camera!=null) {
                //イベントリスナーの追加
                camera.addEventListener(StatusEvent.STATUS,statusHandler);
                //ビデオの生成
                video = new Video( VIDEO_WIDTH, VIDEO_HEIGHT );
                video.attachCamera(camera);
            } else {
                label.text="カメラを利用できません";
            }

            // メインループ追加
            addEventListener(Event.ENTER_FRAME, tick);
            // アプリケーションモード設定
            setApplicationMode(APPMODE_GREEN);

            // ボタン追加
            var buttonY:int = 10;
            var greenButton:TextButton = new TextButton(80, 20, "GREEN");
            greenButton.x = 10;
            greenButton.y = buttonY;
            addChild(greenButton);
            greenButton.addEventListener( MouseEvent.CLICK, greenButtonClick );

            var monoButton:TextButton = new TextButton(80, 20, "MONO");
            monoButton.x = greenButton.x + greenButton.width + 10;
            monoButton.y = buttonY;
            addChild(monoButton);
            monoButton.addEventListener( MouseEvent.CLICK, monoButtonClick );

            var noEffectButton:TextButton = new TextButton(80, 20, "NOEFFECT");
            noEffectButton.x = monoButton.x + monoButton.width + 10;
            noEffectButton.y = buttonY;
            addChild(noEffectButton);
            noEffectButton.addEventListener( MouseEvent.CLICK, noEffectButtonClick );

            var creditButton:TextButton = new TextButton(170, 20, "(C)CyberSpaceCowboys");
            creditButton.x = SCREEN_WIDTH - creditButton.width - 2;
            creditButton.y = SCREEN_HEIGHT - creditButton.height - 10;
            addChild(creditButton);
            creditButton.addEventListener( MouseEvent.CLICK, creditButtonClick );
        }

        /// 背景色の描画
        private function drawBgColor(color:uint):void {
            bgShape.graphics.lineStyle(0,0x000000);
            bgShape.graphics.beginFill(color);
            bgShape.graphics.drawRect(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
            bgShape.graphics.endFill();
        }

        /// CREDITボタン処理 
        private function creditButtonClick(event:MouseEvent):void {
            openWebsite("http://keith.weblogs.jp/cyberspace/");
        }

        /// NOEFFECTボタン処理 
        private function noEffectButtonClick(event:MouseEvent):void {
            setApplicationMode(APPMODE_NOEFFECT);
        }

        /// MONOボタン処理 
        private function monoButtonClick(event:MouseEvent):void {
            setApplicationMode(APPMODE_MONO);
        }

        /// GREENボタン処理 
        private function greenButtonClick(event:MouseEvent):void {
            setApplicationMode(APPMODE_GREEN);
        }

        /**
        * ASCII文字テーブルに同じドット数のものがあるか
        * @param dot ドット数
        * @return あったらtrue、なければfalse
        */
        private function isSameDot(dot:uint):Boolean {
            for( var i:int = 0; i < asciiArray.length; ++i ){
                if( asciiArray[i].dot == dot ){
                    return true;
                }
            }
            return false;
        }

        /// アプリケーションモード設定
        private function setApplicationMode(mode:int):void {
            appMode = mode;

            switch(appMode){
            case APPMODE_MONO:
                bgColor = 0xffffff;
                ascii.textColor = 0x000000;
                drawBgColor(0x333333);
                break;
            case APPMODE_GREEN:
                bgColor = 0x000000;
                ascii.textColor = 0x00ff00;
                drawBgColor(0x334433);
                break;
            case APPMODE_NOEFFECT:
                drawBgColor(0x0020C1);
                break;
            }
        }

        /// ASCIIのフォントサイズの指定(ピクセル単位)
        private function setFontSize(size:int):void {
            var i:uint;
            ascii.text="■";
            for (i=size+10;i>=1;i--) {
                asciiFormat.size=i;
                ascii.setTextFormat(asciiFormat);
                if (ascii.textWidth<=size)
                    break;
            }
        }

        /// メインループ
        private function tick( event:Event ):void
        {
            if(video == null || camera == null){
                return;
            }

            if( camera.muted == true ){
                // カメラがアクセス禁止されている
                return;
            }

            if( appMode == APPMODE_NOEFFECT ){
                // 何もせずにそのままカメラ画像を転送
                bd.draw(video);
                return;
            }

            // ASCIIART化
            // bdTmpにビデオデータを転送            
            bdTmp.draw(video);

            // bdTmpから階調データ作成し、bdにASCII文字を描画
            bd.fillRect(new Rectangle(0,0,bd.width,bd.height),bgColor);

            // drawAsciiを毎ループ呼ぶとやたらと遅いので描画呼び出しを1回に変更。
            var str:String = "";
            for(var y:int = 0; y < video.height; y += FONT_SIZE){
                for(var x:int = 0; x < video.width; x += FONT_SIZE){
                    //drawAscii(bd, getColorStr(bdTmp, x, y), x, y);
                    var s:String = getColorStr(bdTmp, x, y);
                    str += s + s;
                }
                str += "\n";
            }

            drawAscii(bd, str, 0, 0);
        }

        /**
         * BitmapDataに書かれた文字からドットを探してその数を返す
         * @return ドット数
         */
        private function getBlackVal(bitmatData:BitmapData):uint {
            var black:uint = 0;
            var pixArray:ByteArray = bitmatData.getPixels( new Rectangle(0, 0, FONT_SIZE, FONT_SIZE) );
            pixArray.position = 0;

            try {
                while(pixArray.bytesAvailable){
                    /// 白以外の点は黒としてカウントする
                    if( (pixArray.readUnsignedInt() & 0xffffff) != 0xffffff ){
                        ++black;
                    }
                }
            }
            catch(e:EOFError) {
            }

            return black;
        }

        /**
         * startx, startyから、FONT_SIZE分の矩形の中の色を調べて、その色に合う文字列を返す
         */
        private function getColorStr(bitmatData:BitmapData, startx:int, starty:int):String {
            // 全部舐めてると、やたら遅いので真ん中の一点で
            var color:uint = bitmatData.getPixel( startx + FONT_SIZE/2, starty + FONT_SIZE/2 );
            // R,G,Bを足して平均をとる
            var light:Number = ( ((color & 0xff0000) >> 16) + ((color & 0xff00) >> 8) + (color & 0xff) )/3;

            if( appMode == APPMODE_GREEN ){
                // 緑は明るいところを緑で表現するので、そのまま割る
                light /= 0xff;
            }else{
                // 白黒は黒いところを文字で表現するので、255で補数にする
                light = (255 - light) / 0xff;
            }

            /// ASCII文字テーブルから濃度が一番近いものを選択する
            var dec:Number = 1000;
            var no:int = 0;

            for( var i:int = 0; i < asciiArray.length; ++i ){
                var abs:Number = Math.abs(asciiArray[i].per - light)
                if( abs < dec ){
                    no = i;
                    dec = abs;
                }
            }

            return asciiArray[no].str;
        }

        /// ASCII文字の描画
        private function drawAscii( bitmatData:BitmapData, text:String, x:int, y:int):void {
            ascii.text=text;
            var pos:Matrix = new Matrix();
            pos.translate( x, y);
            bitmatData.draw( ascii, pos);
        }

        /// 状態イベントの処理
        private function statusHandler(evt:StatusEvent):void {
            if (camera.muted) label.text="カメラを利用できません";
        }

        /// 指定されているウェブサイトを開く
        private function openWebsite(openURL:String):void {

            var variables:URLVariables = new URLVariables();
            var request:URLRequest = new URLRequest(openURL);

            try {
                navigateToURL(request);
            }
            catch (e:Error) {
                // handle error here
            }

        }
    }
}