/*
NAME
  Config.go

DESCRIPTION
  See Readme.md

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

LICENSE
  Config.go is Copyright (C) 2017-2018 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 revid

import (
	"errors"

	"bitbucket.org/ausocean/utils/logger"
)

// Config provides parameters relevant to a revid instance. A new config must
// be passed to the constructor.
type Config struct {
	LogLevel int8

	Input         uint8
	InputCodec    uint8
	Outputs       []uint8
	RtmpMethod    uint8
	Packetization uint8

	// Quantize specifies whether the input to
	// revid will have constant or variable
	// bitrate.
	Quantize bool

	// FlipHorizonatla and FlipVertical specify
	// whether video frames should be flipped.
	FlipHorizontal bool
	FlipVertical   bool

	FramesPerClip      uint
	RtmpUrl            string
	Bitrate            uint
	OutputPath         string
	InputPath          string
	Height             uint
	Width              uint
	FrameRate          uint
	HttpAddress        string
	Quantization       uint
	IntraRefreshPeriod uint
	RtpAddress         string
	Logger             Logger
	SendRetry          bool
	BurstPeriod        uint
	Rotation           uint
	Brightness         uint
	Saturation         int
	Exposure           string
	AutoWhiteBalance   string
}

// Possible modes for raspivid --exposure parameter.
var ExposureModes = [...]string{
	"auto",
	"night",
	"nightpreview",
	"backlight",
	"spotlight",
	"sports",
	"snow",
	"beach",
	"verylong",
	"fixedfps",
	"antishake",
	"fireworks",
}

// Possible modes for raspivid --awb parameter.
var AutoWhiteBalanceModes = [...]string{
	"off",
	"auto",
	"sun",
	"cloud",
	"shade",
	"tungsten",
	"fluorescent",
	"incandescent",
	"flash",
	"horizon",
}

// Enums for config struct
const (
	NothingDefined = iota
	Raspivid
	V4L
	H264Codec
	File
	Http
	H264
	Mjpeg
	None
	Mpegts
	Ffmpeg
	Flv
	LibRtmp
	QuantizationOn
	QuantizationOff
	Yes
	No
	Rtmp
	FfmpegRtmp
	Udp
	MpegtsRtp
	Rtp
)

// Default config settings
const (
	defaultInput              = Raspivid
	defaultOutput             = Http
	defaultPacketization      = Flv
	defaultFrameRate          = 25
	defaultWidth              = 1280
	defaultHeight             = 720
	defaultIntraRefreshPeriod = 100
	defaultTimeout            = 0
	defaultQuantization       = 40
	defaultBitrate            = 400000
	defaultQuantizationMode   = QuantizationOff
	defaultFramesPerClip      = 1
	httpFramesPerClip         = 560
	defaultInputCodec         = H264
	defaultVerbosity          = No // FIXME(kortschak): This makes no sense whatsoever. No is currently 15.
	defaultRtpAddr            = "localhost:6970"
	defaultBurstPeriod        = 10 // Seconds
	defaultRotation           = 0  // Degrees
	defaultBrightness         = 50
	defaultExposure           = "auto"
	defaultAutoWhiteBalance   = "auto"
)

// Validate checks for any errors in the config fields and defaults settings
// if particular parameters have not been defined.
func (c *Config) Validate(r *Revid) error {
	switch c.LogLevel {
	case Yes:
	case No:
	case NothingDefined:
		c.LogLevel = defaultVerbosity
		c.Logger.Log(logger.Info, pkg+"no LogLevel mode defined, defaulting",
			"LogLevel", defaultVerbosity)
	default:
		return errors.New("bad LogLevel defined in config")
	}

	switch c.Input {
	case Raspivid, V4L, File:
	case NothingDefined:
		c.Logger.Log(logger.Info, pkg+"no input type defined, defaulting", "input",
			defaultInput)
		c.Input = defaultInput
	default:
		return errors.New("bad input type defined in config")
	}

	switch c.InputCodec {
	case H264:
		// FIXME(kortschak): This is not really what we want.
		// Configuration really needs to be rethought here.
		if c.Quantize && c.Quantization == 0 {
			c.Quantization = defaultQuantization
		}

		if (c.Bitrate > 0 && c.Quantize) || (c.Bitrate == 0 && !c.Quantize) {
			return errors.New("bad bitrate and quantization combination for H264 input")
		}

	case Mjpeg:
		if c.Quantization > 0 || c.Bitrate == 0 {
			return errors.New("bad bitrate or quantization for mjpeg input")
		}

	case NothingDefined:
		c.Logger.Log(logger.Info, pkg+"no input codec defined, defaulting",
			"inputCodec", defaultInputCodec)
		c.InputCodec = defaultInputCodec
		c.Logger.Log(logger.Info, pkg+"defaulting quantization", "quantization",
			defaultQuantization)
		c.Quantization = defaultQuantization

	default:
		return errors.New("bad input codec defined in config")
	}

	for i, o := range c.Outputs {
		switch o {
		case File:
		case Udp:
		case Rtmp, FfmpegRtmp:
			if c.RtmpUrl == "" {
				c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP")
				c.Outputs[i] = Http
				// FIXME(kortschak): Does this want the same line as below?
				//  c.FramesPerClip = httpFramesPerClip
				break
			}
			c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for rtmp out",
				"framesPerClip", defaultFramesPerClip)
			c.FramesPerClip = defaultFramesPerClip
			c.Packetization = Flv
			c.SendRetry = true
		case NothingDefined:
			c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output",
				defaultOutput)
			c.Outputs[i] = defaultOutput
			c.Packetization = defaultPacketization
			fallthrough
		case Http, Rtp:
			c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for http out",
				"framesPerClip", httpFramesPerClip)
			c.FramesPerClip = httpFramesPerClip
			c.Packetization = Mpegts
		default:
			return errors.New("bad output type defined in config")
		}
	}

	if c.BurstPeriod == 0 {
		c.Logger.Log(logger.Info, pkg+"no burst period defined, defaulting", "burstPeriod", defaultBurstPeriod)
		c.BurstPeriod = defaultBurstPeriod
	}

	if c.FramesPerClip < 1 {
		c.Logger.Log(logger.Info, pkg+"no FramesPerClip defined, defaulting",
			"framesPerClip", defaultFramesPerClip)
		c.FramesPerClip = defaultFramesPerClip
	}

	if c.Rotation > 359 {
		c.Logger.Log(logger.Warning, pkg+"bad rotate angle, defaulting", "angle", defaultRotation)
		c.Rotation = defaultRotation
	}

	if c.Width == 0 {
		c.Logger.Log(logger.Info, pkg+"no width defined, defaulting", "width", defaultWidth)
		c.Width = defaultWidth
	}

	if c.Height == 0 {
		c.Logger.Log(logger.Info, pkg+"no height defined, defaulting", "height", defaultHeight)
		c.Height = defaultHeight
	}

	if c.FrameRate == 0 {
		c.Logger.Log(logger.Info, pkg+"no frame rate defined, defaulting", "fps", defaultFrameRate)
		c.FrameRate = defaultFrameRate
	}

	if c.Bitrate == 0 {
		c.Logger.Log(logger.Info, pkg+"no bitrate defined, defaulting", "bitrate", defaultBitrate)
		c.Bitrate = defaultBitrate
	}

	if c.IntraRefreshPeriod == 0 {
		c.Logger.Log(logger.Info, pkg+"no intra refresh defined, defaulting", "intraRefresh", defaultIntraRefreshPeriod)
		c.IntraRefreshPeriod = defaultIntraRefreshPeriod
	}

	if c.Quantization == 0 {
		c.Logger.Log(logger.Info, pkg+"no quantization defined, defaulting", "quantization", defaultQuantization)
		c.Quantization = defaultQuantization
	} else if c.Quantization > 51 {
		return errors.New("quantisation is over threshold")
	}

	if c.RtpAddress == "" {
		c.RtpAddress = defaultRtpAddr
	}

	switch {
	case c.Brightness == 0:
		c.Logger.Log(logger.Info, pkg+"brightness undefined, defaulting", "brightness", defaultBrightness)
		c.Brightness = defaultBrightness
	case c.Brightness < 0 || c.Brightness > 100:
		return errors.New(pkg + "bad brightness defined in config")
	}

	if c.Saturation < -100 || c.Saturation > 100 {
		return errors.New(pkg + "bad saturation setting in config")
	}

	switch {
	case c.Exposure == "":
		c.Logger.Log(logger.Info, pkg+"exposure undefined, defaulting", "exposure", defaultExposure)
		c.Exposure = defaultExposure
	case !stringInSlice(c.Exposure, ExposureModes[:]):
		return errors.New(pkg + "bad exposure setting in config")
	}

	switch {
	case c.AutoWhiteBalance == "":
		c.Logger.Log(logger.Info, pkg+"auto white balance undefined, defaulting", "autoWhiteBalance", defaultAutoWhiteBalance)
		c.AutoWhiteBalance = defaultAutoWhiteBalance
	case !stringInSlice(c.AutoWhiteBalance, AutoWhiteBalanceModes[:]):
		return errors.New(pkg + "bad auto white balance setting in config")
	}

	return nil
}

// stringInSlice returns true if want is in slice.
func stringInSlice(want string, slice []string) bool {
	for _, s := range slice {
		if s == want {
			return true
		}
	}
	return false
}