package main

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"path"
	"strings"
	"text/template"

	"github.com/dhowden/tag"
)

const ID3_TEMPLATE = "{{.Artist}} // {{.Album}} // {{.Title}}"

type Tags struct {
	Title string
	Album string
	Artist string
	AlbumArtist string
	Composer string
	Genre string
	Year int
	Track int
	TrackCount int
	Disc int
	DiscCount int
}

func (t *Tags) Validate() error {
	if t.Title == "" {
		return errors.New("missing Title")
	}
	if t.Album == "" {
		return errors.New("missing Album")
	}
	if t.Artist == "" {
		return errors.New("missing Artist")
	}
	if t.AlbumArtist == "" {
		return errors.New("missing AlbumArtist")
	}
	return nil
}

func main() {
	err := mainErr()
	if err != nil {
		log.Fatal("ERROR: ", err)
	}
}

func mainErr() error {
	// file from arg
	playlistFile := os.Args[1]

	newFileLines, err := loadPlaylistFile(playlistFile)
	if err != nil {
		return err
	}

	newFileContent := strings.Join(newFileLines, "\n")

	return writeFile(playlistFile, newFileContent)
}

// returns array of all id3 tags
func loadPlaylistFile(playlistFile string) ([]string, error) {
    file, err := os.Open(playlistFile)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

	playlistTags := make([]string, 0)
	lineNumber := 0

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
		// line by line
        line := scanner.Text()
		tags, err := loadPlaylistFileLine(line)
		if err != nil {
			// return nil, fmt.Errorf("%s line %d: %s", playlistFile, lineNumber+1, err)
			errMsg := fmt.Errorf("line %d (from %s:%d): %s", len(playlistTags)+1, playlistFile, lineNumber+1, err)
			fmt.Println(errMsg)
			playlistTags = append(playlistTags, fmt.Sprintf("BAD LINE --- %s", line))
		} else if tags != nil {
			playlistTags = append(playlistTags, *tags)
		}

		lineNumber++
    }

    if err := scanner.Err(); err != nil {
        return nil, err
    }

	return playlistTags, nil
}

// ignore m3u8 tags
func loadPlaylistFileLine(line string) (*string, error) {
	line = strings.TrimSpace(line)

	if []rune(line)[1] == '#' {
		return nil, nil
	}
	if len(line) == 0 {
		return nil, nil
	}
	
	tags, err := loadSongFile(line)
	return &tags, err
}

// returns id3 tags
func loadSongFile(fileName string) (string, error) {
	// load file
	file, err := os.Open(fileName)
	if err != nil {
		return "", err
	}
	defer file.Close()

	tags, err := tag.ReadFrom(file)
	if err != nil {
		return "", err
	}
	
	// parse tags
	track, trackCount := tags.Track()
	disc, discCount := tags.Disc()

	loadedTags := Tags{
		Title: tags.Title(),
		Album: tags.Album(),
		Artist: tags.Artist(),
		AlbumArtist: tags.AlbumArtist(),
		Composer: tags.Composer(),
		Genre: tags.Genre(),
		Year: tags.Year(),
		Track: track,
		TrackCount: trackCount,
		Disc: disc,
		DiscCount: discCount,
	}

	loadedTags.Album = removeAlbumTypeSuffix(loadedTags.Album)

	err = loadedTags.Validate()
	if err != nil {
		return "", err
	}

	return id3Template(loadedTags)
}

// remove " - Single" and etc
func removeAlbumTypeSuffix(albumName string) string {
	if str, ok := removeSuffix(albumName, " - LP"); ok {
		albumName = str
	} else if str, ok := removeSuffix(albumName, " - EP"); ok {
		albumName = str
	} else if str, ok := removeSuffix(albumName, " - Single"); ok {
		albumName = str
	}
	return albumName
}

func removeSuffix(str, suff string) (string, bool) {
	if strings.HasSuffix(str, suff) {
		suffIndex := len(str) - len(suff)
		return str[:suffIndex], true
	}
	return str, false
}

// converts an id3 tag into a string
func id3Template(tags Tags) (string, error) {
	tmpl := template.Must(template.New("id3template").Parse(ID3_TEMPLATE))

	bufPath := bytes.NewBufferString("")
	err := tmpl.Execute(bufPath, tags)
	if err != nil {
		return "", err
	}

	return bufPath.String(), nil
}

// outputs all strings as a file
func writeFile(inFileName string, fileContent string) error {
	inFileExt := path.Ext(inFileName)
	extIndex := strings.LastIndex(inFileName, inFileExt)
	newFileName := inFileName[:extIndex] + ".txt"

	return os.WriteFile(newFileName, []byte(fileContent), 0666)
}
