// +build ignore

/*
DESCRIPTION
  generate_parameters.go uses a template to generate implementations for the
  Parameter interface.

LICENSE
  Copyright (C) 2020 the Australian Ocean Lab (AusOcean)

  It is free software: you can redistribute it and/or modify them
  under the terms of the GNU General Public License as published by the
  Free Software Foundation, either version 3 of the License, or (at your
  option) any later version.

  It is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 for more details.

  You should have received a copy of the GNU General Public License
  along with revid in gpl.txt.  If not, see http://www.gnu.org/licenses.
*/

package main

import (
	"bytes"
	"fmt"
	"go/format"
	"math"
	"os"
	"strings"
	"text/template"
)

// Filename to house Parameter interface implementations.
const fileName = "parameters.go"

// Param holds characteristics for describing Parameter interface implementations.
type Param struct {
	// N is the name of the struct implementing the Parameter interface.
	N string

	// R is the name of the receiver for the Type and Set methods. This is set to
	// the lowercase of the first letter of the Parameter implementation name, N.
	R string

	// BT is the base type of a Parameter implementation. For example, for a
	// Parameter implementation named Bitrate, i.e. type Bitrate uint, the base
	// type is uint.
	BT string

	// E are the enums of a type i.e. if there is a list of identifiers we wish
	// to recocgnise, they are specified here. For example, a Parameter implementation
	// named Output may take on a value of HTTP, RTMP, RTP or File, so E is set to
	// []string{"HTTP","RTMP","RTP","File"} and a const list of type Output will be
	// generated as a result:
	// const (
	// 		OutputHTTP Output = iota
	//		OutputRTMP
	//		OutputRTP
	//		OutputFile
	// )
	E []string

	// M, if defined, indicates a "multiple option type", which is to mean a slice
	// of another type that is defined. For example, if the base type, BT, is set
	// to a slice of a type "[]Output" (where Output is also defined in the params
	// list), then M would be set to "Output". The E field must be manually set to
	// be consistent with the Enums defined for the Output type.
	M string

	// If we wish an int, uint, or float64 value to be constrained to a particular
	// range then Min and Max are both set to indicate the inclusive Min and Max
	// possible values for a type. This will result in the generation of a range
	// check in the implementation's Set method.
	Min, Max int
}

