/*
 * MumbleDJ
 * By Matthieu Grieger
 * services/soundcloud.go
 * Copyright (c) 2016 Matthieu Grieger (MIT License)
 */

package services

import (
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/antonholmquist/jason"
	"github.com/layeh/gumble/gumble"
	"github.com/matthieugrieger/mumbledj/bot"
	"github.com/matthieugrieger/mumbledj/interfaces"
	"github.com/spf13/viper"
)

// SoundCloud is a wrapper around the SoundCloud API.
// https://developers.soundcloud.com/docs/api/reference
type SoundCloud struct {
	*GenericService
}

// NewSoundCloudService returns an initialized SoundCloud service object.
func NewSoundCloudService() *SoundCloud {
	return &SoundCloud{
		&GenericService{
			ReadableName: "SoundCloud",
			Format:       "bestaudio",
			TrackRegex: []*regexp.Regexp{
				regexp.MustCompile(`https?:\/\/(www\.)?soundcloud\.com\/([\w-]+)\/([\w-]+)(#t=\n\n?(:\n\n)*)?`),
			},
			PlaylistRegex: []*regexp.Regexp{
				regexp.MustCompile(`https?:\/\/(www\.)?soundcloud\.com\/([\w-]+)\/sets\/([\w-]+)`),
			},
		},
	}
}

// CheckAPIKey performs a test API call with the API key
// provided in the configuration file to determine if the
// service should be enabled.
func (sc *SoundCloud) CheckAPIKey() error {
	if viper.GetString("api_keys.soundcloud") == "" {
		return errors.New("No SoundCloud API key has been provided")
	}
	url := "http://api.soundcloud.com/tracks/13158665?client_id=%s"
	response, err := http.Get(fmt.Sprintf(url, viper.GetString("api_keys.soundcloud")))
	defer response.Body.Close()
	if err != nil {
		return err
	}
	if response.StatusCode != 200 {
		return errors.New(response.Status)
	}
	return nil
}

// GetTracks uses the passed URL to find and return
// tracks associated with the URL. An error is returned
// if any error occurs during the API call.
func (sc *SoundCloud) GetTracks(url string, submitter *gumble.User) ([]interfaces.Track, error) {
	var (
		apiURL string
		err    error
		resp   *http.Response
		v      *jason.Object
		track  bot.Track
		tracks []interfaces.Track
	)

	urlSplit := strings.Split(url, "#t=")

	apiURL = "http://api.soundcloud.com/resolve?url=%s&client_id=%s"

	if sc.isPlaylist(url) {
		// Submitter has added a playlist!
		resp, err = http.Get(fmt.Sprintf(apiURL, urlSplit[0], viper.GetString("api_keys.soundcloud")))
		defer resp.Body.Close()
		if err != nil {
			return nil, err
		}

		v, err = jason.NewObjectFromReader(resp.Body)
		if err != nil {
			return nil, err
		}

		title, _ := v.GetString("title")
		permalink, _ := v.GetString("permalink_url")
		playlist := &bot.Playlist{
			ID:        permalink,
			Title:     title,
			Submitter: submitter.Name,
			Service:   sc.ReadableName,
		}

		var scTracks []*jason.Object
		scTracks, err = v.GetObjectArray("tracks")
		if err != nil {
			return nil, err
		}

		dummyOffset, _ := time.ParseDuration("0s")
		for _, t := range scTracks {
			track, err = sc.getTrack(t, dummyOffset, submitter)
			if err != nil {
				// Skip this track.
				continue
			}
			track.Playlist = playlist
			tracks = append(tracks, track)
		}

		if len(tracks) == 0 {
			return nil, errors.New("Invalid playlist. No tracks were added")
		}
		return tracks, nil
	}

	// Submitter has added a track!

	offset := 0
	// Calculate track offset if needed
	if len(urlSplit) == 2 {
		timeSplit := strings.Split(urlSplit[1], ":")
		multiplier := 1
		for i := len(timeSplit) - 1; i >= 0; i-- {
			time, _ := strconv.Atoi(timeSplit[i])
			offset += time * multiplier
			multiplier *= 60
		}
	}
	playbackOffset, _ := time.ParseDuration(fmt.Sprintf("%ds", offset))

	resp, err = http.Get(fmt.Sprintf(apiURL, urlSplit[0], viper.GetString("api_keys.soundcloud")))
	defer resp.Body.Close()
	if err != nil {
		return nil, err
	}

	v, err = jason.NewObjectFromReader(resp.Body)
	if err != nil {
		return nil, err
	}
	track, err = sc.getTrack(v, playbackOffset, submitter)
	if err != nil {
		return nil, err
	}

	tracks = append(tracks, track)
	return tracks, nil
}

func (sc *SoundCloud) getTrack(obj *jason.Object, offset time.Duration, submitter *gumble.User) (bot.Track, error) {
	title, _ := obj.GetString("title")
	idInt, _ := obj.GetInt64("id")
	id := strconv.FormatInt(idInt, 10)
	url, _ := obj.GetString("permalink_url")
	author, _ := obj.GetString("user", "username")
	authorURL, _ := obj.GetString("user", "permalink_url")
	durationMS, _ := obj.GetInt64("duration")
	duration, _ := time.ParseDuration(fmt.Sprintf("%dms", durationMS))
	thumbnail, err := obj.GetString("artwork_url")
	if err != nil {
		// Track has no artwork, using profile avatar instead.
		thumbnail, _ = obj.GetString("user", "avatar_url")
	}

	return bot.Track{
		ID:             id,
		URL:            url,
		Title:          title,
		Author:         author,
		AuthorURL:      authorURL,
		Submitter:      submitter.Name,
		Service:        sc.ReadableName,
		Filename:       id + ".track",
		ThumbnailURL:   thumbnail,
		Duration:       duration,
		PlaybackOffset: offset,
		Playlist:       nil,
	}, nil
}
