package icecast

import (
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
)

type icyMetadata struct {
	StreamTitle string
}

type icyReader struct {
	r          io.Reader
	metaInt    int
	audioLeft  int
	onMetadata func(icyMetadata)
}

func newICYReader(r io.Reader, metaInt int, onMetadata func(icyMetadata)) io.Reader {
	if r == nil || metaInt <= 0 {
		return r
	}
	return &icyReader{
		r:          r,
		metaInt:    metaInt,
		audioLeft:  metaInt,
		onMetadata: onMetadata,
	}
}

func (r *icyReader) Read(p []byte) (int, error) {
	if len(p) == 0 {
		return 0, nil
	}
	for {
		if r.audioLeft == 0 {
			if err := r.readMetadataBlock(); err != nil {
				return 0, err
			}
			r.audioLeft = r.metaInt
			continue
		}
		want := len(p)
		if want > r.audioLeft {
			want = r.audioLeft
		}
		n, err := r.r.Read(p[:want])
		if n > 0 {
			r.audioLeft -= n
			return n, nil
		}
		if err != nil {
			return 0, err
		}
	}
}

func (r *icyReader) readMetadataBlock() error {
	var lenBuf [1]byte
	if _, err := io.ReadFull(r.r, lenBuf[:]); err != nil {
		return err
	}
	blockLen := int(lenBuf[0]) * 16
	if blockLen == 0 {
		return nil
	}
	block := make([]byte, blockLen)
	if _, err := io.ReadFull(r.r, block); err != nil {
		return err
	}
	if r.onMetadata != nil {
		r.onMetadata(parseICYMetadata(block))
	}
	return nil
}

// parseICYMetadata parses the ICY inline metadata block.
//
// ICY metadata is a semicolon-delimited key=value format where values are
// single-quoted strings. A naive strings.Split(raw, ";") breaks when the
// StreamTitle itself contains semicolons (e.g. "Artist - Title; Live Edit").
// This parser is quote-aware: it only splits on semicolons that appear
// outside of single-quoted value strings.
func parseICYMetadata(block []byte) icyMetadata {
	raw := strings.TrimRight(string(bytes.Trim(block, "\x00")), "\x00")
	meta := icyMetadata{}

	fields := splitICYFields(raw)
	for _, field := range fields {
		field = strings.TrimSpace(field)
		if !strings.HasPrefix(field, "StreamTitle=") {
			continue
		}
		v := strings.TrimPrefix(field, "StreamTitle=")
		v = strings.TrimSpace(v)
		// Strip enclosing single or double quotes.
		if len(v) >= 2 {
			if (v[0] == '\'' && v[len(v)-1] == '\'') ||
				(v[0] == '"' && v[len(v)-1] == '"') {
				v = v[1 : len(v)-1]
			}
		}
		meta.StreamTitle = v
		break
	}
	return meta
}

// splitICYFields splits an ICY metadata string on semicolons that appear
// outside of single-quoted value strings. Semicolons inside quotes (e.g.
// StreamTitle='Artist - Song; Live';) are preserved as part of the value.
func splitICYFields(s string) []string {
	var fields []string
	inQuote := false
	start := 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c == '\'' {
			inQuote = !inQuote
		}
		if c == ';' && !inQuote {
			fields = append(fields, s[start:i])
			start = i + 1
		}
	}
	if start < len(s) {
		fields = append(fields, s[start:])
	}
	return fields
}

func parseICYMetaInt(raw string) (int, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 0, nil
	}
	n, err := strconv.Atoi(raw)
	if err != nil || n < 0 {
		return 0, fmt.Errorf("invalid icy-metaint: %q", raw)
	}
	return n, nil
}
