/*
Copyright 2016 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package util

import (
	"net/url"
	"os"
	"path/filepath"

	"github.com/golang/glog"
	"github.com/hashicorp/go-getter"
	"github.com/pkg/errors"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/out"
)

const fileScheme = "file"

// ISODownloader downloads an ISO
type ISODownloader interface {
	GetISOFileURI(isoURL string) string
	CacheMinikubeISOFromURL(isoURL string) error
}

// DefaultDownloader is the default ISODownloader
type DefaultDownloader struct{}

// GetISOFileURI gets the local destination for a remote source
func (f DefaultDownloader) GetISOFileURI(isoURL string) string {
	urlObj, err := url.Parse(isoURL)
	if err != nil {
		return isoURL
	}
	if urlObj.Scheme == fileScheme {
		return isoURL
	}
	isoPath := filepath.Join(constants.GetMinipath(), "cache", "iso", filepath.Base(isoURL))
	// As this is a file URL there should be no backslashes regardless of platform running on.
	return "file://" + filepath.ToSlash(isoPath)
}

// CacheMinikubeISOFromURL downloads the ISO, if it doesn't exist in cache
func (f DefaultDownloader) CacheMinikubeISOFromURL(url string) error {
	if !f.ShouldCacheMinikubeISO(url) {
		glog.Infof("Not caching ISO, using %s", url)
		return nil
	}

	urlWithChecksum := url
	if url == constants.DefaultISOURL {
		urlWithChecksum = url + "?checksum=file:" + constants.DefaultISOSHAURL
	}

	dst := f.GetISOCacheFilepath(url)
	// Predictable temp destination so that resume can function
	tmpDst := dst + ".download"

	opts := []getter.ClientOption{getter.WithProgress(defaultProgressBar)}
	client := &getter.Client{
		Src:     urlWithChecksum,
		Dst:     tmpDst,
		Mode:    getter.ClientModeFile,
		Options: opts,
	}

	glog.Infof("full url: %s", urlWithChecksum)
	out.T(out.ISODownload, "Downloading VM boot image ...")
	if err := client.Get(); err != nil {
		return errors.Wrap(err, url)
	}
	return os.Rename(tmpDst, dst)
}

// ShouldCacheMinikubeISO returns if we need to download the ISO
func (f DefaultDownloader) ShouldCacheMinikubeISO(isoURL string) bool {
	// store the minikube-iso inside the .minikube dir

	urlObj, err := url.Parse(isoURL)
	if err != nil {
		return false
	}
	if urlObj.Scheme == fileScheme {
		return false
	}
	if f.IsMinikubeISOCached(isoURL) {
		return false
	}
	return true
}

// GetISOCacheFilepath returns the path of an ISO in the local cache
func (f DefaultDownloader) GetISOCacheFilepath(isoURL string) string {
	return filepath.Join(constants.GetMinipath(), "cache", "iso", filepath.Base(isoURL))
}

// IsMinikubeISOCached returns if an ISO exists in the local cache
func (f DefaultDownloader) IsMinikubeISOCached(isoURL string) bool {
	if _, err := os.Stat(f.GetISOCacheFilepath(isoURL)); os.IsNotExist(err) {
		return false
	}
	return true
}
