/packet/binary_pattern.go
package packet

import (
    "fmt"
    "sort"
    "strings"

    "bytex64.net/code/bitsmash/ac"
)

var patterns [][]uint8 = [][]uint8{
    {0, 1, 1, 1, 0},
    {0, 1, 1, 1, 1},
    {0, 0, 1, 1, 1},
    {0, 0, 0, 1, 1},
    {0, 0, 0, 0, 1},
    {0, 1, 0, 1},
    {0, 1, 1, 1},
    {0, 1, 1, 0},
    {0, 0, 0, 1},
    {0, 0, 1, 0},
    {0, 1, 0, 0},
    {0, 0, 1, 1},
    {0, 0, 1},
    {0, 1, 1},
    {0, 1, 0},
    {0, 1},
}

type binaryPatternMatch struct {
    Length int
    Pattern uint8
    Repeat uint8
}

type byPatternLength []binaryPatternMatch

func (a byPatternLength) Len() int {
    return len(a)
}

func (a byPatternLength) Swap(i, j int) {
    a[i], a[j] = a[j], a[i]
}

func (a byPatternLength) Less(i, j int) bool {
    return a[j].Length < a[i].Length
}

type BinaryPatternPacket struct {
    c1, c2 uint8
    pattern uint8
    repeat uint8
}

func (self *BinaryPatternPacket) Length() int {
    return len(patterns[self.pattern]) * (int(self.repeat) + 1)
}

func (*BinaryPatternPacket) ByteLength() int {
    return 2
}

func (self *BinaryPatternPacket) Encode(encoder ac.CodecManager) {
    encoder.Encode(CONTEXT_PATTERN_NUMBER, uint32(self.pattern))
    encoder.Encode(CONTEXT_PATTERN_REPEAT, uint32(self.repeat))
    encoder.Encode(CONTEXT_COLOR, uint32(self.c1))
    encoder.Encode(CONTEXT_COLOR, uint32(self.c2))
}

func DecodeBinaryPattern(decoder ac.CodecManager) Packet {
    pattern := byte(decoder.Decode(CONTEXT_PATTERN_NUMBER))
    repeat := byte(decoder.Decode(CONTEXT_PATTERN_REPEAT))
    c1 := byte(decoder.Decode(CONTEXT_COLOR))
    c2 := byte(decoder.Decode(CONTEXT_COLOR))
    return &BinaryPatternPacket{c1, c2, pattern, repeat}
}

func (self *BinaryPatternPacket) Pixels() []uint8 {
    px := make([]uint8, self.Length())
    patternLength := len(patterns[self.pattern])
    c := []uint8{self.c1, self.c2}

    for i := 0; i < int(self.repeat) + 1; i++ {
        for j := 0; j < patternLength; j++ {
            px[i * patternLength + j] = c[patterns[self.pattern][j]]
        }
    }
    return px
}

func (self *BinaryPatternPacket) String() string {
    patternbit := make([]string, len(patterns[self.pattern]))
    for i, v := range(patterns[self.pattern]) {
        patternbit[i] = fmt.Sprintf("%d", v)
    }
    patternstr := strings.Join(patternbit, "")

    return fmt.Sprintf("BinaryPattern colors (%d, %d) pattern %s repeat %d", self.c1, self.c2, patternstr, self.repeat)
}

func findBinaryPattern(pixels []uint8) (uint8, uint8, []uint8) {
    pat := []uint8{0}
    a := pixels[0]
    i := 1

    for i < len(pixels) && pixels[i] == a {
        pat = append(pat, 0)
        i++
    }

    if i == len(pixels) {
        return 0, 0, nil
    }

    b := pixels[i]
    for i < len(pixels) && (pixels[i] == a || pixels[i] == b) {
        if pixels[i] == a {
            pat = append(pat, 0)
        } else if pixels[i] == b {
            pat = append(pat, 1)
        }
        i++
    }

    return a, b, pat
}

func patternPrefixMatch(needle []uint8, haystack []uint8) bool {
    if len(haystack) < len(needle) {
        return false
    }
    for i := 0; i < len(needle); i++ {
        if haystack[i] != needle[i] {
            return false
        }
    }
    return true
}

func ScanBinaryPattern(pixels []uint8) Packet {
    if len(pixels) < 2 {
        return nil
    }

    c1, c2, pat := findBinaryPattern(pixels)
    if pat == nil {
        return nil
    }

    potential_matches := make([]binaryPatternMatch, 0)
    for i, p := range(patterns) {
        l := len(p)
        repeat := 0
        // If this pattern matches the prefix of the found pattern
        if patternPrefixMatch(p, pat) {
            // Find a repeat of this match
            j := l
            for patternPrefixMatch(p, pat[j:]) && repeat < 7 {
                repeat++
                j += l
            }
            potential_matches = append(potential_matches, binaryPatternMatch{l * (repeat + 1), uint8(i), uint8(repeat)})
        }
    }
    if len(potential_matches) == 0 {
        return nil
    }

    // Select longest match
    sort.Sort(byPatternLength(potential_matches))
    best_match := potential_matches[0]
    if best_match.Length < 5 {
        return nil
    }

    return &BinaryPatternPacket{c1, c2, best_match.Pattern, best_match.Repeat}
}