#include "main.h"
#include "state.h"
#include "text.h"
#include "textures.h"
#include "lua_core.h"

// DrawTextBoxed is modified DrawTextBoxedSelectable from raylib [text] example - Rectangle bounds

// Draw text using font inside rectangle limits
static int DrawTextBoxed( Font font, const char *text, Rectangle rec, float fontSize, float spacing,
bool wordWrap, Color *tints, int tintCount, Color *backTints, int backTintCount )
{
    int length = TextLength(text);  // Total length in bytes of the text, scanned by codepoints in loop

    float textOffsetY = 0;          // Offset between lines (on line break '\n')
    float textOffsetX = 0.0f;       // Offset X to next character to draw

    float scaleFactor = fontSize/(float)font.baseSize;     // Character rectangle scaling factor

    // Word/character wrapping mechanism variables
    enum { MEASURE_STATE = 0, DRAW_STATE = 1 };
    int state = wordWrap? MEASURE_STATE : DRAW_STATE;

    int startLine = -1;         // Index where to begin drawing (where a line begins)
    int endLine = -1;           // Index where to stop drawing (where a line ends)
    int lastk = -1;             // Holds last value of the character position
	Color tint = BLACK;
	Color backTint = BLANK;
	Vector2 mousePos = GetMousePosition();
	int mouseChar = -1;

    for (int i = 0, k = 0; i < length; i++, k++)
    {
		if ( i < tintCount ) {
			tint = tints[i];
		}
		if ( i < backTintCount ) {
			backTint = backTints[i];
		}
        // Get next codepoint from byte string and glyph index in font
        int codepointByteCount = 0;
        int codepoint = GetCodepoint(&text[i], &codepointByteCount);
        int index = GetGlyphIndex(font, codepoint);

        // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
        // but we need to draw all of the bad bytes using the '?' symbol moving one byte
        if (codepoint == 0x3f) codepointByteCount = 1;
        i += (codepointByteCount - 1);

        float glyphWidth = 0;
        if (codepoint != '\n')
        {
            glyphWidth = (font.glyphs[index].advanceX == 0) ? font.recs[index].width*scaleFactor : font.glyphs[index].advanceX*scaleFactor;

            if (i + 1 < length) glyphWidth = glyphWidth + spacing;
        }

        // NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside of the rec container
        // We store this info in startLine and endLine, then we change states, draw the text between those two variables
        // and change states again and again recursively until the end of the text (or until we get outside of the container).
        // When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately
        // and begin drawing on the next line before we can get outside the container.
        if (state == MEASURE_STATE)
        {
            // TODO: There are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more
            // Ref: http://jkorpela.fi/chars/spaces.html
            if ((codepoint == ' ') || (codepoint == '\t') || (codepoint == '\n')) endLine = i;

            if ((textOffsetX + glyphWidth) > rec.width)
            {
                endLine = (endLine < 1)? i : endLine;
                if (i == endLine) endLine -= codepointByteCount;
                if ((startLine + codepointByteCount) == endLine) endLine = (i - codepointByteCount);

                state = !state;
            }
            else if ((i + 1) == length)
            {
                endLine = i;
                state = !state;
            }
            else if (codepoint == '\n') state = !state;

            if (state == DRAW_STATE)
            {
                textOffsetX = 0;
                i = startLine;
                glyphWidth = 0;

                // Save character position when we switch states
                int tmp = lastk;
                lastk = k - 1;
                k = tmp;
            }
        }
        else
        {
            if (codepoint == '\n')
            {
                if (!wordWrap)
                {
                    textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor;
                    textOffsetX = 0;
                }
            }
            else
            {
                if (!wordWrap && ((textOffsetX + glyphWidth) > rec.width))
                {
                    textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor;
                    textOffsetX = 0;
                }

                // When text overflows rectangle height limit, just stop drawing
                if ((textOffsetY + font.baseSize*scaleFactor) > rec.height) break;

                // Draw selection background
                // bool isGlyphSelected = false;
                // if ((selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength)))
                // {
                //     DrawRectangleRec((Rectangle){ rec.x + textOffsetX - 1, rec.y + textOffsetY, glyphWidth, (float)font.baseSize*scaleFactor }, selectBackTint);
                //     isGlyphSelected = true;
                // }

				if ( CheckCollisionPointRec( mousePos, (Rectangle){ rec.x + textOffsetX - 1, rec.y + textOffsetY, glyphWidth, (float)font.baseSize*scaleFactor } ) ) {
					mouseChar = i;
				}

				if ( 0 < backTint.a ) {
					DrawRectangleRec((Rectangle){ rec.x + textOffsetX - 1, rec.y + textOffsetY, glyphWidth, (float)font.baseSize*scaleFactor }, backTint);
				}

                // Draw current character glyph
                if ((codepoint != ' ') && (codepoint != '\t'))
                {
                    // DrawTextCodepoint(font, codepoint, (Vector2){ rec.x + textOffsetX, rec.y + textOffsetY }, fontSize, isGlyphSelected? selectTint : tint);
                    DrawTextCodepoint( font, codepoint, (Vector2){ rec.x + textOffsetX, rec.y + textOffsetY }, fontSize, tint );
                }
            }

            if (wordWrap && (i == endLine))
            {
                textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor;
                textOffsetX = 0;
                startLine = endLine;
                endLine = -1;
                glyphWidth = 0;
                // selectStart += lastk - k;
                k = lastk;

                state = !state;
            }
        }

        if ((textOffsetX != 0) || (codepoint != ' ')) textOffsetX += glyphWidth;  // avoid leading spaces
    }
	return mouseChar;
}

