package utils

import (
	//"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"time"

	"github.com/docker/machine/log"
)

const (
	timeout = time.Second * 5
)

func defaultTimeout(network, addr string) (net.Conn, error) {
	return net.DialTimeout(network, addr, timeout)
}

func getClient() *http.Client {
	transport := http.Transport{
		DisableKeepAlives: true,
		Proxy:             http.ProxyFromEnvironment,
		Dial:              defaultTimeout,
	}

	client := http.Client{
		Transport: &transport,
	}

	return &client
}

type B2dUtils struct {
	isoFilename      string
	commonIsoPath    string
	imgCachePath     string
	githubApiBaseUrl string
	githubBaseUrl    string
}

func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, isoFilename string) *B2dUtils {
	defaultBaseApiUrl := "https://api.github.com"
	defaultBaseUrl := "https://github.com"
	imgCachePath := GetMachineCacheDir()

	if githubApiBaseUrl == "" {
		githubApiBaseUrl = defaultBaseApiUrl
	}

	if githubBaseUrl == "" {
		githubBaseUrl = defaultBaseUrl
	}

	return &B2dUtils{
		isoFilename:      isoFilename,
		imgCachePath:     GetMachineCacheDir(),
		commonIsoPath:    filepath.Join(imgCachePath, isoFilename),
		githubApiBaseUrl: githubApiBaseUrl,
		githubBaseUrl:    githubBaseUrl,
	}
}

// Get the latest boot2docker release tag name (e.g. "v0.6.0").
// FIXME: find or create some other way to get the "latest release" of boot2docker since the GitHub API has a pretty low rate limit on API requests
func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) {
	//client := getClient()
	//apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl)
	//rsp, err := client.Get(apiUrl)
	//if err != nil {
	//	return "", err
	//}
	//defer rsp.Body.Close()

	//var t []struct {
	//	TagName    string `json:"tag_name"`
	//	PreRelease bool   `json:"prerelease"`
	//}
	//if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil {
	//	return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err)
	//}
	//if len(t) == 0 {
	//	return "", fmt.Errorf("no releases found")
	//}

	//// find the latest "released" release (i.e. not pre-release)
	//isoUrl := ""
	//for _, r := range t {
	//	if !r.PreRelease {
	//		tag := r.TagName
	//		isoUrl = fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag)
	//		break
	//	}
	//}
	//return isoUrl, nil

	// TODO: once we decide on the final versioning and location we will
	// enable the above "check for latest"
	u := fmt.Sprintf("https://s3.amazonaws.com/docker-mcn/public/b2d-next/%s", b.isoFilename)
	return u, nil

}

func removeFileIfExists(name string) error {
	if _, err := os.Stat(name); err == nil {
		if err := os.Remove(name); err != nil {
			log.Fatalf("Error removing temporary download file: %s", err)
		}
	}
	return nil
}

// Download boot2docker ISO image for the given tag and save it at dest.
func (b *B2dUtils) DownloadISO(dir, file, isoUrl string) error {
	u, err := url.Parse(isoUrl)
	var src io.ReadCloser
	if u.Scheme == "file" || u.Scheme == "" {
		s, err := os.Open(u.Path)
		if err != nil {
			return err
		}
		src = s
	} else {
		client := getClient()
		s, err := client.Get(isoUrl)
		if err != nil {
			return err
		}
		src = s.Body
	}

	defer src.Close()

	// Download to a temp file first then rename it to avoid partial download.
	f, err := ioutil.TempFile(dir, file+".tmp")
	if err != nil {
		return err
	}

	defer func() {
		if err := removeFileIfExists(f.Name()); err != nil {
			log.Fatalf("Error removing file: %s", err)
		}
	}()

	if _, err := io.Copy(f, src); err != nil {
		// TODO: display download progress?
		return err
	}

	if err := f.Close(); err != nil {
		return err
	}

	// Dest is the final path of the boot2docker.iso file.
	dest := filepath.Join(dir, file)

	// Windows can't rename in place, so remove the old file before
	// renaming the temporary downloaded file.
	if err := removeFileIfExists(dest); err != nil {
		return err
	}

	if err := os.Rename(f.Name(), dest); err != nil {
		return err
	}

	return nil
}

func (b *B2dUtils) DownloadLatestBoot2Docker() error {
	latestReleaseUrl, err := b.GetLatestBoot2DockerReleaseURL()
	if err != nil {
		return err
	}

	return b.DownloadISOFromURL(latestReleaseUrl)
}

func (b *B2dUtils) DownloadISOFromURL(latestReleaseUrl string) error {
	log.Infof("Downloading %s to %s...", latestReleaseUrl, b.commonIsoPath)
	if err := b.DownloadISO(b.imgCachePath, b.isoFilename, latestReleaseUrl); err != nil {
		return err
	}

	return nil
}

func (b *B2dUtils) CopyIsoToMachineDir(isoURL, machineName string) error {
	machinesDir := GetMachineDir()
	machineIsoPath := filepath.Join(machinesDir, machineName, b.isoFilename)

	// just in case the cache dir has been manually deleted,
	// check for it and recreate it if it's gone
	if _, err := os.Stat(b.imgCachePath); os.IsNotExist(err) {
		log.Infof("Image cache does not exist, creating it at %s...", b.imgCachePath)
		if err := os.Mkdir(b.imgCachePath, 0700); err != nil {
			return err
		}
	}

	// By default just copy the existing "cached" iso to
	// the machine's directory...
	if isoURL == "" {
		if err := b.copyDefaultIsoToMachine(machineIsoPath); err != nil {
			return err
		}
	} else {
		// But if ISO is specified go get it directly
		log.Infof("Downloading %s from %s...", b.isoFilename, isoURL)
		if err := b.DownloadISO(filepath.Join(machinesDir, machineName), b.isoFilename, isoURL); err != nil {
			return err
		}
	}

	return nil
}

func (b *B2dUtils) copyDefaultIsoToMachine(machineIsoPath string) error {
	if _, err := os.Stat(b.commonIsoPath); os.IsNotExist(err) {
		log.Info("No default boot2docker iso found locally, downloading the latest release...")
		if err := b.DownloadLatestBoot2Docker(); err != nil {
			return err
		}
	}

	if err := CopyFile(b.commonIsoPath, machineIsoPath); err != nil {
		return err
	}

	return nil
}
