// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sfnt

import (
	"golang.org/x/text/encoding/charmap"
)

// Platform IDs and Platform Specific IDs as per
// https://www.microsoft.com/typography/otspec/name.htm
const (
	pidUnicode   = 0
	pidMacintosh = 1
	pidWindows   = 3

	psidUnicode2BMPOnly        = 3
	psidUnicode2FullRepertoire = 4
	// Note that FontForge may generate a bogus Platform Specific ID (value 10)
	// for the Unicode Platform ID (value 0). See
	// https://github.com/fontforge/fontforge/issues/2728

	psidMacintoshRoman = 0

	psidWindowsSymbol = 0
	psidWindowsUCS2   = 1
	psidWindowsUCS4   = 10
)

// platformEncodingWidth returns the number of bytes per character assumed by
// the given Platform ID and Platform Specific ID.
//
// Very old fonts, from before Unicode was widely adopted, assume only 1 byte
// per character: a character map.
//
// Old fonts, from when Unicode meant the Basic Multilingual Plane (BMP),
// assume that 2 bytes per character is sufficient.
//
// Recent fonts naturally support the full range of Unicode code points, which
// can take up to 4 bytes per character. Such fonts might still choose one of
// the legacy encodings if e.g. their repertoire is limited to the BMP, for
// greater compatibility with older software, or because the resultant file
// size can be smaller.
func platformEncodingWidth(pid, psid uint16) int {
	switch pid {
	case pidUnicode:
		switch psid {
		case psidUnicode2BMPOnly:
			return 2
		case psidUnicode2FullRepertoire:
			return 4
		}

	case pidMacintosh:
		switch psid {
		case psidMacintoshRoman:
			return 1
		}

	case pidWindows:
		switch psid {
		case psidWindowsSymbol:
			return 2
		case psidWindowsUCS2:
			return 2
		case psidWindowsUCS4:
			return 4
		}
	}
	return 0
}

// The various cmap formats are described at
// https://www.microsoft.com/typography/otspec/cmap.htm

var supportedCmapFormat = func(format, pid, psid uint16) bool {
	switch format {
	case 0:
		return pid == pidMacintosh && psid == psidMacintoshRoman
	case 4:
		return true
	case 6:
		return true
	case 12:
		return true
	}
	return false
}

func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, glyphIndexFunc, error) {
	switch format {
	case 0:
		return f.makeCachedGlyphIndexFormat0(buf, offset, length)
	case 4:
		return f.makeCachedGlyphIndexFormat4(buf, offset, length)
	case 6:
		return f.makeCachedGlyphIndexFormat6(buf, offset, length)
	case 12:
		return f.makeCachedGlyphIndexFormat12(buf, offset, length)
	}
	panic("unreachable")
}

func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
	if length != 6+256 || offset+length > f.cmap.length {
		return nil, nil, errInvalidCmapTable
	}
	var err error
	buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length))
	if err != nil {
		return nil, nil, err
	}
	var table [256]byte
	copy(table[:], buf[6:])
	return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
		x, ok := charmap.Macintosh.EncodeRune(r)
		if !ok {
			// The source rune r is not representable in the Macintosh-Roman encoding.
			return 0, nil
		}
		return GlyphIndex(table[x]), nil
	}, nil
}

