package api

import (
	"crypto/sha1"
	"encoding/hex"
	"io"
	"log"

	"secondbit.org/uuid"
)

func Upload(collectionID uuid.ID, r io.Reader, c Context) (Item, int64, error) {
	hashReader, hashWriter := io.Pipe()
	uploadReader, uploadWriter := io.Pipe()
	m := io.MultiWriter(hashWriter, uploadWriter)

	hashChan := make(chan string)
	hashError := make(chan error)
	hashDone := make(chan struct{})

	cpDone := make(chan struct{})
	cpErr := make(chan error)

	uploadDone := make(chan struct{})
	uploadErr := make(chan error)

	n := make(chan int64)

	var bytesWritten int64
	tmp := uuid.NewID().String()
	tmp = "tmp/" + tmp

	go hash(hashReader, hashChan, hashError, hashDone)
	go asyncCopy(m, r, n, cpErr, cpDone)
	if c.Storage != nil {
		go c.Storage.Upload(c.Bucket, tmp, uploadReader, uploadErr, uploadDone)
	}

	select {
	case err := <-hashError:
		if err != nil {
			hashWriter.CloseWithError(err)
			uploadWriter.CloseWithError(err)
			return Item{}, 0, err
		} else {
			hashWriter.Close()
			uploadWriter.Close()
		}
	case err := <-cpErr:
		if err != nil {
			hashWriter.CloseWithError(err)
			uploadWriter.CloseWithError(err)
			return Item{}, 0, err
		} else {
			hashWriter.Close()
			uploadWriter.Close()
		}
	case err := <-uploadErr:
		if err != nil {
			hashWriter.CloseWithError(err)
			uploadWriter.CloseWithError(err)
			return Item{}, 0, err
		} else {
			hashWriter.Close()
			uploadWriter.Close()
		}
	case <-cpDone:
		hashWriter.Close()
		uploadWriter.Close()
	}
	<-cpDone
	<-hashDone
	if c.Storage != nil {
		<-uploadDone
	}
	bytesWritten = <-n
	finalLocation := <-hashChan
	if c.Storage != nil {
		err := c.Storage.Move(c.Bucket, tmp, c.Bucket, finalLocation)
		if err != nil {
			return Item{}, 0, err
		}
	}
	return Item{
		Blob:         finalLocation,
		Bucket:       c.Bucket,
		CollectionID: collectionID,
	}, bytesWritten, nil
}

func hash(r io.Reader, resp chan string, errs chan error, done chan struct{}) {
	if resp != nil {
		defer close(resp)
	}
	h := sha1.New()
	go asyncCopy(h, r, nil, errs, done)
	<-done
	resp <- hex.EncodeToString(h.Sum(nil))
}

func del(bucket, tmp string, c Context) {
	err := c.Storage.Delete(bucket, tmp)
	if err != nil {
		log.Printf("Error deleting temporary upload %s in %s: %s\n", tmp, bucket, err)
	}
}

func asyncCopy(dst io.Writer, src io.Reader, n chan int64, errs chan error, done chan struct{}) {
	if errs != nil {
		defer close(errs)
	}
	if done != nil {
		defer close(done)
	}
	written, err := io.Copy(dst, src)
	if errs != nil && err != nil {
		errs <- err
	}
	if n != nil {
		go func(n chan int64, written int64) {
			n <- written
		}(n, written)
	}
}