/*
## Text - Font loading/unloading functions
*/

/*
> RL.GetFontDefault()

Get the default Font. Return as lightuserdata
*/
int ltextGetFontDefault( lua_State *L ) {
	lua_pushlightuserdata( L, &state->defaultFont );

	return 1;
}

/*
> font = RL.LoadFont( string fileName )

Load font from file into GPU memory (VRAM)

- Failure return nil
- Success return Font
*/
int ltextLoadFont( lua_State *L ) {
	if ( FileExists( luaL_checkstring( L, 1 ) ) ) {
		uluaPushFont( L, LoadFont( lua_tostring( L, 1 ) ) );

		return 1;
	}
	TraceLog( state->logLevelInvalid, "Invalid file '%s'", lua_tostring( L, 1 ) );
	lua_pushnil( L );

	return 1;
}

/*
> font = RL.LoadFontEx( string fileName, int fontSize, int{} codepoints )

Load font from file with extended parameters, use NULL for codepoints to load the default character set

- Failure return nil
- Success return Font
*/
int ltextLoadFontEx( lua_State *L ) {
	int fontSize = luaL_checkinteger( L, 2 );

	if ( FileExists( luaL_checkstring( L, 1 ) ) ) {
		if ( lua_istable( L, 3 ) ) {
			int codepointCount = uluaGetTableLen( L, 3 );
			int codepoints[ codepointCount ];

			int t = 3;
			int i = 0;
			lua_pushnil( L );

			while ( lua_next( L, t ) != 0 ) {
				codepoints[i] = lua_tointeger( L, -1 );

				i++;
				lua_pop( L, 1 );
			}
			uluaPushFont( L, LoadFontEx( lua_tostring( L, 1 ), fontSize, codepoints, codepointCount ) );

			return 1;
		}
		uluaPushFont( L, LoadFontEx( lua_tostring( L, 1 ), fontSize, NULL, 0 ) );

		return 1;
	}
	TraceLog( state->logLevelInvalid, "Invalid file '%s'", lua_tostring( L, 1 ) );
	lua_pushnil( L );

	return 1;
}

/*
> font = RL.LoadFontFromImage( Image image, Color key, int firstChar )

Load font from Image (XNA style)

- Success return Font
*/
int ltextLoadFontFromImage( lua_State *L ) {
	Image *image = uluaGetImage( L, 1 );
	Color key = uluaGetColor( L, 2 );
	int firstChar = luaL_checkinteger( L, 3 );

	uluaPushFont( L, LoadFontFromImage( *image, key, firstChar ) );

	return 1;
}

/*
> isReady = RL.IsFontReady( Font font )

Check if a font is ready

- Success return bool
*/
int ltextIsFontReady( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );

	lua_pushboolean( L, IsFontReady( *font ) );

	return 1;
}

/*
> RL.UnloadFont( Font font )

Unload font from GPU memory (VRAM)
*/
int ltextUnloadFont( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );

	UnloadFont( *font );

	return 0;
}

/*
## Text - Text drawing functions
*/

