#define WINVER 0x0500                                  /* バージョン定義 Windows2000以上 */
#define _WIN32_WINNT WINVER

#include "ruby.h"
#ifndef RUBY_ST_H
#include "st.h"
#endif

#define DXRUBY_EXTERN 1
#include "dxruby.h"
#include "font.h"

static VALUE cFont;         /* フォントクラス       */
static VALUE cFontInfo;     /* フォント情報クラス   */
static VALUE symbol_italic  = Qundef;
static VALUE symbol_weight  = Qundef;

VALUE hash_lookup(VALUE hash, VALUE key);

/*********************************************************************
 * Fontクラス
 *
 * D3DXFontインターフェースを使用してフォントを描画する。
 * スプライト描画に混ぜ込むことでとりあえず実装。
 *********************************************************************/
/*--------------------------------------------------------------------
   参照されなくなったときにGCから呼ばれる関数
 ---------------------------------------------------------------------*/
static void Font_free( struct DXRubyFont *font )
{
    RELEASE( font->pD3DXFont );
    DeleteObject( font->hFont );
}
void Font_release( struct DXRubyFont *font )
{
    /* フォントオブジェクトの開放 */
    if( font->pD3DXFont )
    {
        Font_free( font );
    }
    free( font );
    font = NULL;

    g_iRefAll--;
    if( g_iRefAll == 0 )
    {
        CoUninitialize();
    }
}

/*--------------------------------------------------------------------
   Fontクラスのdispose。
 ---------------------------------------------------------------------*/
static VALUE Font_dispose( VALUE self )
{
    struct DXRubyFont *font = DXRUBY_GET_STRUCT( Font, self );
    DXRUBY_CHECK_DISPOSE( font, pD3DXFont );
    Font_free( font );
    return self;
}

/*--------------------------------------------------------------------
   Fontクラスのdisposed?。
 ---------------------------------------------------------------------*/
static VALUE Font_check_disposed( VALUE self )
{
    if( DXRUBY_GET_STRUCT( Font, self )->pD3DXFont == NULL )
    {
        return Qtrue;
    }

    return Qfalse;
}

/*--------------------------------------------------------------------
   Fontクラスのmark
 ---------------------------------------------------------------------*/
static void Font_mark( struct DXRubyFont *font )
{
    rb_gc_mark( font->vfontname );
    rb_gc_mark( font->vitalic );
    rb_gc_mark( font->vweight );
    font->vglyph_naa = Qnil;
    font->vglyph_aa = Qnil;
}

/*--------------------------------------------------------------------
   Fontクラスのallocate。メモリを確保する為にinitialize前に呼ばれる。
 ---------------------------------------------------------------------*/
static VALUE Font_allocate( VALUE klass )
{
    VALUE obj;
    struct DXRubyFont *font;

    /* DXRubyFontのメモリ取得＆Fontオブジェクト生成 */
    font = malloc( sizeof(struct DXRubyFont) );
    if( font == NULL ) rb_raise( eDXRubyError, "メモリの取得に失敗しました - Font_allocate" );
    obj = Data_Wrap_Struct(klass, Font_mark, Font_release, font);
    font->vfontname = Qnil;
    font->vitalic = Qnil;
    font->vweight = Qnil;
    font->vglyph_aa = Qnil;
    font->vglyph_naa = Qnil;
    return obj;
}


/*--------------------------------------------------------------------
   FontクラスのInitialize
 ---------------------------------------------------------------------*/
