package main

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

	"gopkg.in/yaml.v2"
)

type Operation int

const (
	Sum Operation = iota
	Diff
)

var stringToOperation = map[string]Operation{
	"sum":  Sum,
	"diff": Diff,
}

var opertationToYasmFunc = map[Operation]string{
	Sum:  "sum",
	Diff: "diff",
}

func (t *Operation) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var o string
	err := unmarshal(&o)
	if err != nil {
		return err
	}

	if op, ok := stringToOperation[strings.ToLower(o)]; !ok {
		return fmt.Errorf("could not convert %s to operation id", o)
	} else {
		*t = op
	}

	return nil
}

type YasmTags struct {
	Itype string `yaml:"itype"`
	Ctype string `yaml:"ctype"`
	Prj   string `yaml:"prj"`
}

type ConfigItem struct {
	InputSignals       []string  `yaml:"input_signals"`
	InputSignalPattern string    `yaml:"input_signal_pattern"`
	OutputSignal       string    `yaml:"output_signal"`
	Operation          Operation `yaml:"operation"`

	SourceTags YasmTags `yaml:"source_tags"`
	DestTags   YasmTags `yaml:"dest_tags"`

	SignalString string
}

func (t *ConfigItem) mergeSignals() error {
	if len(t.InputSignals) == 0 {
		return errors.New("empty input signals")
	}

	if len(t.InputSignals) == 1 {
		t.SignalString = t.InputSignals[0]
	}

	ss := NewStringStack()
	for _, v := range t.InputSignals[1:] {
		ss.Push(v)
	}

	// Sum all elements before last operation
	op := Sum

	for {
		if ss.Size() <= 1 {
			break
		}

		// Ignore errors because we already checked size
		first, _ := ss.Pop()
		second, _ := ss.Pop()

		str := fmt.Sprintf("%s(or(%s, 0), or(%s, 0))", opertationToYasmFunc[op], second, first)
		ss.Push(str)
	}

	str, err := ss.Pop()
	if err != nil {
		return err
	}

	// Add or diff sum to first signal
	str = fmt.Sprintf("%s(or(%s, 0), or(%s, 0))", opertationToYasmFunc[t.Operation], t.InputSignals[0], str)

	t.SignalString = fmt.Sprintf("ctype=%s;itype=%s;prj=%s:%s",
		t.SourceTags.Ctype, t.SourceTags.Itype, t.SourceTags.Prj, str)
	return nil
}

func (t *ConfigItem) parseSignalString() error {
	// Init pattern signals
	if len(t.InputSignalPattern) > 0 {
		client, err := NewYasmHTTPApi()

		if err != nil {
			return err
		}

		t.InputSignals, err = client.RequestSignalsByPattern(t.SourceTags, t.InputSignalPattern)
		if err != nil {
			return err
		}
	}

	return t.mergeSignals()
}

type Config struct {
	Items []ConfigItem `yaml:"items"`
}

func NewConfig(path string) (*Config, error) {
	var config Config

	fd, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer fd.Close()

	b, err := ioutil.ReadAll(fd)
	if err != nil {
		return nil, err
	}

	err = yaml.Unmarshal(b, &config)
	if err != nil {
		return nil, err
	}

	// range for will create copy of the item
	for i := 0; i < len(config.Items); i++ {
		err = config.Items[i].parseSignalString()
		if err != nil {
			return nil, err
		}
	}

	return &config, nil
}
