package harfbuzz

import (
	"testing"

	"github.com/benoitkugler/textlayout/fonts"
	"github.com/benoitkugler/textlayout/language"
)

// ported from harfbuzz/test/api/test-buffer.c Copyright © 2011  Google, Inc. Behdad Esfahbod

var utf32 = [7]rune{'a', 'b', 0x20000, 'd', 'e', 'f', 'g'}

const (
	bufferEmpty = iota
	bufferOneByOne
	bufferUtf32
	bufferNumTypes
)

func newTestBuffer(kind int) *Buffer {
	b := NewBuffer()

	switch kind {
	case bufferEmpty:

	case bufferOneByOne:
		for i := 1; i < len(utf32)-1; i++ {
			b.AddRune(utf32[i], i)
		}

	case bufferUtf32:
		b.AddRunes(utf32[:], 1, len(utf32)-2)

	}
	return b
}

func testBufferProperties(b *Buffer, t *testing.T) {
	/* test default properties */

	assert(t, b.Props.Direction == 0)
	assert(t, b.Props.Script == 0)
	assert(t, b.Props.Language == "")

	b.Props.Language = language.NewLanguage("fa")
	assert(t, b.Props.Language == language.NewLanguage("Fa"))

	/* test clear_contents clears all these properties: */

	//    hb_buffer_clear_contents (b);

	//    assert (t,hb_buffer_get_unicode_funcs (b) == ufuncs);
	//    assert (t,hb_buffer_get_direction (b) == HB_DIRECTION_INVALID);
	//    assert (t,hb_buffer_get_script (b) == HB_SCRIPT_INVALID);
	//    assert (t,hb_buffer_get_language (b) == NULL);

	/* but not these: */

	//    assert (t,hb_buffer_get_flags (b) != HB_BUFFER_FLAGS_DEFAULT);
	//    assert (t,hb_buffer_get_replacement_codepoint (b) != HB_BUFFER_REPLACEMENT_CODEPOINT_DEFAULT);

	/* test reset clears all properties */

	//    hb_buffer_set_direction (b, HB_DIRECTION_RTL);
	//    assert (t,hb_buffer_get_direction (b) == HB_DIRECTION_RTL);

	//    hb_buffer_set_script (b, HB_SCRIPT_ARABIC);
	//    assert (t,hb_buffer_get_script (b) == HB_SCRIPT_ARABIC);

	//    hb_buffer_set_language (b, hb_language_from_string ("fa", -1));
	//    assert (t,hb_buffer_get_language (b) == hb_language_from_string ("Fa", -1));

	//    hb_buffer_set_flags (b, HB_BUFFER_FLAG_BOT);
	//    assert (t,hb_buffer_get_flags (b) == HB_BUFFER_FLAG_BOT);

	//    hb_buffer_set_replacement_codepoint (b, (unsigned int) -1);
	//    assert (t,hb_buffer_get_replacement_codepoint (b) == (unsigned int) -1);

	//    hb_buffer_reset (b);

	//    assert (t,hb_buffer_get_unicode_funcs (b) == hb_unicode_funcs_get_default ());
	//    assert (t,hb_buffer_get_direction (b) == HB_DIRECTION_INVALID);
	//    assert (t,hb_buffer_get_script (b) == HB_SCRIPT_INVALID);
	//    assert (t,hb_buffer_get_language (b) == NULL);
	//    assert (t,hb_buffer_get_flags (b) == HB_BUFFER_FLAGS_DEFAULT);
	//    assert (t,hb_buffer_get_replacement_codepoint (b) == HB_BUFFER_REPLACEMENT_CODEPOINT_DEFAULT);
}

