package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"time"

	"github.com/pin/tftp"
)

var url string
var dir string

// readHandler is called when client starts file download from server
func readHandler(filename string, rf io.ReaderFrom) error {

	if _, err := os.Stat(path.Join(dir, filename)); err == nil {
		file, err := os.Open(path.Join(dir, filename))
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return err
		}

		fi, err := file.Stat()
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return err
		}

		rf.(tftp.OutgoingTransfer).SetSize(fi.Size())
		n, err := rf.ReadFrom(file)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return err
		}

		fmt.Printf("%s %d bytes sent\n", filename, n)
	} else { // File not found locally. Proxying the request.
		fileUrl := url + "/" + filename
		resp, err := http.Get(fileUrl)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return err
		}
		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			io.Copy(ioutil.Discard, resp.Body)
			return fmt.Errorf("Received status code: %d", resp.StatusCode)
		}

		rf.(tftp.OutgoingTransfer).SetSize(resp.ContentLength)
		n, err := rf.ReadFrom(resp.Body)
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return err
		}

		fmt.Printf("%s %d bytes sent\n", filename, n)
	}

	return nil
}

func main() {
	flag.StringVar(&dir, "dir", "/var/lib/tftpboot", "The directory to serve files from. For example /var/lib/tftpboot")
	flag.StringVar(&url, "url", "http://example.com", "The URL to proxy requests to. For example http://example.com")
	flag.Parse()

	// use nil in place of handler to disable read or write operations
	s := tftp.NewServer(readHandler, nil)
	s.SetTimeout(5 * time.Second)  // optional
	err := s.ListenAndServe(":69") // blocks until s.Shutdown() is called
	if err != nil {
		fmt.Fprintf(os.Stdout, "server: %v\n", err)
		os.Exit(1)
	}
}
