// SPDX-License-Identifier: Unlicense OR BSD-3-Clause

package font

import 

// shared between GSUB and GPOS
type Layout struct {
	Scripts           []Script
	Features          []Feature
	FeatureVariations []tables.FeatureVariationRecord
}

func newLayout( tables.Layout) Layout {
	 := len(.FeatureList.Features)
	 := Layout{
		Scripts:  make([]Script, len(.ScriptList.Scripts)),
		Features: make([]Feature, ),
	}
	for ,  := range .ScriptList.Scripts {
		if  := .DefaultLangSys;  != nil {
			sanitizeLangSys(, )
		}
		for  := range .LangSys {
			sanitizeLangSys(&.LangSys[], )
		}
		.Scripts[] = Script{
			Script: ,
			Tag:    .ScriptList.Records[].Tag,
		}
	}
	for ,  := range .FeatureList.Features {
		.Features[] = Feature{
			Feature: ,
			Tag:     .FeatureList.Records[].Tag,
		}
	}
	if .FeatureVariations != nil {
		.FeatureVariations = .FeatureVariations.FeatureVariationRecords
	}
	return 
}

func sanitizeLangSys( *tables.LangSys,  int) {
	if int(.RequiredFeatureIndex) >=  {
		// invalid index : replace it by the sentinel value
		.RequiredFeatureIndex = 0xFFFF
	}
}

type Script struct {
	tables.Script
	Tag Tag
}

type Feature struct {
	tables.Feature
	Tag Tag
}

// FindScript looks for [script] and return its index into the Scripts slice,
// or -1 if the tag is not found.
func ( *Layout) ( Tag) int {
	// Scripts is sorted: binary search
	,  := 0, len(.Scripts)
	for  <  {
		 :=  + (-)/2 // avoid overflow when computing mid
		 := .Scripts[].Tag
		if  <  {
			 = 
		} else if  >  {
			 =  + 1
		} else {
			return 
		}
	}
	return -1
}

// FindVariationIndex returns the first feature variation matching
// the specified variation coordinates, as an index in the
// `FeatureVariations` field.
// It returns `-1` if not found.
func ( *Layout) ( []VarCoord) int {
	for ,  := range .FeatureVariations {
		if evaluateVarRec(, ) {
			return 
		}
	}
	return -1
}

// returns `true` if the feature is concerned by the `coords`
func evaluateVarRec( tables.FeatureVariationRecord,  []VarCoord) bool {
	for ,  := range .ConditionSet.Conditions {
		if !evaluateCondition(, ) {
			return false
		}
	}
	return true
}

// returns `true` if `coords` match the condition `c`
func evaluateCondition( tables.ConditionFormat1,  []VarCoord) bool {
	var  VarCoord
	if int(.AxisIndex) < len() {
		 = [.AxisIndex]
	}
	return .FilterRangeMinValue <=  &&  <= .FilterRangeMaxValue
}

// FindFeatureIndex fetches the index for a given feature tag in the GSUB or GPOS table.
// Returns false if not found
func ( *Layout) ( Tag) (uint16, bool) {
	for ,  := range .Features { // i fits in uint16
		if  == .Tag {
			return uint16(), true
		}
	}
	return 0, false
}

// ---------------------------------- GSUB ----------------------------------

type GSUB struct {
	Layout
	Lookups []GSUBLookup
}

type LookupOptions struct {
	// Lookup qualifiers.
	Flag uint16
	// Index (base 0) into GDEF mark glyph sets structure,
	// meaningfull only if UseMarkFilteringSet is set.
	MarkFilteringSet uint16
}

const UseMarkFilteringSet = 1 << 4

// Props returns a 32-bit integer where the lower 16-bit is `Flag` and
// the higher 16-bit is `MarkFilteringSet` if the lookup uses one.
func ( LookupOptions) () uint32 {
	 := uint32(.Flag)
	if .Flag&UseMarkFilteringSet != 0 {
		 |= uint32(.MarkFilteringSet) << 16
	}
	return 
}

type GSUBLookup struct {
	LookupOptions
	Subtables []tables.GSUBLookup
}

func newGSUB( tables.Layout) (GSUB, error) {
	 := GSUB{
		Layout:  newLayout(),
		Lookups: make([]GSUBLookup, len(.LookupList.Lookups)),
	}
	for ,  := range .LookupList.Lookups {
		,  := .AsGSUBLookups()
		if  != nil {
			return GSUB{}, 
		}
		for ,  := range  {
			// start by resolving extension
			if ,  := .(tables.ExtensionSubs);  {
				[],  = .Resolve()
				if  != nil {
					return GSUB{}, 
				}
			}

			// sanitize each lookup
			switch subtable := .(type) {
			case tables.MultipleSubs:
				 = .Sanitize()
			case tables.LigatureSubs:
				 = .Sanitize()
			case tables.ContextualSubs:
				 = .Sanitize(uint16(len(.Lookups)))
			case tables.ReverseChainSingleSubs:
				 = .Sanitize()
			}
			if  != nil {
				return GSUB{}, 
			}
		}
		.Lookups[] = GSUBLookup{
			LookupOptions: LookupOptions{
				Flag:             .LookupFlag,
				MarkFilteringSet: .MarkFilteringSet,
			},
			Subtables: ,
		}
	}
	return , nil
}

type GPOS struct {
	Layout
	Lookups []GPOSLookup
}

type GPOSLookup struct {
	LookupOptions
	Subtables []tables.GPOSLookup
}

func newGPOS( tables.Layout) (GPOS, error) {
	 := GPOS{
		Layout:  newLayout(),
		Lookups: make([]GPOSLookup, len(.LookupList.Lookups)),
	}
	for ,  := range .LookupList.Lookups {
		,  := .AsGPOSLookups()
		if  != nil {
			return GPOS{}, 
		}
		for ,  := range  {
			// start by resolving extension
			if ,  := .(tables.ExtensionPos);  {
				[],  = .Resolve()
				if  != nil {
					return GPOS{}, 
				}
			}

			// sanitize each lookup
			switch subtable := .(type) {
			case tables.SinglePos:
				 = .Sanitize()
			case tables.PairPos:
				 = .Sanitize()
			case tables.MarkBasePos:
				 = .Sanitize()
			case tables.MarkLigPos:
				 = .Sanitize()
			case tables.ContextualPos:
				 = .Sanitize(uint16(len(.Lookups)))
			}
			if  != nil {
				return GPOS{}, 
			}
		}
		.Lookups[] = GPOSLookup{
			LookupOptions: LookupOptions{
				Flag:             .LookupFlag,
				MarkFilteringSet: .MarkFilteringSet,
			},
			Subtables: ,
		}
	}
	return , nil
}