#include "mof/Font.hpp"
#include <list>
#include <windows.h>
#include "mof/ConsoleIO.hpp"
#include <vector>
#include "mof/utilities.hpp"

#pragma comment(lib, "gdi32.lib")

const TCHAR* mof::Font::MS_GOTHIC = _T("ＭＳ ゴシック");
const TCHAR* mof::Font::MS_P_GOTHIC = _T("ＭＳ Ｐゴシック");


namespace
{
    struct FontManager 
    {
        std::vector<mof::tstring> additionalFontResources;
        
        ~FontManager()
        {
            foreach( mof::tstring& filename , additionalFontResources )
            {
                RemoveFontResource( filename.c_str() );
            }
        }
    } g_fontManager;
}

struct GlyphData{
    int iOfs_x , iOfs_y , iBmp_w , iBmp_h , level , width;
    BYTE* pBmpBuffer;
    bool valid;

   GlyphData(GLYPHMETRICS& GM , TEXTMETRIC& TM , int _level , int bufSize){
        iOfs_x = GM.gmptGlyphOrigin.x;
        iOfs_y = TM.tmAscent - GM.gmptGlyphOrigin.y;
        iBmp_w = GM.gmBlackBoxX + (4-(GM.gmBlackBoxX%4))%4;
        iBmp_h = GM.gmBlackBoxY;
        level = _level;
        width =  GM.gmCellIncX;
        pBmpBuffer = new BYTE[bufSize];
        valid = true;
   }

   ~GlyphData(){
        delete[] pBmpBuffer;
   }
};

struct mof::Font::Impl{
    HFONT hFont;
    size_t fontSize;
	mof::tstring font_name_;
	mof::font_context context_;

    Impl(const TCHAR* font_name, size_t fontSize_, const mof::font_context& context)
        : font_name_(font_name), hFont(NULL) ,fontSize(fontSize_), context_(context)
    {
    }

    ~Impl(){
        DeleteObject(hFont);
    }

};

mof::Font::Font(const TCHAR* fontName , size_t size)
: m_pImpl(new Impl(fontName, size, mof::font_context()))
{
    LOGFONT lf = {m_pImpl->fontSize , 0, 0, 0, FW_REGULAR, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS , DEFAULT_QUALITY, DEFAULT_PITCH, "" };
    strcpy_s(lf.lfFaceName , fontName );

    if(!(m_pImpl->hFont = CreateFontIndirect(&lf))){
       throw std::runtime_error("Failed --- CreateFontIndirect");
    }
}


mof::Font::Font(const TCHAR* fontName , size_t size, const mof::font_context& context)
: m_pImpl(new Impl(fontName, size, context))
{
    LOGFONT lf = {m_pImpl->fontSize , 0, 0, 0, FW_REGULAR, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS , DEFAULT_QUALITY, DEFAULT_PITCH, "" };
    strcpy_s(lf.lfFaceName , fontName );

    if(!(m_pImpl->hFont = CreateFontIndirect(&lf))){
       throw std::runtime_error("Failed --- CreateFontIndirect");
    }
}

mof::Font::~Font(){
    
}

mof::tstring mof::Font::name() const { return m_pImpl->font_name_; }
size_t mof::Font::size() const { return m_pImpl->fontSize; }
mof::font_context mof::Font::context() const { return m_pImpl->context_; }

