// Copyright 2015 CoreOS, Inc.
//
// 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 metadata

import (
	"bufio"
	"bytes"
	"fmt"
	"net/http"
	"strings"

	"github.com/rancher/os/config/cloudinit/pkg"
	"github.com/rancher/os/pkg/log"
)

type Service struct {
	Root                 string
	Client               pkg.Getter
	APIVersion           string
	IsAvailableCheckPath string
	UserdataPath         string
	MetadataPath         string
	lastError            error
}

// NewDatasource creates as HTTP based cloud-data service with the corresponding paths for the user-data and meta-data.
// To check the available in IsAvailable, the apiVersion is used as path.
func NewDatasource(root, apiVersion, userdataPath, metadataPath string, header http.Header) Service {
	return NewDatasourceWithCheckPath(root, apiVersion, apiVersion, userdataPath, metadataPath, header)
}

// NewDatasourceWithCheckPath creates as HTTP based cloud-data service with the corresponding paths for the user-data and meta-data.
func NewDatasourceWithCheckPath(root, apiVersion, isAvailableCheckPath, userdataPath, metadataPath string, header http.Header) Service {
	if !strings.HasSuffix(root, "/") {
		root += "/"
	}
	return Service{root, pkg.NewHTTPClientHeader(header), apiVersion, isAvailableCheckPath, userdataPath, metadataPath, nil}
}

func (ms Service) IsAvailable() bool {
	checkURL := ms.Root + ms.IsAvailableCheckPath
	_, ms.lastError = ms.Client.Get(checkURL)
	if ms.lastError != nil {
		log.Errorf("%s: %s (lastError: %v)", "IsAvailable", checkURL, ms.lastError)
	}
	return (ms.lastError == nil)
}

func (ms *Service) Finish() error {
	return nil
}

func (ms *Service) String() string {
	return fmt.Sprintf("%s: %s (lastError: %v)", "metadata", ms.UserdataURL(), ms.lastError)
}

func (ms Service) AvailabilityChanges() bool {
	return true
}

func (ms Service) ConfigRoot() string {
	return ms.Root
}

func (ms Service) FetchUserdata() ([]byte, error) {
	return ms.FetchData(ms.UserdataURL())
}

func (ms Service) FetchData(url string) ([]byte, error) {
	if data, err := ms.Client.GetRetry(url); err == nil {
		return data, err
	} else if _, ok := err.(pkg.ErrNotFound); ok {
		return []byte{}, nil
	} else {
		return data, err
	}
}

func (ms Service) MetadataURL() string {
	return (ms.Root + ms.MetadataPath)
}

func (ms Service) UserdataURL() string {
	return (ms.Root + ms.UserdataPath)
}

func (ms Service) FetchAttributes(key string) ([]string, error) {
	url := ms.MetadataURL() + key
	resp, err := ms.FetchData(url)
	if err != nil {
		return nil, err
	}
	scanner := bufio.NewScanner(bytes.NewBuffer(resp))
	data := make([]string, 0)
	for scanner.Scan() {
		data = append(data, scanner.Text())
	}
	return data, scanner.Err()
}

func (ms Service) FetchAttribute(key string) (string, error) {
	attrs, err := ms.FetchAttributes(key)
	if err == nil && len(attrs) > 0 {
		return attrs[0], nil
	}
	return "", err
}