static VALUE Font_initialize( int argc, VALUE *argv, VALUE obj )
{
    struct DXRubyFont *font;
    HRESULT hr;
    D3DXFONT_DESC desc;
    VALUE size, vfontname, voption, hash, vweight, vitalic;
    LOGFONT logfont;
    int weight, italic;

    g_iRefAll++;

    rb_scan_args( argc, argv, "12", &size, &vfontname, &hash );

    if( hash == Qnil )
    {
        voption = rb_hash_new();
    }
    else
    {
        Check_Type( hash, T_HASH );
        voption = hash;
    }

    vweight = hash_lookup( voption, symbol_weight );
    vitalic = hash_lookup( voption, symbol_italic );

    if( vweight == Qnil || vweight == Qfalse )
    {
        weight = 400;
    }
    else if( vweight == Qtrue )
    {
        weight = 1000;
    }
    else
    {
        weight = NUM2INT( vweight );
    }

    if( vitalic == Qnil || vitalic == Qfalse )
    {
        italic = FALSE;
    }
    else
    {
        italic = TRUE;
    }

    desc.Height          = NUM2INT( size );
    desc.Width           = 0;
    desc.Weight          = weight;
    desc.MipLevels       = 0;
    desc.Italic          = italic;
    desc.CharSet         = DEFAULT_CHARSET;
    desc.OutputPrecision = 0;
    desc.Quality         = DEFAULT_QUALITY;
    desc.PitchAndFamily  = DEFAULT_PITCH || FF_DONTCARE;

    ZeroMemory( &logfont, sizeof(logfont) );
    logfont.lfHeight          = NUM2INT( size );
    logfont.lfWidth           = 0;
    logfont.lfWeight          = weight;
    logfont.lfItalic          = italic;
    logfont.lfCharSet         = DEFAULT_CHARSET;
    logfont.lfQuality         = ANTIALIASED_QUALITY;

    if( vfontname == Qnil )
    {
        lstrcpy(desc.FaceName, "");
        lstrcpy(logfont.lfFaceName, "");
    }
    else
    {
        VALUE vsjisstr;
        Check_Type(vfontname, T_STRING);
#ifdef HAVE_RB_ENC_STR_NEW
        if( rb_enc_get_index( vfontname ) != 0 )
        {
            vsjisstr = rb_funcall( vfontname, rb_intern( "encode" ), 1, rb_str_new2( sys_encode ) );
        }
        else
        {
            vsjisstr = vfontname;
        }
#else
        vsjisstr = vfontname;
#endif
        lstrcpy(desc.FaceName, RSTRING_PTR( vsjisstr ));
        lstrcpy(logfont.lfFaceName, RSTRING_PTR( vsjisstr ));
    }

    /* フォントオブジェクト取り出し */
    Data_Get_Struct( obj, struct DXRubyFont, font );

    hr = D3DXCreateFontIndirect( g_pD3DDevice, &desc, &font->pD3DXFont );

    if( FAILED( hr ) )
    {
        rb_raise( eDXRubyError, "フォントの作成に失敗しました - D3DXCreateFontIndirect" );
    }

    font->hFont = CreateFontIndirect( &logfont );

    if( font->hFont == NULL )
    {
        rb_raise( eDXRubyError, "フォントの作成に失敗しました - CreateFontIndirect" );
    }

    font->size = NUM2INT( size );
    font->vitalic = vitalic;
    font->vweight = vweight;
#ifdef HAVE_RB_ENC_STR_NEW
    font->vfontname = vfontname == Qnil ? Qnil : rb_str_new_shared( vfontname );
#else
    font->vfontname = vfontname == Qnil ? Qnil : rb_str_new2( RSTRING_PTR(vfontname) );
#endif

    return obj;
}


/*--------------------------------------------------------------------
   Fontクラスのinstall
 ---------------------------------------------------------------------*/
static VALUE Font_install( VALUE klass, VALUE vstr )
{
    int result;
    VALUE vsjisstr;
    Check_Type( vstr, T_STRING );

#ifdef HAVE_RB_ENC_STR_NEW
    if( rb_enc_get_index( vstr ) != 0 )
    {
        vsjisstr = rb_funcall( vstr, rb_intern( "encode" ), 1, rb_str_new2( sys_encode ) );
    }
    else
    {
        vsjisstr = vstr;
    }
#else
    vsjisstr = vstr;
#endif
    result = AddFontResourceEx( RSTRING_PTR( vsjisstr ), 0x10, 0 );
    if( result == 0 )
    {
        rb_raise( eDXRubyError, "フォントのインストールに失敗しました - Font_install" );
    }

    return INT2FIX( result );
}