/*
> RL.DrawFPS( Vector2 pos )

Draw current FPS
*/
int ltextDrawFPS( lua_State *L ) {
	Vector2 pos = uluaGetVector2( L, 1 );

	DrawFPS( pos.x, pos.y );

	return 0;
}

/*
> RL.DrawText( string text, Vector2 position, float fontSize, Color tint )

Draw text (using default font)
*/
int ltextDrawText( lua_State *L ) {
	Vector2 position = uluaGetVector2( L, 2 );
	float fontSize = luaL_checknumber( L, 3 );
	Color tint = uluaGetColor( L, 4 );

	DrawText( luaL_checkstring( L, 1 ), position.x, position.y, fontSize, tint );

	return 0;
}

/*
> RL.DrawTextEx( Font font, string text, Vector2 position, float fontSize, float spacing, Color tint )

Draw text using font and additional parameters
*/
int ltextDrawTextEx( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	Vector2 position = uluaGetVector2( L, 3 );
	float fontSize = luaL_checknumber( L, 4 );
	float spacing = luaL_checknumber( L, 5 );
	Color tint = uluaGetColor( L, 6 );

	DrawTextEx( *font, luaL_checkstring( L, 2 ), position, fontSize, spacing, tint );

	return 0;
}

/*
> RL.DrawTextPro( Font font, string text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint )

Draw text using Font and pro parameters (rotation)
*/
int ltextDrawTextPro( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	Vector2 position = uluaGetVector2( L, 3 );
	Vector2 origin = uluaGetVector2( L, 4 );
	float rotation = luaL_checknumber( L, 5 );
	float fontSize = luaL_checknumber( L, 6 );
	float spacing = luaL_checknumber( L, 7 );
	Color tint = uluaGetColor( L, 8 );

	DrawTextPro( *font, luaL_checkstring( L, 2 ), position, origin, rotation, fontSize, spacing, tint );

	return 0;
}

/*
> RL.DrawTextCodepoint( Font font, int codepoint, Vector2 position, float fontSize, Color tint )

Draw one character (codepoint)
*/
int ltextDrawTextCodepoint( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	int codepoint = luaL_checkinteger( L, 2 );
	Vector2 position = uluaGetVector2( L, 3 );
	float fontSize = luaL_checknumber( L, 4 );
	Color tint = uluaGetColor( L, 5 );

	DrawTextCodepoint( *font, codepoint, position, fontSize, tint );

	return 0;
}

/*
> RL.DrawTextCodepoints( Font font, int{} codepoints, Vector2 position, float fontSize, float spacing, Color tint )

Draw multiple character (codepoint)
*/
int ltextDrawTextCodepoints( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	Vector2 position = uluaGetVector2( L, 3 );
	float fontSize = luaL_checknumber( L, 4 );
	float spacing = luaL_checknumber( L, 5 );
	Color tint = uluaGetColor( L, 6 );

	int codepointCount = uluaGetTableLen( L, 2 );
	int codepoints[ codepointCount ];

	int t = 2;
	int i = 0;
	lua_pushnil( L );

	while ( lua_next( L, t ) != 0 ) {
		codepoints[i] = lua_tointeger( L, -1 );

		i++;
		lua_pop( L, 1 );
	}
	DrawTextCodepoints( *font, codepoints, codepointCount, position, fontSize, spacing, tint );

	return 0;
}

/*
> mouseCharId = RL.DrawTextBoxed(Font font, string text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint )

Draw text using font inside rectangle limits. Return character id from mouse position (default -1). Function from raylib [text] example - Rectangle bounds.

- Success return int
*/
int ltextDrawTextBoxed( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	const char *text = luaL_checkstring( L, 2 );
	Rectangle rec = uluaGetRectangle( L, 3 );
	float fontSize = luaL_checknumber( L, 4 );
	float spacing = luaL_checknumber( L, 5 );
	bool wordWrap = uluaGetBoolean( L, 6 );
	Color tint = uluaGetColor( L, 7 );

	lua_pushinteger( L, DrawTextBoxed( *font, text, rec, fontSize, spacing, wordWrap, &tint, 1, NULL, 0 ) );

	return 1;
}