func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
	const headerSize = 14
	if offset+headerSize > f.cmap.length {
		return nil, nil, errInvalidCmapTable
	}
	var err error
	buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
	if err != nil {
		return nil, nil, err
	}
	offset += headerSize

	segCount := u16(buf[6:])
	if segCount&1 != 0 {
		return nil, nil, errInvalidCmapTable
	}
	segCount /= 2
	if segCount > maxCmapSegments {
		return nil, nil, errUnsupportedNumberOfCmapSegments
	}

	eLength := 8*uint32(segCount) + 2
	if offset+eLength > f.cmap.length {
		return nil, nil, errInvalidCmapTable
	}
	buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
	if err != nil {
		return nil, nil, err
	}
	offset += eLength

	entries := make([]cmapEntry16, segCount)
	for i := range entries {
		entries[i] = cmapEntry16{
			end:    u16(buf[0*len(entries)+0+2*i:]),
			start:  u16(buf[2*len(entries)+2+2*i:]),
			delta:  u16(buf[4*len(entries)+2+2*i:]),
			offset: u16(buf[6*len(entries)+2+2*i:]),
		}
	}
	indexesBase := f.cmap.offset + offset
	indexesLength := f.cmap.length - offset

	return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
		if uint32(r) > 0xffff {
			return 0, nil
		}

		c := uint16(r)
		for i, j := 0, len(entries); i < j; {
			h := i + (j-i)/2
			entry := &entries[h]
			if c < entry.start {
				j = h
			} else if entry.end < c {
				i = h + 1
			} else if entry.offset == 0 {
				return GlyphIndex(c + entry.delta), nil
			} else {
				offset := uint32(entry.offset) + 2*uint32(h-len(entries)+int(c-entry.start))
				if offset > indexesLength || offset+2 > indexesLength {
					return 0, errInvalidCmapTable
				}
				x, err := b.view(&f.src, int(indexesBase+offset), 2)
				if err != nil {
					return 0, err
				}
				return GlyphIndex(u16(x)), nil
			}
		}
		return 0, nil
	}, nil
}

func (f *Font) makeCachedGlyphIndexFormat6(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
	const headerSize = 10
	if offset+headerSize > f.cmap.length {
		return nil, nil, errInvalidCmapTable
	}
	var err error
	buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
	if err != nil {
		return nil, nil, err
	}
	offset += headerSize

	firstCode := u16(buf[6:])
	entryCount := u16(buf[8:])

	eLength := 2 * uint32(entryCount)
	if offset+eLength > f.cmap.length {
		return nil, nil, errInvalidCmapTable
	}

	if entryCount != 0 {
		buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
		if err != nil {
			return nil, nil, err
		}
		offset += eLength
	}

	entries := make([]uint16, entryCount)
	for i := range entries {
		entries[i] = u16(buf[2*i:])
	}

	return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
		if uint16(r) < firstCode {
			return 0, nil
		}

		c := int(uint16(r) - firstCode)
		if c >= len(entries) {
			return 0, nil
		}
		return GlyphIndex(entries[c]), nil
	}, nil
}

func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, glyphIndexFunc, error) {
	const headerSize = 16
	if offset+headerSize > f.cmap.length {
		return nil, nil, errInvalidCmapTable
	}
	var err error
	buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
	if err != nil {
		return nil, nil, err
	}
	length := u32(buf[4:])
	if f.cmap.length < offset || length > f.cmap.length-offset {
		return nil, nil, errInvalidCmapTable
	}
	offset += headerSize

	numGroups := u32(buf[12:])
	if numGroups > maxCmapSegments {
		return nil, nil, errUnsupportedNumberOfCmapSegments
	}

	eLength := 12 * numGroups
	if headerSize+eLength != length {
		return nil, nil, errInvalidCmapTable
	}
	buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
	if err != nil {
		return nil, nil, err
	}
	offset += eLength

	entries := make([]cmapEntry32, numGroups)
	for i := range entries {
		entries[i] = cmapEntry32{
			start: u32(buf[0+12*i:]),
			end:   u32(buf[4+12*i:]),
			delta: u32(buf[8+12*i:]),
		}
	}

	return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
		c := uint32(r)
		for i, j := 0, len(entries); i < j; {
			h := i + (j-i)/2
			entry := &entries[h]
			if c < entry.start {
				j = h
			} else if entry.end < c {
				i = h + 1
			} else {
				return GlyphIndex(c - entry.start + entry.delta), nil
			}
		}
		return 0, nil
	}, nil
}

type cmapEntry16 struct {
	end, start, delta, offset uint16
}

type cmapEntry32 struct {
	start, end, delta uint32
}
