package sointu

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
)

// Read4klangPatch reads a 4klang patch (a file usually with .4kp extension)
// from r and returns a Patch, making best attempt to convert 4klang file to a
// sointu Patch. It returns an error if the file is malformed or if the 4kp file
// version is not supported.
func Read4klangPatch(r io.Reader) (patch Patch, err error) {
	var versionTag uint32
	var version int
	var polyphonyUint32 uint32
	var instrumentNames [_4KLANG_MAX_INSTRS]string
	patch = make(Patch, 0)
	if err := binary.Read(r, binary.LittleEndian, &versionTag); err != nil {
		return nil, fmt.Errorf("binary.Read: %w", err)
	}
	var ok bool
	if version, ok = _4klangVersionTags[versionTag]; !ok {
		return nil, fmt.Errorf("unknown 4klang version tag: %d", versionTag)
	}
	if err := binary.Read(r, binary.LittleEndian, &polyphonyUint32); err != nil {
		return nil, fmt.Errorf("binary.Read: %w", err)
	}
	for i := range instrumentNames {
		instrumentNames[i], err = read4klangName(r)
		if err != nil {
			return nil, fmt.Errorf("read4klangName: %w", err)
		}
	}
	m := make(_4klangTargetMap)
	id := 1
	for instrIndex := 0; instrIndex < _4KLANG_MAX_INSTRS; instrIndex++ {
		var units []Unit
		if units, err = read4klangUnits(r, version, instrIndex, m, &id); err != nil {
			return nil, fmt.Errorf("read4klangUnits: %w", err)
		}
		if len(units) > 0 {
			patch = append(patch, Instrument{Name: instrumentNames[instrIndex], NumVoices: 1, Units: units})
		}
	}
	var units []Unit
	if units, err = read4klangUnits(r, version, _4KLANG_MAX_INSTRS, m, &id); err != nil {
		return nil, fmt.Errorf("read4klangUnits: %w", err)
	}
	if len(units) > 0 {
		patch = append(patch, Instrument{Name: "Global", NumVoices: 1, Units: units})
	}
	for i, instr := range patch {
		fix4klangTargets(i, instr, m)
	}
	return
}

// Read4klangInstrument reads a 4klang instrument (a file usually with .4ki
// extension) from r and returns an Instrument, making best attempt to convert
// 4ki file to a sointu Instrument. It returns an error if the file is malformed
// or if the 4ki file version is not supported.
func Read4klangInstrument(r io.Reader) (instr Instrument, err error) {
	var versionTag uint32
	var version int
	var name string
	if err := binary.Read(r, binary.LittleEndian, &versionTag); err != nil {
		return Instrument{}, fmt.Errorf("binary.Read: %w", err)
	}
	var ok bool
	if version, ok = _4klangVersionTags[versionTag]; !ok {
		return Instrument{}, fmt.Errorf("unknown 4klang version tag: %d", versionTag)
	}
	if name, err = read4klangName(r); err != nil {
		return Instrument{}, fmt.Errorf("read4klangName: %w", err)
	}
	var units []Unit
	id := 1
	m := make(_4klangTargetMap)
	if units, err = read4klangUnits(r, version, 0, m, &id); err != nil {
		return Instrument{}, fmt.Errorf("read4klangUnits: %w", err)
	}
	ret := Instrument{Name: name, NumVoices: 1, Units: units}
	fix4klangTargets(0, ret, m)
	return ret, nil
}

type (
	_4klangStackUnit struct {
		stack, unit int
	}

	_4klangTargetMap map[_4klangStackUnit]int

	_4klangPorts struct {
		UnitType string
		PortName [8]string
	}
)

const (
	_4KLANG_MAX_INSTRS   = 16
	_4KLANG_MAX_UNITS    = 64
	_4KLANG_MAX_SLOTS    = 16
	_4KLANG_MAX_NAME_LEN = 64
)

