/*
Raw Action

Directly write a file to the output image at a given offset.
This is typically useful for bootloaders.

Yaml syntax:
 - action: raw
   origin: name
   source: filename
   offset: bytes

Mandatory properties:

- origin -- reference to named file or directory.

- source -- the name of file located in 'origin' to be written into the output image.

Optional properties:

- offset -- offset in bytes for output image file.
It is possible to use internal templating mechanism of debos to calculate offset
with sectors (512 bytes) instead of bytes, for instance: '{{ sector 256 }}'.
The default value is zero.

- partition -- named partition to write to
*/
package actions

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strconv"

	"github.com/go-debos/debos"
)

type RawAction struct {
	debos.BaseAction `yaml:",inline"`
	Origin           string // there the source comes from
	Offset           string
	Source           string // relative path inside of origin
	Path             string // deprecated option (for backward compatibility)
	Partition        string // Partition to write otherwise full image
}

func (raw *RawAction) checkDeprecatedSyntax() error {

	// New syntax is based on 'origin' and 'source'
	// Check if we do not mix new and old syntax
	// TODO: remove deprecated syntax verification
	if len(raw.Path) > 0 {
		// Deprecated syntax based on 'source' and 'path'
		log.Printf("Usage of 'source' and 'path' properties is deprecated.")
		log.Printf("Please use 'origin' and 'source' properties.")
		if len(raw.Origin) > 0 {
			return errors.New("Can't mix 'origin' and 'path'(deprecated option) properties")
		}
		if len(raw.Source) == 0 {
			return errors.New("'source' and 'path' properties can't be empty")
		}
		// Switch to new syntax
		raw.Origin = raw.Source
		raw.Source = raw.Path
		raw.Path = ""
	}
	return nil
}

func (raw *RawAction) Verify(context *debos.DebosContext) error {
	if err := raw.checkDeprecatedSyntax(); err != nil {
		return err
	}

	if len(raw.Origin) == 0 || len(raw.Source) == 0 {
		return errors.New("'origin' and 'source' properties can't be empty")
	}

	return nil
}

func (raw *RawAction) Run(context *debos.DebosContext) error {
	raw.LogStart()
	origin, found := context.Origins[raw.Origin]
	if !found {
		return fmt.Errorf("Origin `%s` doesn't exist\n", raw.Origin)
	}
	s := path.Join(origin, raw.Source)
	content, err := ioutil.ReadFile(s)

	if err != nil {
		return fmt.Errorf("Failed to read %s", s)
	}

	var devicePath string
	if raw.Partition != "" {
		for _, p := range context.ImagePartitions {
			if p.Name == raw.Partition {
				devicePath = p.DevicePath
				break
			}
		}

		if devicePath == "" {
			return fmt.Errorf("Failed to find partition named %s", raw.Partition)
		}
	} else {
		devicePath = context.Image
	}

	target, err := os.OpenFile(devicePath, os.O_WRONLY, 0)
	if err != nil {
		return fmt.Errorf("Failed to open %s: %v", devicePath, err)
	}
	defer target.Close()

	var offset int64 = 0
	if len(raw.Offset) > 0 {
		offset, err = strconv.ParseInt(raw.Offset, 0, 64)
		if err != nil {
			return fmt.Errorf("Couldn't parse offset %v", err)
		}
	}

	bytes, err := target.WriteAt(content, offset)
	if bytes != len(content) {
		return fmt.Errorf("Couldn't write complete data %v", err)
	}

	err = target.Sync()
	if err != nil {
		return fmt.Errorf("Couldn't sync content %v", err)
	}

	return nil
}
