package matchfinder

import 

const (
	zmTableBits     = 15
	zmTableSize     = 1 << zmTableBits
	zmLongTableBits = 17
	zmLongTableSize = 1 << zmLongTableBits
)

// ZM is a MatchFinder that combines the cache tables of ZDFast with the
// overlap-based parsing of M4.
type ZM struct {
	MaxDistance int
	history     []byte
	table       [zmTableSize]tableEntry
	longTable   [zmLongTableSize]tableEntry
}

func ( *ZM) () {
	.table = [zmTableSize]tableEntry{}
	.longTable = [zmLongTableSize]tableEntry{}
	.history = .history[:0]
}

func ( *ZM) ( []Match,  []byte) []Match {
	if .MaxDistance == 0 {
		.MaxDistance = 1 << 16
	}

	if len(.history) > .MaxDistance*2 {
		 := len(.history) - .MaxDistance
		copy(.history, .history[:])
		.history = .history[:.MaxDistance]

		for  := range .table {
			 := .table[].offset
			 -= int32()
			if  < 0 {
				.table[] = tableEntry{}
			} else {
				.table[].offset = 
			}
		}
		for  := range .longTable {
			 := .longTable[].offset
			 -= int32()
			if  < 0 {
				.longTable[] = tableEntry{}
			} else {
				.longTable[].offset = 
			}
		}
	}

	if len() < 16 {
		return append(, Match{
			Unmatched: len(),
		})
	}

	 := matchEmitter{
		Dst:      ,
		NextEmit: len(.history),
	}
	.history = append(.history, ...)
	 = .history

	// matches stores the matches that have been found but not emitted,
	// in reverse order. (matches[0] is the most recent one.)
	var  [3]absoluteMatch

	 := int32(len()) - 10

:
	for {
		// Search for a match, starting after the last match emitted.
		 := int32(.NextEmit)
		if  >  {
			break 
		}

		// t will contain the match offset when we find one.
		var  int32

		 := binary.LittleEndian.Uint64([:])

		for {
			 := .hashLong()
			 := .hashShort()
			 := .longTable[]
			 := .table[]

			 := tableEntry{offset: , val: uint32()}
			.longTable[] = 
			.table[] = 

			// Look for a repeat match one byte after the current position.
			if len(.Dst) > 0 {
				 := int32(.Dst[len(.Dst)-1].Distance)
				if  != 0 {
					 :=  -  + 1
					if  >= 0 && binary.LittleEndian.Uint32([:]) == uint32(>>8) {
						// There is a repeated match at s+2.
						++
						 = 
						break
					}
				}
			}

			if .offset <  && -.offset < int32(.MaxDistance) && uint32() == .val &&
				binary.LittleEndian.Uint32([.offset:]) == uint32() {
				// There is a long match at s.
				 = .offset
				break
			}
			if .offset <  && -.offset < int32(.MaxDistance) && uint32() == .val &&
				binary.LittleEndian.Uint32([.offset:]) == uint32() {
				// There is a regular match at s.
				// See if we can find a long match at s+1.
				 := binary.LittleEndian.Uint64([+1:])
				 = .hashLong()
				 = .longTable[]
				 :=  - .offset + 1
				.longTable[] = tableEntry{offset:  + 1, val: uint32()}
				if .offset < +1 &&  < int32(.MaxDistance) && uint32() == .val &&
					binary.LittleEndian.Uint32([.offset:]) == uint32() {
					// We found a long match at s+1, so we'll use that instead
					// of the regular match at s.
					 = .offset
					++
					break
				}

				 = .offset
				break
			}

			 += 1 + (( - int32(.NextEmit)) >> 7)
			if  >  {
				break 
			}
			 = binary.LittleEndian.Uint64([:])
		}

		 := extendMatch2(, int(), int(), .NextEmit)
		[0] = 

		// Store some table entries after s.
		 :=  + 1
		 := binary.LittleEndian.Uint64([:])
		 := tableEntry{offset: , val: uint32()}
		.longTable[.hashLong()] = 
		 >>= 8
		.offset++
		.val = uint32()
		.table[.hashShort()] = 

		// We have a match in matches[0].
		// Now look for overlapping matches.

		for {
			if [0].End > int() {
				break
			}
			 = int32(max([0].Start+2, [0].End-6))
			 = binary.LittleEndian.Uint64([:])

			 := .hashLong()
			 := .hashShort()
			 := .longTable[]
			 := .table[]

			 := tableEntry{offset: , val: uint32()}
			.longTable[] = 
			.table[] = 

			 = -1
			if .offset <  && -.offset < int32(.MaxDistance) && uint32() == .val &&
				binary.LittleEndian.Uint32([.offset:]) == uint32() {
				// There is a long match at s.
				 = .offset
			} else if .offset <  && -.offset < int32(.MaxDistance) && uint32() == .val &&
				binary.LittleEndian.Uint32([.offset:]) == uint32() {
				// There is a regular match at s.
				 = .offset
				// See if we can find a long match at s+1.
				 := binary.LittleEndian.Uint64([+1:])
				 = .hashLong()
				 = .longTable[]
				 :=  - .offset + 1
				.longTable[] = tableEntry{offset:  + 1, val: uint32()}
				if .offset < +1 &&  < int32(.MaxDistance) && uint32() == .val &&
					binary.LittleEndian.Uint32([.offset:]) == uint32() {
					// We found a long match at s+1, so we'll use that instead
					// of the regular match at s.
					 = .offset
					++
				}
			}

			if  == -1 {
				// No overlapping match was found.
				break
			}

			 := extendMatch2(, int(), int(), .NextEmit)

			if .End-.Start <= [0].End-[0].Start {
				// The new match isn't longer than the old one, so we break out of the loop
				// of looking for overlapping matches.
				break
			}

			 = [3]absoluteMatch{
				,
				[0],
				[1],
			}

			if [2] == (absoluteMatch{}) {
				continue
			}

			// We have three matches, so it's time to emit one and/or eliminate one.
			switch {
			case [0].Start < [2].End:
				// The first and third matches overlap; discard the one in between.
				 = [3]absoluteMatch{
					[0],
					[2],
					{},
				}

			case [0].Start < [2].End+4:
				// The first and third matches don't overlap, but there's no room for
				// another match between them. Emit the first match and discard the second.
				.emit([2])
				 = [3]absoluteMatch{
					[0],
					{},
					{},
				}

			default:
				// Emit the first match, shortening it if necessary to avoid overlap with the second.
				if [2].End > [1].Start {
					[2].End = [1].Start
				}
				if [2].End-[2].Start >= 4 {
					.emit([2])
				}
				[2] = absoluteMatch{}
			}
		}

		// Store some table entries at the end of the last match.
		 := int32([0].End - 2)
		if  <  {
			 := binary.LittleEndian.Uint64([:])
			 := tableEntry{offset: , val: uint32()}
			.longTable[.hashLong()] = 
			 >>= 8
			.offset++
			.val = uint32()
			.table[.hashShort()] = 
		}

		// We're done looking for overlapping matches; emit the ones we have.

		if [1] != (absoluteMatch{}) {
			if [1].End > [0].Start {
				[1].End = [0].Start
			}
			if [1].End-[1].Start >= 4 {
				.emit([1])
			}
		}
		.emit([0])
		 = [3]absoluteMatch{}
	}

	 = .Dst
	if .NextEmit < len() {
		 = append(, Match{
			Unmatched: len() - .NextEmit,
		})
	}

	return 
}

func ( *ZM) ( uint64) uint32 {
	return uint32((( << 24) * 889523592379) >> (64 - zmTableBits))
}

func ( *ZM) ( uint64) uint32 {
	return uint32(( * 0xcf1bbcdcb7a56463) >> (64 - zmLongTableBits))
}