/*
> mouseCharId = RL.DrawTextBoxedTinted( Font font, string text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tints, Color backTints )

Draw text using font inside rectangle limits with support for tint and background tint for each character. Return character id from mouse position (default -1)

- Success return int
*/
int ltextDrawTextBoxedTinted( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	const char *text = luaL_checkstring( L, 2 );
	Rectangle rec = uluaGetRectangle( L, 3 );
	float fontSize = luaL_checknumber( L, 4 );
	float spacing = luaL_checknumber( L, 5 );
	bool wordWrap = uluaGetBoolean( L, 6 );
	int tintCount = uluaGetTableLen( L, 7 );
	int backTintCount = uluaGetTableLen( L, 8 );

	Color tints[ tintCount ];
	Color backTints[ backTintCount ];

	/* Tints. */
	int t = 7, i = 0;
	lua_pushnil( L );

	while ( lua_next( L, t ) != 0 ) {
		tints[i] = uluaGetColor( L, lua_gettop( L ) );
		i++;
		lua_pop( L, 1 );
	}
	/* Back tints. */
	t = 8; i = 0;
	lua_pushnil( L );

	while ( lua_next( L, t ) != 0 ) {
		backTints[i] = uluaGetColor( L, lua_gettop( L ) );
		i++;
		lua_pop( L, 1 );
	}
	lua_pushinteger( L, DrawTextBoxed( *font, text, rec, fontSize, spacing, wordWrap, tints, tintCount, backTints, backTintCount ) );

	return 1;
}

/*
## Text - Text font info functions
*/

/*
> size = RL.SetTextLineSpacing( int spacing )

Set vertical line spacing when drawing with line-breaks
*/
int ltextSetTextLineSpacing( lua_State *L ) {
	int spacing = luaL_checkinteger( L, 1 );

	SetTextLineSpacing( spacing );
}

/*
> size = RL.MeasureText( Font font, string text, float fontSize, float spacing )

Measure string size for Font

- Success return Vector2
*/
int ltextMeasureText( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	float fontSize = luaL_checknumber( L, 3 );
	float spacing = luaL_checknumber( L, 4 );

	uluaPushVector2( L, MeasureTextEx( *font, luaL_checkstring( L, 2 ), fontSize, spacing ) );

	return 1;
}

/*
> index = RL.GetGlyphIndex( Font font, int codepoint )

Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found

- Success return int
*/
int ltextGetGlyphIndex( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	int codepoint = luaL_checkinteger( L, 2 );

	lua_pushinteger( L, GetGlyphIndex( *font, codepoint ) );

	return 1;
}

/*
> glyphInfo = RL.GetGlyphInfo( Font font, int codepoint )

Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found.
Return Image as lightuserdata

- Success return GlyphInfo
*/
int ltextGetGlyphInfo( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	int codepoint = luaL_checkinteger( L, 2 );

	int id = GetGlyphIndex( *font, codepoint );
	uluaPushGlyphInfo( L, font->glyphs[id], &font->glyphs[id].image );

	return 1;
}

/*
> rect = RL.GetGlyphAtlasRec( Font font, int codepoint )

Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found

- Success return Rectangle
*/
int ltextGetGlyphAtlasRec( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );
	int codepoint = luaL_checkinteger( L, 2 );

	uluaPushRectangle( L, GetGlyphAtlasRec( *font, codepoint ) );

	return 1;
}

/*
> baseSize = RL.GetFontBaseSize( Font font )

Get font base size (default chars height)

- Success return int
*/
int ltextGetFontBaseSize( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );

	lua_pushinteger( L, font->baseSize );

	return 1;
}

/*
> glyphCount = RL.GetFontGlyphCount( Font font )

Get font number of glyph characters

- Success return int
*/
int ltextGetFontGlyphCount( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );

	lua_pushinteger( L, font->glyphCount );

	return 1;
}

/*
> glyphPadding = RL.GetFontGlyphPadding( Font font )

Get font padding around the glyph characters

- Success return int
*/
int ltextGetFontGlyphPadding( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );

	lua_pushinteger( L, font->glyphPadding );

	return 1;
}

/*
> texture = RL.GetFontTexture( Font font )

Get font texture atlas containing the glyphs. Return as lightuserdata

- Success return Texture
*/
int ltextGetFontTexture( lua_State *L ) {
	Font *font = uluaGetFont( L, 1 );

	// uluaPushTexture( L, font->texture );
	lua_pushlightuserdata( L, &font->texture );

	return 1;
}
