// Copyright © 2016 Asteris, LLC
//
// 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 content

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/asteris-llc/converge/resource"
	"golang.org/x/net/context"
)

// Content renders content to disk
type Content struct {
	// configured content of the file
	Content string `export:"content"`

	// configured destination of the file
	Destination string `export:"destination"`
}

// Check if the content needs to be rendered
func (t *Content) Check(context.Context, resource.Renderer) (resource.TaskStatus, error) {
	diffs := make(map[string]resource.Diff)
	contentDiff := resource.TextDiff{Values: [2]string{"", t.Content}}
	stat, err := os.Stat(t.Destination)
	if os.IsNotExist(err) {
		contentDiff.Values[0] = "<file-missing>"
		diffs[t.Destination] = contentDiff
		return &resource.Status{
			Level:       resource.StatusWillChange,
			Differences: diffs,
			Output:      []string{t.Destination + ": File is missing"},
		}, nil
	} else if err != nil {
		return &resource.Status{
			Level:  resource.StatusFatal,
			Output: []string{"Cannot read `" + t.Destination + "`"},
		}, nil
	} else if stat.IsDir() {
		return &resource.Status{
			Level:  resource.StatusCantChange,
			Output: []string{t.Destination + " is a directory"},
		}, fmt.Errorf("cannot update contents of %q, it is a directory", t.Destination)
	}

	actual, err := ioutil.ReadFile(t.Destination)
	if err != nil {
		return &resource.Status{}, err
	}

	statusMessage := "OK"

	if string(actual) != t.Content {
		statusMessage = "contents differ"
		diffs[t.Destination] = resource.TextDiff{Values: [2]string{string(actual), t.Content}}
	}

	return &resource.Status{
		Output:      []string{statusMessage},
		Differences: diffs,
	}, nil
}

// Apply writes the content to disk
func (t *Content) Apply(context.Context) (resource.TaskStatus, error) {
	var perm os.FileMode
	var preChange string
	diffs := make(map[string]resource.Diff)

	stat, err := os.Stat(t.Destination)
	if os.IsNotExist(err) {
		diffs["mode"] = resource.TextDiff{Values: [2]string{"not set", "0600"}}
		perm = 0600
	} else if err != nil {
		return &resource.Status{
			Level:  resource.StatusFatal,
			Output: []string{err.Error()},
		}, err
	} else {
		perm = stat.Mode()
	}

	if rawData, readErr := ioutil.ReadFile(t.Destination); readErr != nil {
		preChange = "<file-missing>"
	} else {
		preChange = string(rawData)
	}

	diffs[t.Destination] = resource.TextDiff{Values: [2]string{preChange, t.Content}}

	if err = ioutil.WriteFile(t.Destination, []byte(t.Content), perm); err != nil {
		return &resource.Status{
			Output:      []string{err.Error()},
			Level:       resource.StatusFatal,
			Differences: diffs,
		}, err
	}

	return &resource.Status{Differences: diffs}, nil
}
