package rpc

import (
	"io"
	"log"
	"net/rpc"

	"github.com/hashicorp/packer/common/random"
)

// TrackProgress starts a pair of ProgressTrackingClient and ProgressProgressTrackingServer
// that will send the size of each read bytes of stream.
// In order to track an operation on the terminal side.
func (u *Ui) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
	pl := &TrackProgressParameters{
		Src:         src,
		CurrentSize: currentSize,
		TotalSize:   totalSize,
	}
	var trackingID string
	if err := u.client.Call("Ui.NewTrackProgress", pl, &trackingID); err != nil {
		log.Printf("Error in Ui.NewTrackProgress RPC call: %s", err)
		return stream
	}
	cli := &ProgressTrackingClient{
		id:     trackingID,
		client: u.client,
		stream: stream,
	}
	return cli
}

type ProgressTrackingClient struct {
	id     string
	client *rpc.Client
	stream io.ReadCloser
}

// Read will send len(b) over the wire instead of it's content
func (u *ProgressTrackingClient) Read(b []byte) (read int, err error) {
	defer func() {
		if err := u.client.Call("Ui"+u.id+".Add", read, new(interface{})); err != nil {
			log.Printf("Error in ProgressTrackingClient.Read RPC call: %s", err)
		}
	}()
	return u.stream.Read(b)
}

func (u *ProgressTrackingClient) Close() error {
	log.Printf("closing")
	if err := u.client.Call("Ui"+u.id+".Close", nil, new(interface{})); err != nil {
		log.Printf("Error in ProgressTrackingClient.Close RPC call: %s", err)
	}
	return u.stream.Close()
}

type TrackProgressParameters struct {
	Src         string
	TotalSize   int64
	CurrentSize int64
}

func (ui *UiServer) NewTrackProgress(pl *TrackProgressParameters, reply *string) error {
	// keep identifier as is for now
	srvr := &ProgressTrackingServer{
		id: *reply,
	}

	*reply = pl.Src + random.AlphaNum(6)
	srvr.stream = ui.ui.TrackProgress(pl.Src, pl.CurrentSize, pl.TotalSize, nopReadCloser{})
	err := ui.register("Ui"+*reply, srvr)
	if err != nil {
		log.Printf("failed to register ProgressTrackingServer at %s: %s", *reply, err)
		return err
	}
	return nil
}

type ProgressTrackingServer struct {
	id     string
	stream io.ReadCloser
}

func (t *ProgressTrackingServer) Add(size int, _ *interface{}) error {
	stubBytes := make([]byte, size, size)
	t.stream.Read(stubBytes)
	return nil
}

func (t *ProgressTrackingServer) Close(_, _ *interface{}) error {
	t.stream.Close()
	return nil
}

type nopReadCloser struct {
}

func (nopReadCloser) Close() error               { return nil }
func (nopReadCloser) Read(b []byte) (int, error) { return len(b), nil }