/*--------------------------------------------------------------------
   フォントの幅を取得する
 ---------------------------------------------------------------------*/
VALUE Font_getWidth( VALUE obj, VALUE vstr )
{
    HDC hDC;
    struct DXRubyFont *font;
    RECT rc;
    VALUE vsjisstr;

    Check_Type( vstr, T_STRING );

    /* フォントオブジェクト取り出し */
    Data_Get_Struct( obj, struct DXRubyFont, font );
    DXRUBY_CHECK_DISPOSE( font, pD3DXFont );

    hDC = GetDC( g_hWnd );
    if( hDC == NULL )
    {
        rb_raise( eDXRubyError, "DCの取得に失敗しました - GetDC" );
    }

    SelectObject( hDC, font->hFont );
    SetRect( &rc, 0, 0, 1, 1 );
#ifdef HAVE_RB_ENC_STR_NEW
    if( rb_enc_get_index( vstr ) != 0 )
    {
        vsjisstr = rb_funcall( vstr, rb_intern( "encode" ), 1, rb_str_new2( sys_encode ) );
    }
    else
    {
        vsjisstr = vstr;
    }
#else
    vsjisstr = vstr;
#endif
    DrawText( hDC, RSTRING_PTR( vsjisstr ), -1, &rc, DT_LEFT | DT_NOCLIP | DT_NOPREFIX | DT_CALCRECT | DT_SINGLELINE);
    ReleaseDC( g_hWnd, hDC );

    return INT2FIX( rc.right );
}


/*--------------------------------------------------------------------
   フォントのサイズを取得する
 ---------------------------------------------------------------------*/
VALUE Font_getSize( VALUE obj )
{
    struct DXRubyFont *font;

    /* フォントオブジェクト取り出し */
    Data_Get_Struct( obj, struct DXRubyFont, font );
    DXRUBY_CHECK_DISPOSE( font, pD3DXFont );

    return INT2FIX( font->size );
}


/*--------------------------------------------------------------------
   フォント名称を取得する
 ---------------------------------------------------------------------*/
static VALUE Font_getFontname( VALUE obj )
{
    struct DXRubyFont *font;

    /* フォントオブジェクト取り出し */
    Data_Get_Struct( obj, struct DXRubyFont, font );
    DXRUBY_CHECK_DISPOSE( font, pD3DXFont );

    return font->vfontname;
}


/*--------------------------------------------------------------------
   イタリックフラグを取得する
 ---------------------------------------------------------------------*/
static VALUE Font_getItalic( VALUE obj )
{
    struct DXRubyFont *font;

    /* フォントオブジェクト取り出し */
    Data_Get_Struct( obj, struct DXRubyFont, font );
    DXRUBY_CHECK_DISPOSE( font, pD3DXFont );

    return font->vitalic;
}


/*--------------------------------------------------------------------
   フォントの太さを取得する
 ---------------------------------------------------------------------*/
static VALUE Font_getWeight( VALUE obj )
{
    struct DXRubyFont *font;

    /* フォントオブジェクト取り出し */
    Data_Get_Struct( obj, struct DXRubyFont, font );
    DXRUBY_CHECK_DISPOSE( font, pD3DXFont );

    return font->vweight;
}

/*--------------------------------------------------------------------
   グリフ情報を取得する
 ---------------------------------------------------------------------*/