var (
	_4klangVersionTags map[uint32]int = map[uint32]int{
		0x31316b34: 11, // 4k11
		0x32316b34: 12, // 4k12
		0x33316b34: 13, // 4k13
		0x34316b34: 14, // 4k14
	}

	_4klangDelays []int = []int{ // these are the numerators, if denominator is 48, fraction of beat time
		4,   // 0 = 4.0f * (1.0f/32.0f) * (2.0f/3.0f)
		6,   // 1 = 4.0f * (1.0f/32.0f),
		9,   // 2 = 4.0f * (1.0f/32.0f) * (3.0f/2.0f),
		8,   // 3 = 4.0f * (1.0f/16.0f) * (2.0f/3.0f),
		12,  // 4 = 4.0f * (1.0f/16.0f),
		18,  // 5 = 4.0f * (1.0f/16.0f) * (3.0f/2.0f),
		16,  // 6 = 4.0f * (1.0f/8.0f) * (2.0f/3.0f),
		24,  // 7 = 4.0f * (1.0f/8.0f),
		36,  // 8 = 4.0f * (1.0f/8.0f) * (3.0f/2.0f),
		32,  // 9 = 4.0f * (1.0f/4.0f) * (2.0f/3.0f),
		48,  // 10 = 4.0f * (1.0f/4.0f),
		72,  // 11 = 4.0f * (1.0f/4.0f) * (3.0f/2.0f),
		64,  // 12 = 4.0f * (1.0f/2.0f) * (2.0f/3.0f),
		96,  // 13 = 4.0f * (1.0f/2.0f),
		144, // 14 = 4.0f * (1.0f/2.0f) * (3.0f/2.0f),
		128, // 15 = 4.0f * (1.0f) * (2.0f/3.0f),
		192, // 16 = 4.0f * (1.0f),
		288, // 17 = 4.0f * (1.0f) * (3.0f/2.0f),
		256, // 18 = 4.0f * (2.0f) * (2.0f/3.0f),
		384, // 19 = 4.0f * (2.0f),
		576, // 20 = 4.0f * (2.0f) * (3.0f/2.0f),
		72,  // 21 = 4.0f * (3.0f/8.0f),
		120, // 22 = 4.0f * (5.0f/8.0f),
		168, // 23 = 4.0f * (7.0f/8.0f),
		216, // 24 = 4.0f * (9.0f/8.0f),
		264, // 25 = 4.0f * (11.0f/8.0f),
		312, // 26 = 4.0f * (13.0f/8.0f),
		360, // 27 = 4.0f * (15.0f/8.0f),
		144, // 28 = 4.0f * (3.0f/4.0f),
		240, // 29 = 4.0f * (5.0f/4.0f),
		336, // 30 = 4.0f * (7.0f/4.0f),
		288, // 31 = 4.0f * (3.0f/2.0f),
		288, // 32 = 4.0f * (3.0f/2.0f),
	}

	_4klangUnitPorts []_4klangPorts = []_4klangPorts{
		{"", [8]string{"", "", "", "", "", "", "", ""}},
		{"envelope", [8]string{"", "", "gain", "attack", "decay", "", "release", ""}},
		{"oscillator", [8]string{"", "transpose", "detune", "", "phase", "color", "shape", "gain"}},
		{"filter", [8]string{"", "", "", "", "frequency", "resonance", "", ""}},
		{"envelope", [8]string{"", "", "drive", "frequency", "", "", "", ""}},
		{"delay", [8]string{"pregain", "feedback", "dry", "damp", "", "", "", ""}},
		{"", [8]string{"", "", "", "", "", "", "", ""}},
		{"", [8]string{"", "", "", "", "", "", "", ""}},
		{"pan", [8]string{"panning", "", "", "", "", "", "", ""}},
		{"outaux", [8]string{"auxgain", "outgain", "", "", "", "", "", ""}},
		{"", [8]string{"", "", "", "", "", "", "", ""}},
		{"load", [8]string{"value", "", "", "", "", "", "", ""}},
	}
)

func read4klangName(r io.Reader) (string, error) {
	var name [_4KLANG_MAX_NAME_LEN]byte
	if err := binary.Read(r, binary.LittleEndian, &name); err != nil {
		return "", fmt.Errorf("binary.Read: %w", err)
	}
	n := bytes.IndexByte(name[:], 0)
	if n == -1 {
		n = _4KLANG_MAX_NAME_LEN
	}
	return string(name[:n]), nil
}

func read4klangUnits(r io.Reader, version, instrIndex int, m _4klangTargetMap, id *int) (units []Unit, err error) {
	numUnits := _4KLANG_MAX_UNITS
	if version <= 13 {
		numUnits = 32
	}
	units = make([]Unit, 0, numUnits)
	for unitIndex := 0; unitIndex < numUnits; unitIndex++ {
		var u []Unit
		if u, err = read4klangUnit(r, version); err != nil {
			return nil, fmt.Errorf("read4klangUnit: %w", err)
		}
		if u == nil {
			continue
		}
		m[_4klangStackUnit{instrIndex, unitIndex}] = *id
		for i := range u {
			u[i].ID = *id
			*id++
		}
		units = append(units, u...)
	}
	return
}

