package cache

import (
	"io"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"
)

// DownloadError conveys information about a download request that failed.
type DownloadError struct {
	Status string
}

// Error returns a description of the error.
func (d *DownloadError) Error() string {
	return d.Status
}

// downloader attempts to download a file from a remote URL.
type downloader struct {
	doneMutex  sync.Mutex
	err        error
	entry      *Entry
	entryMutex sync.Mutex
}

// newDownloader creates a new downloader.
func newDownloader(rawurl, jsonFilename, dataFilename string) *downloader {
	d := &downloader{}
	d.doneMutex.Lock()
	d.entryMutex.Lock()
	go func() {
		defer func() {
			d.doneMutex.Unlock()
		}()
		once := &sync.Once{}
		trigger := func() {
			once.Do(func() {
				d.entryMutex.Unlock()
			})
		}
		defer trigger()
		resp, err := http.Get(rawurl)
		if err != nil {
			d.err = err
			return
		}
		defer resp.Body.Close()
		if resp.StatusCode != 200 {
			d.err = &DownloadError{
				Status: resp.Status,
			}
			return
		}
		f, err := os.Create(dataFilename)
		if err != nil {
			d.err = err
			return
		}
		defer f.Close()
		d.entry = &Entry{
			URL:           rawurl,
			ContentLength: strconv.FormatInt(resp.ContentLength, 10),
			ContentType:   resp.Header.Get("Content-Type"),
			LastModified:  resp.Header.Get("Last-Modified"),
		}
		if d.entry.ContentType == "" {
			d.entry.ContentType = "application/octet-stream"
		}
		if d.entry.LastModified == "" {
			d.entry.LastModified = time.Now().Format(http.TimeFormat)
		}
		if err = d.entry.Save(jsonFilename); err != nil {
			d.err = err
			return
		}
		trigger()
		n, err := io.Copy(f, resp.Body)
		if err != nil {
			d.err = err
			return
		}
		d.entry.ContentLength = strconv.FormatInt(n, 10)
		d.entry.Complete = true
		d.entry.Save(jsonFilename)
	}()
	return d
}

// GetEntry retrieves the entry associated with the download.
func (d *downloader) GetEntry() (*Entry, error) {
	d.entryMutex.Lock()
	defer d.entryMutex.Unlock()
	return d.entry, d.err
}

// WaitForDone will block until the download completes.
func (d *downloader) WaitForDone() error {
	d.doneMutex.Lock()
	defer d.doneMutex.Unlock()
	return d.err
}