char *Font_getGlyph( VALUE self, UINT widechr, HDC hDC, GLYPHMETRICS *gm, VALUE vaa_flag )
{
    struct DXRubyFont *font = DXRUBY_GET_STRUCT( Font, self );
    MAT2 mat2 = {{0,1},{0,0},{0,0},{0,1}};
    VALUE vstr;
    VALUE temp_naa = font->vglyph_naa;
    VALUE temp_aa = font->vglyph_aa;

    if( temp_naa == Qnil )
    {
        temp_naa = rb_hash_new();
    }
    if( temp_aa == Qnil )
    {
        temp_aa = rb_hash_new();
    }

    if( vaa_flag == Qnil )
    {
        if( font->size > 24 )
        {
            vaa_flag = Qtrue;
        }
        else
        {
            vaa_flag = Qfalse;
        }
    }

    if( RTEST(vaa_flag) )
    { /* AAあり */
        int bufsize;
        char *buf;
        vstr = hash_lookup( temp_aa, INT2NUM( widechr ) );

        bufsize = GetGlyphOutlineW( hDC, widechr, GGO_GRAY8_BITMAP,
                                        gm, 0, NULL, &mat2 );

        if( vstr == Qnil )
        {
            buf = alloca( bufsize );
            GetGlyphOutlineW( hDC, widechr, GGO_GRAY8_BITMAP, 
                              gm, bufsize, (LPVOID)buf, &mat2 );

            vstr = rb_str_new( buf, bufsize );
            rb_hash_aset( temp_aa, INT2NUM( widechr ), vstr );
        }
    }
    else
    { /* AAなし。モノクロデータをGRAY8形式に変換してキャッシュする */
        vstr = hash_lookup( temp_naa, INT2NUM( widechr ) );

        if( vstr == Qnil )
        {
            int bufsize_mono, bufsize_gray8;
            char *buf_mono, *buf_gray8;
            int x, y;
            int pitch_mono, pitch_gray8;

            /* モノクロデータ取得 */
            bufsize_mono  = GetGlyphOutlineW( hDC, widechr, GGO_BITMAP,
                                              gm, 0, NULL, &mat2 );
            buf_mono = alloca( bufsize_mono );
            GetGlyphOutlineW( hDC, widechr, GGO_BITMAP, 
                              gm, bufsize_mono, (LPVOID)buf_mono, &mat2 );

            bufsize_gray8 = ((gm->gmBlackBoxX + 3) & 0xfffc) * gm->gmBlackBoxY;
            buf_gray8 = alloca( bufsize_gray8 );

            /* 変換処理 */
            pitch_mono = ((gm->gmBlackBoxX + 31) / 32) * 4;
            pitch_gray8 = ((gm->gmBlackBoxX + 3) & 0xfffc);
            for( y = 0; y < gm->gmBlackBoxY; y++ )
            {
                for( x = 0; x < gm->gmBlackBoxX; x++ )
                {
                    if( *(buf_mono + x/8 + y * pitch_mono) & (1 << (7-(x%8))) )
                    {
                        *(buf_gray8 + x + y * pitch_gray8) = 64;
                    }
                    else
                    {
                        *(buf_gray8 + x + y * pitch_gray8) = 0;
                    }
                }
            }

            vstr = rb_str_new( buf_gray8, bufsize_gray8 );
            rb_hash_aset( temp_naa, INT2NUM( widechr ), vstr );
        }
        else
        {
            int bufsize;
            bufsize = GetGlyphOutlineW( hDC, widechr, GGO_BITMAP,
                                        gm, 0, NULL, &mat2 );
        }
    }

    font->vglyph_naa = temp_naa;
    font->vglyph_aa = temp_aa;
    return RSTRING_PTR( vstr );
}