func testBufferContents(b *Buffer, kind int, t *testing.T) {
	if kind == bufferEmpty {
		assertEqualInt(t, len(b.Info), 0)
		return
	}

	glyphs := b.Info
	L := len(glyphs)
	assertEqualInt(t, L, 5)
	assertEqualInt(t, len(b.Pos), 5)

	for _, g := range glyphs {
		assertEqualInt(t, int(g.Mask), 0)
		assertEqualInt(t, int(g.glyphProps), 0)
		assertEqualInt(t, int(g.ligProps), 0)
		assertEqualInt(t, int(g.syllable), 0)
		assertEqualInt(t, int(g.unicode), 0)
		assertEqualInt(t, int(g.complexAux), 0)
		assertEqualInt(t, int(g.complexCategory), 0)
	}

	for i, g := range glyphs {
		cluster := 1 + i
		assertEqualInt(t, int(g.codepoint), int(utf32[1+i]))
		assertEqualInt(t, g.Cluster, cluster)
	}

	/* reverse, test, and reverse back */

	b.Reverse()
	for i, g := range glyphs {
		assertEqualInt(t, int(g.codepoint), int(utf32[L-i]))
	}

	b.Reverse()
	for i, g := range glyphs {
		assertEqualInt(t, int(g.codepoint), int(utf32[1+i]))
	}

	// reverse_clusters works same as reverse for now since each codepoint is
	// in its own cluster

	b.reverseClusters()
	for i, g := range glyphs {
		assertEqualInt(t, int(g.codepoint), int(utf32[L-i]))
	}

	b.reverseClusters()
	for i, g := range glyphs {
		assertEqualInt(t, int(g.codepoint), int(utf32[1+i]))
	}

	/* now form a cluster and test again */
	glyphs[2].Cluster = glyphs[1].Cluster

	/* reverse, test, and reverse back */

	b.Reverse()
	for i, g := range glyphs {
		assertEqualInt(t, int(g.codepoint), int(utf32[L-i]))
	}

	b.Reverse()
	for i, g := range glyphs {
		assertEqualInt(t, int(g.codepoint), int(utf32[1+i]))
	}

	// reverse_clusters twice still should return the original string,
	// but when applied once, the 1-2 cluster should be retained.

	b.reverseClusters()
	for i, g := range glyphs {
		j := L - 1 - i
		if j == 1 {
			j = 2
		} else if j == 2 {
			j = 1
		}
		assertEqualInt(t, int(g.codepoint), int(utf32[1+j]))
	}

	b.reverseClusters()
	for i, g := range glyphs {
		assertEqualInt(t, int(g.codepoint), int(utf32[1+i]))
	}

	/* test reset clears content */

	b.Clear()
	assertEqualInt(t, len(b.Info), 0)
	assertEqualInt(t, len(b.Pos), 0)
}

func testBufferPositions(b *Buffer, t *testing.T) {
	/* Without shaping, positions should all be zero */
	assertEqualInt(t, len(b.Info), len(b.Pos))
	for _, pos := range b.Pos {
		assertEqualInt(t, 0, int(pos.XAdvance))
		assertEqualInt(t, 0, int(pos.YAdvance))
		assertEqualInt(t, 0, int(pos.XOffset))
		assertEqualInt(t, 0, int(pos.YOffset))
		assertEqualInt(t, 0, int(pos.attachChain))
		assertEqualInt(t, 0, int(pos.attachType))
	}

	//    /* test reset clears content */
	//    hb_buffer_reset (b);
	//    assertEqualInt (t, hb_buffer_get_length (b), ==, 0);
}

func TestBuffer(t *testing.T) {
	for i := 0; i < bufferNumTypes; i++ {
		buffer := newTestBuffer(i)

		testBufferProperties(buffer, t)
		testBufferContents(buffer, i, t)
		testBufferPositions(buffer, t)
	}
}

/*
 * Comparing buffers.
 */

/**
 *
 * Flags from comparing two #hb_buffer_t's.
 *
 * Buffer with different #hb_buffer_content_type_t cannot be meaningfully
 * compared in any further detail.
 *
 * For buffers with differing length, the per-glyph comparison is not
 * attempted, though we do still scan reference buffer for dotted circle and
 * `.notdef` glyphs.
 *
 * If the buffers have the same length, we compare them glyph-by-glyph and
 * report which aspect(s) of the glyph info/position are different.
 *
 * Since: 1.5.0
 */
