package beep

import (
	"archive/zip"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"strings"
)

// Violin voice
type Violin struct {
	naturalVoice      bool
	naturalVoiceFound bool
	keyDefMap         map[rune][]int16 // default voice
	keyNatMap         map[rune][]int16 // natural voice
	keyFreqMap        map[rune]float64
	keyNoteMap        map[rune]string
	noteKeyMap        map[string]rune
}

// NewViolin return new violin voice
func NewViolin() *Violin {
	v := &Violin{
		keyDefMap:  make(map[rune][]int16),
		keyNatMap:  make(map[rune][]int16),
		keyFreqMap: make(map[rune]float64),
		keyNoteMap: make(map[rune]string),
		noteKeyMap: make(map[string]rune),
	}

	keys := "q2w3er5t6y7ui9o0p[=]azsxcfvgbnjmk,l."

	octaveFreq3 := []float64{
		// C3, Db3, D3, Eb3, E3
		196.0, 207.6, 220.0, 233.0, 246.9, // 3
	}
	octaveFreq456 := []float64{
		// C4, Db4, D4, Eb4, E4, F4, Gb4, G4, Ab4, A4, Bb4, B4
		261.6, 277.1, 293.6, 311.1, 329.6, 349.2, 369.9, 392.0, 415.3, 440.0, 466.1, 493.8, // 4
		523.2, 554.3, 587.3, 622.2, 659.2, 698.4, 739.9, 783.9, 830.6, 880.0, 932.3, 987.7, // 5
		1046.5, 1108.7, 1174.6, 1244.5, 1318.5, 1396.9, 1479.9, 1567, 1661, 1760, 1864, 1975, // 6
	}
	octaveFreq7 := []float64{
		// C7, Db7, D7, Eb7, E7
		2093, 2217.5, 2349.3, 2489, 2637, 2793, 2960,
	}

	noteNames := []string{
		"G3", "Ab3", "A3", "Bb3", "B3",
		"C4", "Db4", "D4", "Eb4", "E4", "F4", "Gb4", "G4", "Ab4", "A4", "Bb4", "B4",
		"C5", "Db5", "D5", "Eb5", "E5", "F5", "Gb5", "G5", "Ab5", "A5", "Bb5", "B5",
		"C6", "Db6", "D6", "Eb6", "E6", "F6", "Gb6", "G6", "Ab6", "A6", "Bb6", "B6",
		"C7", "Db7", "D7", "Eb7", "E7",
	}

	// initialize maps
	ni := 0
	for i, key := range keys[31:] { // actave 3
		keyID := 2000 + key
		note := noteNames[ni]
		v.keyFreqMap[keyID] = octaveFreq3[i]
		v.keyNoteMap[keyID] = note
		v.noteKeyMap[note] = keyID
		ni++
	}
	for i, key := range keys { // actave 4, 5, 6
		keyID := 3000 + key
		note := noteNames[ni]
		v.keyFreqMap[keyID] = octaveFreq456[i]
		v.keyNoteMap[keyID] = note
		v.noteKeyMap[note] = keyID
		ni++
	}
	for i, key := range keys[:5] { // actave 7
		keyID := 4000 + key
		note := noteNames[ni]
		v.keyFreqMap[keyID] = octaveFreq7[i]
		v.keyNoteMap[keyID] = note
		v.noteKeyMap[note] = keyID
		ni++
	}
	for key := range v.keyFreqMap {
		// generate default violin voice
		v.keyDefMap[key] = v.generateNote(key, wholeNote)
	}

	// load natural voice file, if exists
	filename := filepath.Join(HomeDir(), "voices", "violin.zip")
	voiceFile, err := zip.OpenReader(filename)
	if err == nil {
		// voice file exists
		defer voiceFile.Close()
		v.naturalVoice = true
		for _, zfile := range voiceFile.File {
			file, err := zfile.Open()
			if err != nil {
				fmt.Fprintln(os.Stderr, "Unable to open file from zip:", zfile.Name)
				continue
			}
			defer file.Close()
			if !strings.HasSuffix(zfile.Name, ".wav") {
				continue
			}
			noteName := strings.Split(filepath.Base(zfile.Name), ".")[0]
			if key, found := v.noteKeyMap[noteName]; found {
				var header WaveHeader
				header.ReadHeader(file)
				if header.SampleRate != 44100 || header.BitsPerSample != 16 {
					fmt.Fprintln(os.Stderr, "Unsupported sample file:", zfile.Name)
					continue
				}
				buf, err := ioutil.ReadAll(file)
				if err != nil {
					fmt.Fprintln(os.Stderr, "Unable to read file from zip:", zfile.Name)
					continue
				}
				rest := wholeNote - len(buf)/2
				if rest > 0 {
					// too short, sample should be a whole note
					bufRest := make([]byte, rest*2)
					buf = append(buf, bufRest...)
				}
				buf16 := byteToInt16Buf(buf)
				if len(buf16) < wholeNote {
					fmt.Fprintln(os.Stderr, "Sample note duration must be 90112 samples long.")
				}
				trimWave(buf16)
				v.keyNatMap[key] = buf16
			} else {
				fmt.Fprintln(os.Stderr, "Unknown note name in voice file:", noteName)
			}
		}
	}

	return v
}