VALUE Font_getInfo( VALUE self, VALUE vstr )
{
    struct DXRubyFont *font = DXRUBY_GET_STRUCT( Font, self );
    MAT2 mat2 = {{0,1},{0,0},{0,0},{0,1}};
    int bufsize;
    char *buf;
    TEXTMETRIC tm;
    GLYPHMETRICS gm;
    HDC hDC;
    LPWSTR widestr;
    VALUE vwidestr;
    VALUE ary[6];

    /* 描画文字のUTF16LE化 */
#ifdef HAVE_RB_ENC_STR_NEW
    if( rb_enc_get_index( vstr ) != 0 )
    {
        vwidestr = rb_funcall( vstr, rb_intern( "encode" ), 1, rb_str_new2( "UTF-16LE" ) );
    }
    else
    {
        char *buf;
        int bufsize;
        bufsize = MultiByteToWideChar(CP_ACP, 0, RSTRING_PTR( vstr ), RSTRING_LEN( vstr ), 0, 0);
        buf = alloca(bufsize * 2);
        MultiByteToWideChar(CP_ACP, 0, RSTRING_PTR( vstr ), RSTRING_LEN( vstr ), (LPWSTR)buf, bufsize);
        vwidestr = rb_str_new( buf, bufsize*2 );
    }
#else
    {
        char *buf;
        int bufsize;
        bufsize = MultiByteToWideChar(CP_ACP, 0, RSTRING_PTR( vstr ), RSTRING_LEN( vstr ), 0, 0);
        buf = alloca(bufsize * 2);
        MultiByteToWideChar(CP_ACP, 0, RSTRING_PTR( vstr ), RSTRING_LEN( vstr ), (LPWSTR)buf, bufsize);
        vwidestr = rb_str_new( buf, bufsize*2 );
    }
#endif
    widestr = alloca( RSTRING_LEN( vwidestr ) + 2 );
    ZeroMemory( widestr, RSTRING_LEN( vwidestr ) + 2 );
    memcpy( widestr, RSTRING_PTR( vwidestr ), RSTRING_LEN( vwidestr ) );

    hDC = GetDC( g_hWnd );
    SelectObject( hDC, font->hFont );
    GetTextMetrics( hDC, &tm );

    bufsize = GetGlyphOutlineW( hDC, *widestr, GGO_GRAY8_BITMAP,
                                    &gm, 0, NULL, &mat2 );
    ReleaseDC( g_hWnd, hDC );

    ary[0] = INT2NUM(gm.gmBlackBoxX);
    ary[1] = INT2NUM(gm.gmBlackBoxY);
    ary[2] = INT2NUM(gm.gmCellIncX);
    ary[3] = INT2NUM(gm.gmptGlyphOrigin.x);
    ary[4] = INT2NUM(gm.gmptGlyphOrigin.y);
    ary[5] = INT2NUM(tm.tmAscent);
    ary[6] = INT2NUM(tm.tmDescent);

    return rb_class_new_instance(7, ary, cFontInfo);
}


void Init_dxruby_Font()
{
    /* Fontクラス登録 */
    cFont = rb_define_class_under( mDXRuby, "Font", rb_cObject );

    /* Fontクラスにクラスメソッド登録*/
    rb_define_singleton_method(cFont, "install", Font_install, 1);

    /* Fontクラスにメソッド登録*/
    rb_define_private_method( cFont, "initialize", Font_initialize, -1 );
    rb_define_method( cFont, "dispose"   , Font_dispose   , 0 );
    rb_define_method( cFont, "disposed?" , Font_check_disposed, 0 );
    rb_define_method( cFont, "get_width" , Font_getWidth  , 1 );
    rb_define_method( cFont, "getWidth"  , Font_getWidth  , 1 );
    rb_define_method( cFont, "fontname", Font_getFontname  , 0 );
    rb_define_method( cFont, "italic", Font_getItalic  , 0 );
    rb_define_method( cFont, "weight" , Font_getWeight  , 0 );
    rb_define_method( cFont, "size"      , Font_getSize  , 0 );
    rb_define_method( cFont, "info"      , Font_getInfo  , 1 );

    /* Fontオブジェクトを生成した時にinitializeの前に呼ばれるメモリ割り当て関数登録 */
    rb_define_alloc_func( cFont, Font_allocate );

    symbol_italic         = ID2SYM(rb_intern("italic"));
    symbol_weight         = ID2SYM(rb_intern("weight"));

    cFontInfo = rb_struct_define( NULL, "gm_blackbox_x", "gm_blackbox_y", "gm_cellinc_x", "gmpt_glyphorigin_x", "gmpt_glyphorigin_y", "tm_ascent", "tm_descent", 0 );
    rb_define_const( cFont, "FontInfo", cFontInfo );
}