const (
	bufferDiffFlagEqual = 0x0000

	// /* Buffers with different content_type cannot be meaningfully compared
	//  * in any further detail. */
	// HB_BUFFER_DIFF_FLAG_CONTENT_TYPE_MISMATCH	= 0x0001

	/* For buffers with differing length, the per-glyph comparison is not
	 * attempted, though we do still scan reference for dottedcircle / .notdef
	 * glyphs. */
	HB_BUFFER_DIFF_FLAG_LENGTH_MISMATCH = 0x0002

	/* We want to know if dottedcircle / .notdef glyphs are present in the
	 * reference, as we may not care so much about other differences in this
	 * case. */
	HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT        = 0x0004
	HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT = 0x0008

	/* If the buffers have the same length, we compare them glyph-by-glyph
	 * and report which aspect(s) of the glyph info/position are different. */
	HB_BUFFER_DIFF_FLAG_CODEPOINT_MISMATCH   = 0x0010
	HB_BUFFER_DIFF_FLAG_CLUSTER_MISMATCH     = 0x0020
	HB_BUFFER_DIFF_FLAG_GLYPH_FLAGS_MISMATCH = 0x0040
	HB_BUFFER_DIFF_FLAG_POSITION_MISMATCH    = 0x0080
)

/**
 * hb_buffer_diff:
 * @buffer: a buffer.
 * @reference: other buffer to compare to.
 * @dottedcircleGlyph: glyph id of U+25CC DOTTED CIRCLE, or (hb_codepont_t) -1.
 * @positionFuzz: allowed absolute difference in position values.
 *
 * If dottedcircleGlyph is (hb_codepoint_t) -1 then #HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT
 * and #HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT are never returned.  This should be used by most
 * callers if just comparing two buffers is needed.
 *
 * Since: 1.5.0
 **/

func bufferDiff(buffer, reference *Buffer, dottedcircleGlyph fonts.GID, positionFuzz int32) int {
	//    if (buffer.content_type != reference.content_type && buffer.len && reference.len){
	// 	 return HB_BUFFER_DIFF_FLAG_CONTENT_TYPE_MISMATCH;}

	result := bufferDiffFlagEqual
	contains := dottedcircleGlyph != ^fonts.GID(0)

	count := len(reference.Info)

	if len(buffer.Info) != count {
		/*
		 * we can't compare glyph-by-glyph, but we do want to know if there
		 * are .notdef or dottedcircle glyphs present in the reference buffer
		 */
		info := reference.Info
		for i := 0; i < count; i++ {
			if contains && info[i].Glyph == dottedcircleGlyph {
				result |= HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT
			}
			if contains && info[i].Glyph == 0 {
				result |= HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT
			}
		}
		result |= HB_BUFFER_DIFF_FLAG_LENGTH_MISMATCH
		return result
	}

	if count == 0 {
		return result
	}

	bufInfo := buffer.Info
	refInfo := reference.Info
	for i := 0; i < count; i++ {
		if bufInfo[i].codepoint != refInfo[i].codepoint {
			result |= HB_BUFFER_DIFF_FLAG_CODEPOINT_MISMATCH
		}
		if bufInfo[i].Cluster != refInfo[i].Cluster {
			result |= HB_BUFFER_DIFF_FLAG_CLUSTER_MISMATCH
		}
		if (bufInfo[i].Mask & ^refInfo[i].Mask & glyphFlagDefined) != 0 {
			result |= HB_BUFFER_DIFF_FLAG_GLYPH_FLAGS_MISMATCH
		}
		if contains && refInfo[i].Glyph == dottedcircleGlyph {
			result |= HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT
		}
		if contains && refInfo[i].Glyph == 0 {
			result |= HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT
		}
	}

	isDifferent := func(a, b int32) bool {
		d := a - b
		if d < 0 {
			d = -d
		}
		return d > positionFuzz
	}

	bufPos := buffer.Pos
	refPos := reference.Pos
	for i := 0; i < count; i++ {
		if isDifferent(bufPos[i].XAdvance, refPos[i].XAdvance) ||
			isDifferent(bufPos[i].YAdvance, refPos[i].YAdvance) ||
			isDifferent(bufPos[i].XOffset, refPos[i].XOffset) ||
			isDifferent(bufPos[i].YOffset, refPos[i].YOffset) {
			result |= HB_BUFFER_DIFF_FLAG_POSITION_MISMATCH
			break
		}
	}

	return result
}
