package libforerunner

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"

	"code.justin.tv/release/libforerunner/internal/config"
	"code.justin.tv/release/libforerunner/pkg/interchange"
)

type ForerunnerAPI interface {
	GetConfig(interface{}) error
	Write(w io.Writer) error
	Print() error
	Version() string
}

// Assert that Forerunner implements the ForerunnerAPI
var _ ForerunnerAPI = &Forerunner{}

// Forerunner provides a series of functions to interact with the forerunner
// system.
type Forerunner struct {
	interchange         *interchange.Interchange
	interchangeLocation string
	config              *config.Config
}

type Options struct {
	DefaultConfig interface{}
}

// Init will load and initialize the forerunner session.
func Init(options *Options) (*Forerunner, error) {
	fr := initStruct()

	fr.interchangeLocation = os.Getenv(interchange.EnvName)
	if fr.interchangeLocation != "" {
		f, err := os.Open(fr.interchangeLocation)
		if err != nil {
			return nil, fmt.Errorf("Issue opening forerunner data: %v", err)
		}
		defer f.Close()

		err = json.NewDecoder(f).Decode(fr.interchange)
		if err != nil {
			return nil, fmt.Errorf("Issue decoding forerunner data: %v", err)
		}
	} else {
		log.Printf("ENV %q isn't set, was this executed via forerunner?", interchange.EnvName)
	}

	if options != nil && options.DefaultConfig != nil {
		var err error

		fr.config, err = config.NewConfig()
		if err != nil {
			return nil, err
		}
		fr.config.SetDefaults(options.DefaultConfig)
	}

	return fr, nil
}

// NewInterchange will init maps/etc inside the forerunner struct.
func initStruct() *Forerunner {
	return &Forerunner{
		interchange: &interchange.Interchange{
			Entries: map[string]*interchange.ConfigEntry{},
		},
	}
}

// loadInterchange will read the interchange from disk and load the contents into memory.
func (fr *Forerunner) loadInterchange() error {
	f, err := os.Open(fr.interchangeLocation)
	if err != nil {
		return err
	}
	defer f.Close()

	if err := json.NewDecoder(f).Decode(fr); err != nil {
		return fmt.Errorf("Unable to read %q: %v", fr.interchangeLocation, err)
	}

	if fr.interchange.Version != interchange.Version {
		return fmt.Errorf("Invalid interchange version, got: %f; want: %f", fr.interchange.Version, interchange.Version)
	}

	return nil
}

// FPrint a copy of the current running config to `f` for debugging.
func (fr *Forerunner) Write(w io.Writer) error {
	return fr.config.Write(w)
}

// Print a copy of the current running config to stderr for debugging.
func (fr *Forerunner) Print() error {
	return fr.Write(os.Stderr)
}

// GetConfig will take a struct and populate fields based upon type and other
// settings.
func (fr *Forerunner) GetConfig(v interface{}) error {
	if fr.config == nil {
		return fmt.Errorf("Config was not enabled. Please add default config at initalizaiton of forerunner.")
	}

	entries := interchange.EntrySet{}
	if fr.interchange != nil && fr.interchange.Entries != nil {
		entries = fr.interchange.Entries
	}
	return fr.config.GetConfig(v, entries)
}

// Version returns the version of the forerunner interchange format that is
// compiled.
func (fr *Forerunner) Version() string {
	return interchange.Version
}