mof::PixelMap* mof::Font::createText(const mof::tstring& text) const{

    // デバイスコンテキスト取得
    // デバイスにフォントを持たせないとGetGlyphOutline関数はエラーとなる
    HDC hdc = GetDC(NULL);
    HFONT oldFont = static_cast<HFONT>(SelectObject(hdc , m_pImpl->hFont));
    TEXTMETRIC TM;
    GetTextMetrics( hdc, &TM );

    int sumWidth = 0;
    int maxHeight = 0;
    typedef std::list<GlyphData*>::iterator GDITR;
    std::list<GlyphData*> glyphDataList;
    for(int i = 0 ; text.c_str()[i] ; ){


        // 文字コード取得
        UINT code = 0;
        #if _UNICODE
            // unicodeの場合、文字コードは単純にワイド文字のUINT変換です
            code = (UINT)str[i++];
        #else
            // マルチバイト文字の場合、
            // 1バイト文字のコードは1バイト目のUINT変換、
            // 2バイト文字のコードは[先導コード]*256 + [文字コード]です
            if(IsDBCSLeadByte(text[i])){
                code = (text[i] << 8) | (text[i+1] & 0xff); // 文字
                code &= 0x0000ffff;
                i += 2;
            }
            else{
                code = text[i++];
            }
        #endif

        unsigned int spaceCode = (_T(" ")[0]);
        unsigned int wSpaceCode = (_T("　")[0] << 8) | (_T("　")[1] & 0xff);
        wSpaceCode &= 0x0000ffff;

        // フォントビットマップ取得
        GLYPHMETRICS GM;
		ZeroMemory(&GM, sizeof(GM));
        CONST MAT2 Mat = {{0,1},{0,0},{0,0},{0,1}};
        DWORD size = GetGlyphOutline(hdc, code , GGO_GRAY8_BITMAP, &GM, 0, NULL, &Mat);
		if (size == GDI_ERROR) throw std::runtime_error("failed: GetGlyphOutline");
        GlyphData* pGD = new GlyphData(GM , TM , 65 , size);
        GetGlyphOutline(hdc, code, GGO_GRAY8_BITMAP, &GM, size, pGD->pBmpBuffer , &Mat);

        if(code == spaceCode || code == wSpaceCode){
            //空白は何故かうまく書き込めないことへの対処
            pGD->valid = false;
        }
        glyphDataList.push_back(pGD);
        sumWidth += pGD->width;//全体の幅
        maxHeight = max(maxHeight , TM.tmHeight);
    }

    
    // デバイスコンテキストとフォントハンドルの開放
    SelectObject(hdc, oldFont);
    ReleaseDC(NULL, hdc);

    typedef mof::PixelMap::size_type size_type;
    boost::array<size_type , 2> sizes = {{ sumWidth , maxHeight }};
    mof::PixelMap* pPixelMap = new mof::PixelMap(sizes);

    // フォント情報の書き込み
    // iOfs_x, iOfs_y : 書き出し位置(左上)
    // iBmp_w, iBmp_h : フォントビットマップの幅高
    // Level : α値の段階 (GGO_GRAY4_BITMAPなので17段階)

    DWORD Alpha, Color;
    int hoseiX = 0;
    for(int y = 0 ; y < maxHeight ; y++){
        for(int x = 0 ; x < sumWidth ; x++){
            (*pPixelMap)[x][y] = 0;
        }
    }

	// TODO 高速化
	mof::Color4f color = context().font_color;
	color.alpha = 0;
	mof::Color code = color.toColorCode();
    for(GDITR itr = glyphDataList.begin() ; itr != glyphDataList.end() ; ++itr){
       for(int y = (*itr)->iOfs_y;  y < (*itr)->iOfs_y + (*itr)->iBmp_h;  y++){
            if(!(*itr)->valid)break;//空白文字なら書き込みを無視する
            for(int x = (*itr)->iOfs_x + hoseiX ; x < (*itr)->iOfs_x + (*itr)->iBmp_w + hoseiX && x < sumWidth ;  x++){
				if (x < 0) continue;// GM.gmptGlyphOrigin.xが負値になることがある
                Alpha = (255 * (*itr)->pBmpBuffer[x - ((*itr)->iOfs_x + hoseiX) + (*itr)->iBmp_w * ( y - (*itr)->iOfs_y ) ] ) / ((*itr)->level-1);
				Color = code | (Alpha<<24);
                
                (*pPixelMap)[x][y] = Color;
                
            }
       }
       hoseiX += (*itr)->width;
       delete *itr;
   }

    return pPixelMap;
}


bool mof::Font::addFontResource(const mof::tstring& filename ){
    g_fontManager.additionalFontResources.push_back(filename);
    return AddFontResource( filename.c_str() ) ? true : false ;
}
