/*
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"
	"strconv"

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

// Config provides parameters relevant to a revid instance. A new config must
// be passed to the constructor.
type Config struct {
	Input            uint8
	InputCodec       uint8
	Output1          uint8
	Output2          uint8
	RtmpMethod       uint8
	Packetization    uint8
	QuantizationMode uint8
	LogLevel         int8

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

	FramesPerClip      int
	RtmpUrl            string
	Bitrate            string
	OutputFileName     string
	InputFileName      string
	Height             string
	Width              string
	FrameRate          string
	HttpAddress        string
	Quantization       string
	Timeout            string
	IntraRefreshPeriod string
	RtpAddress         string
	Logger             Logger
	SendRetry          bool
}

// Enums for config struct
const (
	NothingDefined = iota
	Raspivid
	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
	defaultVerticalFlip       = No
	defaultHorizontalFlip     = No
	httpFramesPerClip         = 560
	defaultInputCodec         = H264
	defaultVerbosity          = No
	defaultRtpAddr            = "localhost:6970"
)

// 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(smartlogger.Warning, pkg+"no LogLevel mode defined, defaulting",
			"LogLevel", defaultVerbosity)
	default:
		return errors.New("bad LogLevel defined in config")
	}

	switch c.QuantizationMode {
	case QuantizationOn:
	case QuantizationOff:
	case NothingDefined:
		c.Logger.Log(smartlogger.Warning, pkg+"no quantization mode defined, defaulting",
			"quantizationMode", QuantizationOff)
		c.QuantizationMode = QuantizationOff
	default:
		return errors.New("bad QuantizationMode defined in config")
	}

	switch c.Input {
	case Raspivid:
	case File:
	case NothingDefined:
		c.Logger.Log(smartlogger.Warning, 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:
		if c.Bitrate != "" && c.Quantization != "" {
			bitrate, err := strconv.Atoi(c.Bitrate)
			if err != nil {
				return errors.New("bitrate not an integer")
			}
			quantization, err := strconv.Atoi(c.Quantization)
			if err != nil {
				return errors.New("quantization not an integer")
			}
			if (bitrate > 0 && quantization > 0) || (bitrate == 0 && quantization == 0) {
				return errors.New("bad bitrate and quantization combination for H264 input")
			}
		}
	case Mjpeg:
		if c.Quantization != "" {
			quantization, err := strconv.Atoi(c.Quantization)
			if err != nil {
				return errors.New("quantization not an integer")
			}
			if quantization > 0 || c.Bitrate == "" {
				return errors.New("bad bitrate or quantization for mjpeg input")
			}
		}
	case NothingDefined:
		c.Logger.Log(smartlogger.Warning, pkg+"no input codec defined, defaulting",
			"inputCodec", defaultInputCodec)
		c.InputCodec = defaultInputCodec
		c.Logger.Log(smartlogger.Warning, pkg+"defaulting quantization", "quantization",
			defaultQuantization)
		c.Quantization = defaultQuantization
	default:
		return errors.New("bad input codec defined in config")
	}

	switch c.Output1 {
	case File:
	case Udp:
	case Rtmp, FfmpegRtmp:
		if c.RtmpUrl == "" {
			c.Logger.Log(smartlogger.Info, pkg+"no RTMP URL: falling back to HTTP")
			c.Output1 = Http
			break
		}
		c.Logger.Log(smartlogger.Info, pkg+"defaulting frames per clip for rtmp out",
			"framesPerClip", defaultFramesPerClip)
		c.FramesPerClip = defaultFramesPerClip
	case NothingDefined:
		c.Logger.Log(smartlogger.Warning, pkg+"no output defined, defaulting", "output",
			defaultOutput)
		c.Output1 = defaultOutput
		fallthrough
	case Http, Rtp:
		c.Logger.Log(smartlogger.Info, pkg+"defaulting frames per clip for http out",
			"framesPerClip", httpFramesPerClip)
		c.FramesPerClip = httpFramesPerClip
	default:
		return errors.New("bad output type defined in config")
	}

	switch c.Output2 {
	case File:
	case Rtp:
	case Udp:
	case Rtmp, FfmpegRtmp:
		if c.RtmpUrl == "" {
			c.Logger.Log(smartlogger.Info, pkg+"no RTMP URL: falling back to HTTP")
			c.Output2 = Http
			break
		}
	case NothingDefined:
	case Http:
	default:
		return errors.New("bad output2 type defined in config")
	}

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

	if c.Width == "" {
		c.Logger.Log(smartlogger.Warning, pkg+"no width defined, defaulting", "width",
			defaultWidth)
		c.Width = defaultWidth
	} else {
		if integer, err := strconv.Atoi(c.Width); integer < 0 || err != nil {
			return errors.New("width not unsigned integer")
		}
	}

	if c.Height == "" {
		c.Logger.Log(smartlogger.Warning, pkg+"no height defined, defaulting", "height",
			defaultHeight)
		c.Height = defaultHeight
	} else {
		if integer, err := strconv.Atoi(c.Height); integer < 0 || err != nil {
			return errors.New("height not unsigned integer")
		}
	}

	if c.FrameRate == "" {
		c.Logger.Log(smartlogger.Warning, pkg+"no frame rate defined, defaulting", "fps",
			defaultFrameRate)
		c.FrameRate = defaultFrameRate
	} else {
		if integer, err := strconv.Atoi(c.FrameRate); integer < 0 || err != nil {
			return errors.New("frame rate not unsigned integer")
		}
	}

	if c.Bitrate == "" {
		c.Logger.Log(smartlogger.Warning, pkg+"no bitrate defined, defaulting", "bitrate",
			defaultBitrate)
		c.Bitrate = defaultBitrate
	} else {
		if integer, err := strconv.Atoi(c.Bitrate); integer < 0 || err != nil {
			return errors.New("bitrate not unsigned integer")
		}
	}

	if c.Timeout == "" {
		c.Logger.Log(smartlogger.Warning, pkg+"no timeout defined, defaulting", "timeout", defaultTimeout)
		c.Timeout = defaultTimeout
	} else {
		if integer, err := strconv.Atoi(c.Timeout); integer < 0 || err != nil {
			return errors.New("timeout not unsigned integer")
		}
	}

	if c.IntraRefreshPeriod == "" {
		c.Logger.Log(smartlogger.Warning, pkg+"no intra refresh defined, defaulting", "intraRefresh",
			defaultIntraRefreshPeriod)
		c.IntraRefreshPeriod = defaultIntraRefreshPeriod
	} else {
		if integer, err := strconv.Atoi(c.IntraRefreshPeriod); integer < 0 || err != nil {
			return errors.New("intra refresh not unsigned integer")
		}
	}

	if c.Quantization == "" {
		c.Logger.Log(smartlogger.Warning, pkg+"no quantization defined, defaulting", "quantization",
			defaultQuantization)
		c.Quantization = defaultQuantization
	} else {
		if integer, err := strconv.Atoi(c.Quantization); integer < 0 || integer > 51 || err != nil {
			return errors.New("quantisation not unsigned integer or is over threshold")
		}
	}

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