package font

import (
	

	ot 
	
)

// name values corresponding to the xxxConsts arrays
var (
	styleStrings   [len(styleConsts)]string
	weightStrings  [len(weightConsts)]string
	stretchStrings [len(stretchConsts)]string
)

func init() {
	for ,  := range styleConsts {
		styleStrings[] = .name
	}
	for ,  := range weightConsts {
		weightStrings[] = .name
	}
	for ,  := range stretchConsts {
		stretchStrings[] = .name
	}
}

var styleConsts = [...]struct {
	name  string
	value Style
}{
	{"italic", StyleItalic},
	{"kursiv", StyleItalic},
	{"oblique", StyleItalic}, // map Oblique to Italic
}

var weightConsts = [...]struct {
	name  string
	value Weight
}{
	{"thin", WeightThin},
	{"extralight", WeightExtraLight},
	{"ultralight", WeightExtraLight},
	{"light", WeightLight},
	{"demilight", (WeightLight + WeightNormal) / 2},
	{"semilight", (WeightLight + WeightNormal) / 2},
	{"book", WeightNormal - 20},
	{"regular", WeightNormal},
	{"normal", WeightNormal},
	{"medium", WeightMedium},
	{"demibold", WeightSemibold},
	{"demi", WeightSemibold},
	{"semibold", WeightSemibold},
	{"extrabold", WeightExtraBold},
	{"superbold", WeightExtraBold},
	{"ultrabold", WeightExtraBold},
	{"bold", WeightBold},
	{"ultrablack", WeightBlack + 20},
	{"superblack", WeightBlack + 20},
	{"extrablack", WeightBlack + 20},
	{"black", WeightBlack},
	{"heavy", WeightBlack},
}

var stretchConsts = [...]struct {
	name  string
	value Stretch
}{
	{"ultracondensed", StretchUltraCondensed},
	{"extracondensed", StretchExtraCondensed},
	{"semicondensed", StretchSemiCondensed},
	{"condensed", StretchCondensed},
	{"normal", StretchNormal},
	{"semiexpanded", StretchSemiExpanded},
	{"extraexpanded", StretchExtraExpanded},
	{"ultraexpanded", StretchUltraExpanded},
	{"expanded", StretchExpanded},
	{"extended", StretchExpanded},
}

// Style (also called slant) allows italic or oblique faces to be selected.
type Style uint8

// note that we use the 0 value to indicate no style has been found yet
const (
	// A face that is neither italic not obliqued.
	StyleNormal Style = iota + 1
	// A form that is generally cursive in nature or slanted.
	// This groups what is usually called Italic or Oblique.
	StyleItalic
)

// Weight is the degree of blackness or stroke thickness of a font.
// This value ranges from 100.0 to 900.0, with 400.0 as normal.
type Weight float32

const (
	// Thin weight (100), the thinnest value.
	WeightThin Weight = 100
	// Extra light weight (200).
	WeightExtraLight Weight = 200
	// Light weight (300).
	WeightLight Weight = 300
	// Normal (400).
	WeightNormal Weight = 400
	// Medium weight (500, higher than normal).
	WeightMedium Weight = 500
	// Semibold weight (600).
	WeightSemibold Weight = 600
	// Bold weight (700).
	WeightBold Weight = 700
	// Extra-bold weight (800).
	WeightExtraBold Weight = 800
	// Black weight (900), the thickest value.
	WeightBlack Weight = 900
)

// Stretch is the width of a font as an approximate fraction of the normal width.
// Widths range from 0.5 to 2.0 inclusive, with 1.0 as the normal width.
type Stretch float32

const (
	// Ultra-condensed width (50%), the narrowest possible.
	StretchUltraCondensed Stretch = 0.5
	// Extra-condensed width (62.5%).
	StretchExtraCondensed Stretch = 0.625
	// Condensed width (75%).
	StretchCondensed Stretch = 0.75
	// Semi-condensed width (87.5%).
	StretchSemiCondensed Stretch = 0.875
	// Normal width (100%).
	StretchNormal Stretch = 1.0
	// Semi-expanded width (112.5%).
	StretchSemiExpanded Stretch = 1.125
	// Expanded width (125%).
	StretchExpanded Stretch = 1.25
	// Extra-expanded width (150%).
	StretchExtraExpanded Stretch = 1.5
	// Ultra-expanded width (200%), the widest possible.
	StretchUltraExpanded Stretch = 2.0
)