func read4klangUnit(r io.Reader, version int) ([]Unit, error) {
	var unitType byte
	if err := binary.Read(r, binary.LittleEndian, &unitType); err != nil {
		return nil, fmt.Errorf("binary.Read: %w", err)
	}
	var vals [15]byte
	if err := binary.Read(r, binary.LittleEndian, &vals); err != nil {
		return nil, fmt.Errorf("binary.Read: %w", err)
	}
	if version <= 13 {
		// versions <= 13 had 16 unused slots for each unit
		if written, err := io.CopyN(io.Discard, r, 16); err != nil || written < 16 {
			return nil, fmt.Errorf("io.CopyN: %w", err)
		}
	}
	switch unitType {
	case 1:
		return read4klangENV(vals, version), nil
	case 2:
		return read4klangVCO(vals, version), nil
	case 3:
		return read4klangVCF(vals, version), nil
	case 4:
		return read4klangDST(vals, version), nil
	case 5:
		return read4klangDLL(vals, version), nil
	case 6:
		return read4klangFOP(vals, version), nil
	case 7:
		return read4klangFST(vals, version), nil
	case 8:
		return read4klangPAN(vals, version), nil
	case 9:
		return read4klangOUT(vals, version), nil
	case 10:
		return read4klangACC(vals, version), nil
	case 11:
		return read4klangFLD(vals, version), nil
	default:
		return nil, nil
	}
}

func read4klangENV(vals [15]byte, version int) []Unit {
	return []Unit{{
		Type: "envelope",
		Parameters: map[string]int{
			"stereo":  0,
			"attack":  int(vals[0]),
			"decay":   int(vals[1]),
			"sustain": int(vals[2]),
			"release": int(vals[3]),
			"gain":    int(vals[4]),
		},
	}}
}

func read4klangVCO(vals [15]byte, version int) []Unit {
	v := vals[:8]
	var transpose, detune, phase, color, gate, shape, gain, flags, stereo, typ, lfo int
	transpose, v = int(v[0]), v[1:]
	detune, v = int(v[0]), v[1:]
	phase, v = int(v[0]), v[1:]
	if version <= 11 {
		gate = 0x55
	} else {
		gate, v = int(v[0]), v[1:]
	}
	color, v = int(v[0]), v[1:]
	shape, v = int(v[0]), v[1:]
	gain, v = int(v[0]), v[1:]
	flags, v = int(v[0]), v[1:]
	if flags&0x10 == 0x10 {
		lfo = 1
	}
	if flags&0x40 == 0x40 {
		stereo = 1
	}
	switch {
	case flags&0x01 == 0x01: // Sine
		typ = Sine
		if version <= 13 {
			color = 128
		}
	case flags&0x02 == 0x02: // Trisaw
		typ = Trisaw
	case flags&0x04 == 0x04: // Pulse
		typ = Pulse
	case flags&0x08 == 0x08: // Noise is handled differently in sointu
		return []Unit{{
			Type: "noise",
			Parameters: map[string]int{
				"stereo": stereo,
				"shape":  shape,
				"gain":   gain,
			},
		}}
	case flags&0x20 == 0x20: // Gate
		color = gate
	}
	return []Unit{{
		Type: "oscillator",
		Parameters: map[string]int{
			"stereo":    stereo,
			"transpose": transpose,
			"detune":    detune,
			"phase":     phase,
			"color":     color,
			"shape":     shape,
			"gain":      gain,
			"type":      typ,
			"lfo":       lfo,
		},
	}}
}

func read4klangVCF(vals [15]byte, version int) []Unit {
	flags := vals[2]
	var stereo, lowpass, bandpass, highpass, neghighpass int
	if flags&0x01 == 0x01 {
		lowpass = 1
	}
	if flags&0x02 == 0x02 {
		highpass = 1
	}
	if flags&0x04 == 0x04 {
		bandpass = 1
	}
	if flags&0x08 == 0x08 {
		lowpass = 1
		neghighpass = 1
	}
	if flags&0x10 == 0x10 {
		stereo = 1
	}
	return []Unit{{
		Type: "filter",
		Parameters: map[string]int{
			"stereo":      stereo,
			"frequency":   int(vals[0]),
			"resonance":   int(vals[1]),
			"lowpass":     lowpass,
			"bandpass":    bandpass,
			"highpass":    highpass,
			"negbandpass": 0,
			"neghighpass": neghighpass,
		}},
	}
}

func read4klangDST(vals [15]byte, version int) []Unit {
	return []Unit{
		{Type: "distort", Parameters: map[string]int{"drive": int(vals[0]), "stereo": int(vals[2])}},
		{Type: "hold", Parameters: map[string]int{"holdfreq": int(vals[1]), "stereo": int(vals[2])}},
	}
}