// NB: Alphabetical order.
var params = []Param{
	{N: "AutoWhiteBalance", BT: "uint8", E: []string{"Off", "Auto", "Sun", "Cloud", "Shade", "Tungsten", "Fluorescent", "Incandescent", "Flash", "Horizon"}},
	{N: "BitDepth", BT: "uint"}, // TODO(Trek): bounds.
	{N: "Bitrate", BT: "uint", Min: 1000, Max: 10000000},
	{N: "Brightness", BT: "uint", Min: 0, Max: 100},
	{N: "BurstPeriod", BT: "time.Duration"},
	{N: "CBR", BT: "bool"},
	{N: "CameraChan", BT: "uint8", E: []string{"Channel1", "Channel2"}},
	{N: "CameraIP", BT: "string"},
	{N: "Channels", BT: "uint"}, // TODO(Trek): bounds.
	{N: "ClipDuration", BT: "time.Duration"},
	{N: "Codec", BT: "uint8", E: []string{"H264", "H265", "MJPEG", "PCM", "ADPCM"}},
	{N: "Exposure", BT: "uint8", E: []string{"Auto", "Night", "NightPreview", "BackLight", "SpotLight", "Sports", "Snow", "Beach", "VeryLong", "FixedFPS", "AntiShake", "Fireworks"}},
	{N: "FileFPS", BT: "uint", Min: 1, Max: 30},
	{N: "Filter", BT: "uint8", E: []string{"NoOp", "MOG", "VariableFPS", "KNN", "Difference", "Basic"}},
	{N: "FrameRate", BT: "uint", Min: 1, Max: 30},
	{N: "HTTPAddress", BT: "string"},
	{N: "Height", BT: "uint", Min: 360, Max: 1080},
	{N: "HorizontalFlip", BT: "bool"},
	{N: "Input", BT: "uint8", E: []string{"File", "Raspivid", "Webcam", "RTSP"}},
	{N: "InputPath", BT: "string"},
	{N: "Level", BT: "uint8", E: []string{"Debug", "Info", "Warning", "Error", "Fatal"}},
	{N: "MinFPS", BT: "uint", Min: 1, Max: 30},
	{N: "MinFrames", BT: "uint", Min: 0, Max: 1000},
	{N: "Mode", BT: "uint8", E: []string{"Normal", "Paused", "Burst", "Loop"}},
	{N: "MotionDownscaling", BT: "uint"}, // TODO(Scott): define bounds.
	{N: "MotionHistory", BT: "uint"},     // TODO(Scott/Ella): define bounds.
	{N: "MotionInterval", BT: "uint", Min: 0, Max: 30},
	{N: "MotionKernel", BT: "uint"},       // TODO(Scott/Ella): define bounds.
	{N: "MotionMinArea", BT: "float64"},   // TODO(Scott/Ella): define bounds.
	{N: "MotionPixels", BT: "uint"},       // TODO(Scott/Ella): define bounds.
	{N: "MotionThreshold", BT: "float64"}, // TODO(Scott/Ella): define bounds.
	{N: "Output", BT: "uint8", E: []string{"HTTP", "RTMP", "RTP", "File"}},
	{N: "OutputPath", BT: "string"},
	{N: "Outputs", BT: "[]Output", M: "Output", E: []string{"HTTP", "RTMP", "RTP", "File"}},
	{N: "PSITime", BT: "time.Duration"},
	{N: "Quantization", BT: "uint"},
	{N: "RBCapacity", BT: "uint", Min: 1000000, Max: 100000000},
	{N: "RBMaxElements", BT: "uint", Min: 0, Max: math.MaxUint32},
	{N: "RBWriteTimeout", BT: "time.Duration"},
	{N: "RTMPURL", BT: "string"},
	{N: "RTPAddress", BT: "string"},
	{N: "RecPeriod", BT: "float64"}, // TODO(Trek): bounds.
	{N: "Rotation", BT: "uint", Min: 0, Max: 359},
	{N: "SampleRate", BT: "uint"}, // TODO(Trek): bounds.
	{N: "Saturation", BT: "int", Min: -50, Max: 50},
	{N: "ShowWindows", BT: "bool"},
	{N: "VBRBitrate", BT: "uint", Min: 1, Max: 30},
	{N: "VBRQuality", BT: "uint8", E: []string{"Standard", "Fair", "Good", "Great", "Excellent"}},
	{N: "VerticalFlip", BT: "bool"},
	{N: "Width", BT: "uint", Min: 640, Max: 1920},
	{N: "WriteRate", BT: "float64"},
}

const fileHeader = `
/*
DESCRIPTION
  Code generated by "go run generate_parameters.go”; DO NOT EDIT.

  parameters.go contains implementations of the Parameter interface for all
  parameter types required by the configuration struct.

AUTHORS
  Saxon Nelson-Milton <saxon@ausocean.org>

LICENSE
  Copyright (C) 2020 the Australian Ocean Lab (AusOcean)

  It is free software: you can redistribute it and/or modify them
  under the terms of the GNU General Public License as published by the
  Free Software Foundation, either version 3 of the License, or (at your
  option) any later version.

  It is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  for more details.

  You should have received a copy of the GNU General Public License
  along with revid in gpl.txt.  If not, see http://www.gnu.org/licenses.
*/

// Package parameter is autogenerated code for config parameters - DO NOT EDIT.
package parameter

import (
  "fmt"
  "strconv"
  "time"
  "strings"
)

type Parameter interface {
  Type() string
  Set(val string) error
}
`

