package s19

import (
	"bufio"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"sort"
	"strconv"
)

type S19Reader struct {
	FilePath string
	File     *os.File
	OsSize   int64
}

func NewS19Reader(filePath string) (*S19Reader, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	return &S19Reader{
		FilePath: filePath,
		File:     file,
		OsSize:   stat.Size(),
	}, nil
}

func (fr *S19Reader) ReadAllRecords() (records []*SRecord, err error) {
	_, err = fr.File.Seek(0, 0)
	if err != nil {
		return nil, err
	}

	scanner := bufio.NewScanner(fr.File)
	for scanner.Scan() {
		if len(scanner.Text()) < 10 {
			continue
		}
		record, err := parseSRecord(scanner.Text())
		if err != nil {
			return nil, err
		}

		records = append(records, record)
		//log.Debug().Msg(fmt.Sprint(record))
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return records, nil
}

func parseSRecord(line string) (*SRecord, error) {
	if len(line) < 10 || line[0] != 'S' {
		return nil, fmt.Errorf("invalid S-record format '%s'", line)
	}

	record := &SRecord{}
	record.Type = line[:2]

	// Extract byte count (hexadecimal, 2 characters)
	count, err := strconv.ParseInt(line[2:4], 16, 8)
	if err != nil {
		return nil, fmt.Errorf("invalid byte count: %v", err)
	}
	record.Count = int(count)

	// Address length depends on the type
	var addressLength int
	switch record.Type {
	case "S0", "S1", "S9": // Address is 2 bytes
		addressLength = 4
	case "S2", "S8": // Address is 3 bytes
		addressLength = 6
	case "S3", "S7": // Address is 4 bytes
		addressLength = 8
	default:
		return nil, fmt.Errorf("unsupported record type: %s", record.Type)
	}

	// Extract and parse the address
	if len(line) < 4+addressLength {
		return nil, errors.New("record is too short for address")
	}
	address, err := strconv.ParseUint(line[4:4+addressLength], 16, 32)
	if err != nil {
		return nil, fmt.Errorf("invalid address: %v", err)
	}
	record.Address = uint32(address)

	// Extract data and checksum
	dataStart := 4 + addressLength
	dataEnd := len(line) - 2 // Exclude checksum
	if dataEnd > dataStart {
		record.Data, err = hex.DecodeString(line[dataStart:dataEnd])
		if err != nil {
			return nil, fmt.Errorf("invalid data: %v", err)
		}
	}

	// Validate checksum
	checksum, err := strconv.ParseUint(line[len(line)-2:], 16, 8)
	if err != nil {
		return nil, fmt.Errorf("invalid checksum: %v", err)
	}
	if !validateChecksum(line, byte(checksum)) {
		return nil, errors.New("checksum validation failed")
	}

	return record, nil
}

func (fr *S19Reader) Close() error {
	return fr.File.Close()
}

func (*S19Reader) DetectAddressRanges(records []*SRecord) []AddressRange {
	// Sort records by address
	sort.Slice(records, func(i, j int) bool {
		return records[i].Address < records[j].Address
	})

	var ranges []AddressRange
	var currentRange *AddressRange

	for sliceIndex, record := range records {
		if currentRange == nil {
			// Start a new range
			currentRange = &AddressRange{
				SliceStart:   uint32(sliceIndex),
				SliceEnd:     0,
				StartAddress: record.Address,
				EndAddress:   record.Address + uint32(len(record.Data)) - 1,
				Size:         uint32(len(record.Data)),
			}
			continue
		}

		// Check if the current record is contiguous with the previous range
		if record.Address == currentRange.EndAddress+1 {
			currentRange.EndAddress = record.Address + uint32(len(record.Data)) - 1
			currentRange.Size += uint32(len(record.Data))
			currentRange.SliceEnd = uint32(sliceIndex)
		} else {
			// Save the current range and start a new one
			ranges = append(ranges, *currentRange)
			currentRange = &AddressRange{
				SliceStart:   uint32(sliceIndex),
				SliceEnd:     0,
				StartAddress: record.Address,
				EndAddress:   record.Address + uint32(len(record.Data)) - 1,
				Size:         uint32(len(record.Data)),
			}
		}
	}

	// Append the last range
	if currentRange != nil {
		ranges = append(ranges, *currentRange)
	}

	return ranges
}