// 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 mode

import (
	"fmt"
	"os"

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

// Mode monitors the mode of a file
type Mode struct {
	resource.Status

	// path to the file that will be modified
	Destination string `export:"destination"`

	// the mode that the file or directory should be configured with
	Mode os.FileMode `export:"mode"`
}

// Check whether the Destination has the right Mode
func (t *Mode) Check(context.Context, resource.Renderer) (resource.TaskStatus, error) {
	diffs := make(map[string]resource.Diff)
	stat, err := os.Stat(t.Destination)
	if os.IsNotExist(err) {
		diffs[t.Destination] = &FileModeDiff{Expected: t.Mode}
		status := fmt.Sprintf("%q does not exist", t.Destination)
		stat := &resource.Status{
			Level:       resource.StatusMayChange,
			Differences: diffs,
		}
		stat.SetWarning(status)
		return stat, nil
	} else if err != nil {
		return nil, err
	}
	mode := stat.Mode().Perm()
	modeDiff := &FileModeDiff{Actual: mode, Expected: t.Mode}
	diffs[t.Destination] = modeDiff
	status := fmt.Sprintf("%q's mode is %q expected %q", t.Destination, mode, t.Mode)
	warningLevel := resource.StatusWontChange
	if modeDiff.Changes() {
		warningLevel = resource.StatusWillChange
	}

	t.Status = resource.Status{
		Level:       warningLevel,
		Differences: diffs,
		Output: []string{
			fmt.Sprintf("%q exists", t.Destination),
			status,
		},
	}
	return t, nil
}

// Apply the changes the Mode
func (t *Mode) Apply(context.Context) (resource.TaskStatus, error) {
	err := os.Chmod(t.Destination, t.Mode.Perm())

	if err != nil {
		return &resource.Status{
			Level:  resource.StatusFatal,
			Output: []string{fmt.Sprintf("failed to set mode on %s: %s", t.Destination, err)},
		}, err
	}

	return t, nil
}

// Validate Mode
func (t *Mode) Validate() error {
	if t.Destination == "" {
		return fmt.Errorf("task requires a %q parameter", "destination")
	}
	if !(t.Mode.IsDir() || t.Mode.IsRegular()) {
		return fmt.Errorf("invalid %q parameter: %q", "mode", t.Mode)
	}
	return nil
}

// FileModeDiff shows a diff of the file modes
type FileModeDiff struct {
	Actual   os.FileMode
	Expected os.FileMode
}

// Original shows the original file mode
func (diff *FileModeDiff) Original() string {
	return fmt.Sprint(diff.Actual)
}

// Current shows the current file mode
func (diff *FileModeDiff) Current() string {
	return fmt.Sprint(diff.Expected)
}

// Changes returns true if the expected file mode differs from the current mode
func (diff *FileModeDiff) Changes() bool {
	return diff.Actual != diff.Expected
}