func (v *Violin) generateNote(key rune, duration int) []int16 {
	// default voice
	freq, found := v.keyFreqMap[key]
	if !found {
		fmt.Fprintln(os.Stderr, "frequency not found: key", key)
		return []int16{}
	}
	buf := make([]int16, duration)
	timer0 := 0.0
	timer1 := 0.0
	timer2 := 0.0
	timer3 := 0.0
	tick0 := 2 * math.Pi / SampleRate64 * freq
	tick1 := tick0 * 2
	tick2 := tick1 * 3
	tick3 := tick2 * 4
	amp := SampleAmp16bit * 0.5
	for i := range buf {
		sin0 := math.Sin(timer0)
		sin1 := sin0 * math.Sin(timer1)
		sin2 := sin1 * math.Sin(timer2)
		sin3 := sin2 * math.Sin(timer3)
		bar0 := amp * sin0
		bar1 := bar0 * sin1 / 2 * sin0
		bar2 := bar0 * sin2 / 3 * sin0
		bar3 := bar0 * sin3 / 4 * sin0
		buf[i] = int16(bar0 + bar1 + bar2 + bar3)
		timer0 += tick0
		timer1 += tick1
		timer2 += tick2
		timer3 += tick3
	}
	trimWave(buf)
	return buf
}

// GetNote prepares note wave form
func (v *Violin) GetNote(note *Note, sustain *Sustain) (found bool) {
	var bufNote []int16
	if v.naturalVoice {
		bufNote, found = v.keyNatMap[note.key]
	}
	if !found {
		bufNote, found = v.keyDefMap[note.key]
	}
	if !found {
		return
	}

	buf := make([]int16, len(bufNote))
	copy(buf, bufNote) // get a copy of the note
	applyNoteVolume(buf, note.volume, note.amplitude)

	// Sustain note
	if note.duration == 'W' {
		// Whole note
		if v.NaturalVoice() {
			// sustain current note
			copyBuffer(sustain.buf, buf[len(buf)/3:])
		}
	} else {
		if v.NaturalVoice() {
			// sustain current note
			copyBuffer(sustain.buf, buf[note.samples:])
		}
	}

	// measure note

	if n := note.samples - len(buf); n > 0 {
		// expand buffer
		buf = append(buf, make([]int16, n)...)
	}
	buf = buf[:note.samples]

	// clean note
	trimWave(buf)

	// release note
	releaseNote(buf, 0, 0.99)

	note.buf = buf

	return
}

// Sustain flag
func (v *Violin) Sustain() bool {
	return false
}

// NaturalVoice flag
func (v *Violin) NaturalVoice() bool {
	return v.naturalVoice
}

// NaturalVoiceFound flag
func (v *Violin) NaturalVoiceFound() bool {
	return v.naturalVoiceFound
}

// ComputerVoice flag
func (v *Violin) ComputerVoice(enable bool) {
	v.naturalVoice = !enable
}

func (v *Violin) raiseNote(note *Note, ratio float64) {
	buflen := len(note.buf)
	raise := float64(buflen) * ratio
	tick := SampleAmp16bit / raise
	volume := 0.0
	for i, bar := range note.buf {
		bar64 := float64(bar)
		bar64 = bar64 * (volume / SampleAmp16bit)
		note.buf[i] = int16(bar64)
		volume += tick
		if SampleAmp16bit <= volume {
			break
		}
	}
}

// SustainNote applies sustain settings to a note
func (v *Violin) SustainNote(note *Note, sustain *Sustain) {

	// |    ___ release
	// |  /      \
	// | /         ----|    <----------- sustain
	// |/                \     buflen
	// |---|---|-------|--|----|
	//   attack|       |  duration > 0
	//         |       ratio
	//         decay
	//
	// attack: allows overriting the beginning by the previous note
	//

	buf := note.buf
	buflen := len(buf)
	volume64 := float64(note.volume)
	if v.naturalVoice {
		attack := float64(9-sustain.attack) / 10
		v.raiseNote(note, attack)
		release := float64(1+sustain.release) / 10
		releaseNote(buf, 0, release)
		return
	}

	// Sustain default voice amplitude for ADSR phases
	// |  /|\            A - attack
	// | / | \ _____     D - decay
	// |/  |  |    | \   S - sustain
	// |--------------   R - release
	//   A  D  S    R

	if note.volume == 0 {
		return
	}
	attack := int(float64(buflen/200) * float64(sustain.attack))
	decay := (buflen-attack)/10 + ((buflen - attack) / 20 * sustain.decay)
	S := int16(volume64 / 10.0 * float64(sustain.sustain+1))
	sustainCount := (buflen - attack - decay) / 2
	R := buflen - attack - decay - sustainCount
	attack64 := float64(attack)
	decay64 := float64(decay)
	sustain64 := float64(S)
	release64 := float64(R)
	countD := 0.0
	countR := 0.0
	for i, bar := range buf {
		i64 := float64(i)
		bar64 := float64(bar)
		if i >= attack+decay+sustainCount {
			// Release phase, decay volume to zero
			bar64 = bar64 * ((sustain64 * (release64 - countR) / release64) / volume64)
			countR++
		} else if i >= attack+decay {
			// Sustain phase, hold volume on sustain level
			bar64 = bar64 * (sustain64 / volume64)
		} else if i >= attack && decay > 0 {
			// Decay phase, decay volume to sustain level
			gap := (volume64 - sustain64) * ((decay64 - countD) / decay64)
			bar64 = bar64 * ((sustain64 + gap) / volume64)
			countD++
		} else if i <= attack && attack > 0 {
			// Attack phase, raise volume to max
			bar64 = bar64 * (i64 / attack64)
		}
		buf[i] = int16(bar64)
	}
}