// Aspect stores the properties that specify which font in a family to use:
// style, weight, and stretchiness.
type Aspect struct {
	Style   Style
	Weight  Weight
	Stretch Stretch
}

// aspect returns the [aspect] of the font,
// defaulting to regular style.
func ( *fontDescriptor) () Aspect {
	// use rawAspect and additionalStyle to infer the Aspect

	 := .rawAspect() // load the aspect properties ...

	// ... try to fill the missing one with the "style"
	.inferFromStyle(.additionalStyle())

	// ... and finally add default to regular values :
	// StyleNormal, WeightNormal, StretchNormal
	.SetDefaults()

	return 
}

// some fonts includes aspect information in a string description,
// usually called "style"
// inferFromStyle scans such a string and fills the missing fields,
func ( *Aspect) ( string) {
	 = NormalizeFamily()

	if .Style == 0 {
		if  := stringContainsConst(, styleStrings[:]);  != -1 {
			.Style = styleConsts[].value
		}
	}

	if .Weight == 0 {
		if  := stringContainsConst(, weightStrings[:]);  != -1 {
			.Weight = weightConsts[].value
		}
	}

	if .Stretch == 0 {
		if  := stringContainsConst(, stretchStrings[:]);  != -1 {
			.Stretch = stretchConsts[].value
		}
	}
}

// SetDefaults replace unspecified values by the default values: StyleNormal, WeightNormal, StretchNormal
func ( *Aspect) () {
	if .Style == 0 {
		.Style = StyleNormal
	}

	if .Stretch == 0 {
		.Stretch = StretchNormal
	}

	if .Weight == 0 {
		.Weight = WeightNormal
	}
}

func ( *fontDescriptor) () string {
	var  string
	if .os2 != nil && .os2.fsSelection&256 != 0 {
		 = .names.Name(namePreferredSubfamily)
		if  == "" {
			 = .names.Name(nameFontSubfamily)
		}
	} else {
		 = .names.Name(nameWWSSubfamily)
		if  == "" {
			 = .names.Name(namePreferredSubfamily)
		}
		if  == "" {
			 = .names.Name(nameFontSubfamily)
		}
	}
	 = strings.TrimSpace()
	return 
}

func ( *fontDescriptor) () Aspect {
	var (
		   Style
		  Weight
		 Stretch
	)

	if .os2 != nil {
		// We have an OS/2 table; use the `fsSelection' field.  Bit 9
		// indicates an oblique font face.  This flag has been
		// introduced in version 1.5 of the OpenType specification.
		if .os2.fsSelection&(1<<9) != 0 || .os2.fsSelection&1 != 0 {
			 = StyleItalic
		}

		 = Weight(.os2.usWeightClass)

		switch .os2.usWidthClass {
		case 1:
			 = StretchUltraCondensed
		case 2:
			 = StretchExtraCondensed
		case 3:
			 = StretchCondensed
		case 4:
			 = StretchSemiCondensed
		case 5:
			 = StretchNormal
		case 6:
			 = StretchSemiExpanded
		case 7:
			 = StretchExpanded
		case 8:
			 = StretchExtraExpanded
		case 9:
			 = StretchUltraExpanded
		}

	} else {
		// this is an old Mac font, use the header field
		if  := .head.MacStyle&2 != 0;  {
			 = StyleItalic
		}
		if  := .head.MacStyle&1 != 0;  {
			 = WeightBold
		}
	}

	return Aspect{, , }
}

var rp = strings.NewReplacer(" ", "", "\t", "")

// NormalizeFamily removes spaces and lower the given string.
func ( string) string { return rp.Replace(strings.ToLower()) }