func read4klangDLL(vals [15]byte, version int) []Unit {
	var delaytimes []int
	var notetracking int
	if vals[11] > 0 {
		if vals[10] > 0 { // left reverb
			delaytimes = []int{1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618}
		} else { // right reverb
			delaytimes = []int{1140, 1212, 1300, 1380, 1446, 1516, 1580, 1642}
		}
	} else {
		synctype := vals[9]
		switch synctype {
		case 0:
			delaytimes = []int{int(vals[8]) * 16}
		case 1: // relative to BPM
			notetracking = 2
			index := vals[8] >> 2
			delaytime := 48
			if int(index) < len(_4klangDelays) {
				delaytime = _4klangDelays[index]
			}
			delaytimes = []int{delaytime}
		case 2: // notetracking
			notetracking = 1
			delaytimes = []int{10787}
		}
	}
	return []Unit{{
		Type: "delay",
		Parameters: map[string]int{
			"stereo":       0,
			"pregain":      int(vals[0]),
			"dry":          int(vals[1]),
			"feedback":     int(vals[2]),
			"damp":         int(vals[3]),
			"notetracking": notetracking,
		},
		VarArgs: delaytimes,
	}}
}

func read4klangFOP(vals [15]byte, version int) []Unit {
	var t string
	var stereo int
	switch vals[0] {
	case 1:
		t, stereo = "pop", 0
	case 2:
		t, stereo = "addp", 0
	case 3:
		t, stereo = "mulp", 0
	case 4:
		t, stereo = "push", 0
	case 5:
		t, stereo = "xch", 0
	case 6:
		t, stereo = "add", 0
	case 7:
		t, stereo = "mul", 0
	case 8:
		t, stereo = "addp", 1
	case 9:
		return []Unit{{Type: "loadnote", Parameters: map[string]int{"stereo": stereo}}, // 4klang loadnote gives 0..1, sointu gives -1..1
			{Type: "loadval", Parameters: map[string]int{"value": 128, "stereo": stereo}},
			{Type: "addp", Parameters: map[string]int{"stereo": stereo}},
			{Type: "gain", Parameters: map[string]int{"stereo": stereo, "gain": 64}}}
	default:
		t, stereo = "mulp", 1
	}
	return []Unit{{
		Type:       t,
		Parameters: map[string]int{"stereo": stereo},
	}}
}

func read4klangFST(vals [15]byte, version int) []Unit {
	sendpop := 0
	if vals[1]&0x40 == 0x40 {
		sendpop = 1
	}
	return []Unit{{
		Type: "send",
		Parameters: map[string]int{
			"amount":     int(vals[0]),
			"sendpop":    sendpop,
			"dest_stack": int(vals[2]),
			"dest_unit":  int(vals[3]),
			"dest_slot":  int(vals[4]),
			"dest_id":    int(vals[5]),
		}}}
}

func fix4klangTargets(instrIndex int, instr Instrument, m _4klangTargetMap) {
	for _, u := range instr.Units {
		if u.Type == "send" {
			destStack := u.Parameters["dest_stack"]
			if destStack == 255 {
				destStack = instrIndex
			}
			fourKlangTarget := _4klangStackUnit{
				destStack,
				u.Parameters["dest_unit"]}
			u.Parameters["target"] = m[fourKlangTarget]
			if u.Parameters["dest_id"] < len(_4klangUnitPorts) && u.Parameters["dest_slot"] < 8 {
				if u.Parameters["dest_id"] == 4 && u.Parameters["dest_slot"] == 3 { // distortion is split into 2 units
					u.Parameters["target"]++
					u.Parameters["port"] = 0
				} else {
					modTarget := _4klangUnitPorts[u.Parameters["dest_id"]]
					for i, s := range Ports[modTarget.UnitType] {
						if s == modTarget.PortName[u.Parameters["dest_slot"]] {
							u.Parameters["port"] = i
							break
						}
					}
				}
			}
			delete(u.Parameters, "dest_stack")
			delete(u.Parameters, "dest_unit")
			delete(u.Parameters, "dest_slot")
			delete(u.Parameters, "dest_id")
		}
	}
}

func read4klangPAN(vals [15]byte, version int) []Unit {
	return []Unit{{
		Type: "pan",
		Parameters: map[string]int{
			"stereo":  0,
			"panning": int(vals[0]),
		}}}
}

func read4klangOUT(vals [15]byte, version int) []Unit {
	return []Unit{{
		Type: "outaux",
		Parameters: map[string]int{
			"stereo":  1,
			"outgain": int(vals[0]),
			"auxgain": int(vals[1])},
	}}
}

func read4klangACC(vals [15]byte, version int) []Unit {
	c := 0
	if vals[0] != 0 {
		c = 2
	}
	return []Unit{{
		Type:       "in",
		Parameters: map[string]int{"stereo": 1, "channel": c},
	}}
}

func read4klangFLD(vals [15]byte, version int) []Unit {
	return []Unit{{
		Type:       "loadval",
		Parameters: map[string]int{"stereo": 0, "value": int(vals[0])},
	}}
}