const paramTemplate = `
type {{.N}} {{.BT}}
{{if and .E (not .M)}}
const (
  {{$name := .N}}
  {{- range $i, $e := .E}}{{- if eq $i 0}}{{$name}}{{$e}} {{$name}} = iota{{else}}{{$name}}{{$e}}{{end}}
  {{end -}}
)
{{end -}}

{{- if .E}}
func ({{.R}} *{{.N}}) Type() string { return "enum:{{range $i, $e := .E}}{{if eq $i 0}}{{$e}}{{else}},{{$e}}{{end}}{{end}}"}
{{else}}
func ({{.R}} *{{.N}}) Type() string { return "{{.BT}}"}
{{end -}}

func ({{.R}} *{{.N}}) Set(val string) error {
  {{- if eq .BT "string"}}
  *{{.R}} = {{.N}}(val)
  {{else if eq .BT "bool"}}
  switch val {
  case "true":
    *{{.R}} = true
  case "false":
    *{{.R}} = false
  default:
    return fmt.Errorf("not boolean value: %s",val)
  }
  {{else if eq .BT "int"}}
  _v, err := strconv.Atoi(val)
  if err != nil {
    return fmt.Errorf("could not convert set string to int: %w",err)
  }
  {{if ne .Min .Max}}
  if _v < {{.Min}} || _v > {{.Max}} {
    return fmt.Errorf("invalid value %v",_v)
  }
  {{end}}
  *{{.R}} = {{.N}}(_v)
  {{else if eq .BT "uint"}}
  _v, err := strconv.Atoi(val)
  if err != nil {
    return fmt.Errorf("could not convert set string to int: %w",err)
  }
  {{if ne .Min .Max}}
  if _v < {{.Min}} || _v > {{.Max}} {
    return fmt.Errorf("invalid value %v",_v)
  }
  {{end}}
  *{{.R}} = {{.N}}(_v)
  {{else if eq .BT "float64"}}
  _v, err := strconv.ParseFloat(val,64)
  if err != nil {
    return fmt.Errorf("could not convert set string to float: %w",err)
  }
  {{if ne .Min .Max}}
  if _v < {{.Min}} || _v > {{.Max}} {
    return fmt.Errorf("invalid value %v",_v)
  }
  {{end}}
  *{{.R}} = {{.N}}(_v)
  {{else if eq .BT "time.Duration"}}
  _v, err := strconv.Atoi(val)
  if err != nil {
    return fmt.Errorf("could not convert set string to int: %w",err)
  }
  *{{.R}} = {{.N}}(time.Duration(_v)*time.Second)
  {{else if .M}}
  vals := strings.Split(val, ",")
  *{{.R}} = make({{.BT}}, len(vals))

  for i, v := range vals {
  switch v {
  {{- $receiver := .R}}
    {{- $m := .M}}
    {{range .E}}case "{{ . }}":
      (*{{$receiver}})[i] = {{$m}}{{ . }}
    {{end -}}
    default:
      return fmt.Errorf("unrecognised {{.N}}: %s",val)
    }
  }
  {{else}}
  switch val {
  {{- $receiver := .R}}
  {{- $name := .N}}
  {{range .E}}case "{{ . }}":
    *{{$receiver}} = {{$name}}{{ . }}
  {{end -}}
  default:
    return fmt.Errorf("unrecognised {{.N}}: %s",val)
  }
  {{end -}}

  return nil
}
`

func main() {
	f, err := os.Create(fileName)
	if err != nil {
		panic(fmt.Sprintf("error creating %s file: %v", fileName, err))
	}
	defer f.Close()

	var buf bytes.Buffer

	_, err = buf.Write([]byte(fileHeader))
	if err != nil {
		panic(fmt.Sprintf("error writing header: %v", err))
	}

	param := template.Must(template.New("param").Parse(paramTemplate))

	for _, p := range params {
		// Use first letter of parameter name as receiver.
		p.R = strings.ToLower(p.N[:1])
		err = param.Execute(&buf, p)
		if err != nil {
			panic(fmt.Sprintf("error executing template: %v", err))
		}
	}

	b, err := format.Source(buf.Bytes())
	if err != nil {
		f.Write(buf.Bytes()) // Useful to debug bad format.
		panic(fmt.Sprintf("error formatting: %v", err))
	}

	_, err = f.Write(b)
	if err != nil {
		panic(fmt.Sprintf("error writing %s file: %v", fileName, err))
	}
}