// returns the index in `constants` of a constant contained in `str`,
// or -1
func stringContainsConst( string,  []string) int {
	for ,  := range  {
		if strings.Contains(, ) {
			return 
		}
	}
	return -1
}

const (
	nameFontFamily         tables.NameID = 1
	nameFontSubfamily      tables.NameID = 2
	namePreferredFamily    tables.NameID = 16 // or Typographic Family
	namePreferredSubfamily tables.NameID = 17 // or Typographic Subfamily
	nameWWSFamily          tables.NameID = 21 //
	nameWWSSubfamily       tables.NameID = 22 //
)

type os2Desc struct {
	usWeightClass uint16
	usWidthClass  uint16
	fsSelection   uint16
}

func newOS2Desc( tables.Os2) *os2Desc {
	return &os2Desc{
		usWeightClass: .USWeightClass,
		usWidthClass:  .USWidthClass,
		fsSelection:   .FsSelection,
	}
}

// fontDescriptor provides access to family and aspect
type fontDescriptor struct {
	// these tables are required both in Family
	// and Aspect
	os2   *os2Desc // optional
	names tables.Name
	head  tables.Head
}

func newFontDescriptor( *ot.Loader,  []byte) (fontDescriptor, []byte) {
	var  fontDescriptor

	// load tables, all considered optional
	, _ = .RawTableTo(ot.MustNewTag("OS/2"), )
	if , ,  := tables.ParseOs2();  == nil {
		.os2 = newOS2Desc()
	}

	.head, , _ = LoadHeadTable(, )

	, _ = .RawTableTo(ot.MustNewTag("name"), )
	.names, _, _ = tables.ParseName()

	return , 
}

// family returns the font family name.
func ( *fontDescriptor) () string {
	var  string
	if .os2 != nil && .os2.fsSelection&256 != 0 {
		 = .names.Name(namePreferredFamily)
		if  == "" {
			 = .names.Name(nameFontFamily)
		}
	} else {
		 = .names.Name(nameWWSFamily)
		if  == "" {
			 = .names.Name(namePreferredFamily)
		}
		if  == "" {
			 = .names.Name(nameFontFamily)
		}
	}
	return 
}

func max(,  int) int {
	if  >  {
		return 
	}
	return 
}

func abs( int) int {
	if  < 0 {
		return -
	}
	return 
}

func approximatelyEqual(,  int) bool { return abs(-)*33 <= max(abs(), abs()) }

// IsMonospace returns 'true' if the font is monospace,
// by inspecting the horizontal advances of its glyphs.
func ( *Font) () bool {
	// code adapted from fontconfig

	// try the fast shortcuts
	if .post.isFixedPitch {
		return true
	}

	if .hmtx.IsEmpty() {
		// we can't be sure, so be conservative
		return false
	}

	if len(.hmtx.Metrics) == 1 {
		return true
	}

	// directly read the advances in the 'hmtx' table
	var  int
	for ,  := range .hmtx.Metrics {
		if  == 0 { // ignore the 'unset' glyph, which may be different
			continue
		}
		 := int(.AdvanceWidth)
		if  == 0 { // do not count zero as a proper width
			continue
		}

		if  == 0 {
			 = 
			continue
		}

		if approximatelyEqual(, ) {
			continue
		}

		// two distinct advances : the font is not monospace
		return false
	}

	return true
}

// Description provides font metadata.
type Description struct {
	Family string
	Aspect Aspect
}

// Describe provides access to family and aspect.
//
// 'buffer' may be provided to reduce allocations.
//
// It provides an efficient API, loading only the mininum
// tables required. See also the method [Font.Describe]
// if you already have loaded the font.
func ( *ot.Loader,  []byte) (Description, []byte) {
	,  := newFontDescriptor(, )
	return Description{.family(), .aspect()}, 
}

// Describe provides access to family and aspect.
//
// See also the package level function [Describe],
// which is more efficient if you only need the font
// metadata.
func ( *Font) () Description {
	 := fontDescriptor{.os2.os2Desc, .names, .head}
	return Description{.family(), .aspect()}
}

type STAT = tables.STAT