package logrotate

import (
	"errors"
	"os"
	"path/filepath"
	"time"
)

type period string

const (
	// PeriodHourly rotates log every hour
	PeriodHourly period = "hourly"
	// PeriodDaily rotates log by every day
	PeriodDaily period = "daily"
	// PeriodWeekly rotates log by every week
	PeriodWeekly period = "weekly"
	// PeriodMonthly rotates log by every month
	PeriodMonthly period = "monthly"
)

var (
	DefaultArchiveTimeFormat = "2006-01-02_15:04:05.000"
	DefaultMaxArchives       = 100
	DefaultMaxArchiveDays    = 14
)

type Options struct {
	// File is the file to write logs to.
	// It uses <process name>.log in os.TempDir() if empty.
	File string `json:"file" toml:"file" yaml:"file"`

	// RotatePeriod is time period for rotate log.
	// It supports hourly, daily, weekly, monthly.
	RotatePeriod string `json:"rotate_period" toml:"rotate_period" yaml:"rotate_period"`

	// RotateSize is the maximum size of the log file before it gets rotated
	RotateSize string `json:"rotate_size" toml:"rotate_size" yaml:"rotate_size"`

	// MaxArchives is the maximum number of old log files to retain
	MaxArchives int `json:"max_archives" toml:"max_archives" yaml:"max_archives"`

	// MaxArchiveDays is the maximum number of days to archived files
	MaxArchiveDays int `json:"max_archive_days" toml:"max_archive_days" yaml:"max_archive_days"`

	// ArchiveTimeFormat is the format of the archived files
	ArchiveTimeFormat string `json:"archive_time_format" toml:"archive_time_format" yaml:"archive_time_format"`

	// Compress determines if the rotated log files should be compressed
	// using gzip. The default is not to perform compression.
	Compress bool `json:"compress" toml:"compress" yaml:"compress"`

	rotateSize         int64
	cron               string
	maxArchiveDuration time.Duration
}

func (o *Options) Apply() error {
	if o.RotateSize != "" {
		size, err := stringToBytes(o.RotateSize)
		if err != nil {
			return err
		}
		o.rotateSize = size
	}
	if o.RotatePeriod != "" {
		switch period(o.RotatePeriod) {
		case PeriodHourly:
			o.cron = "0 * * * *"
		case PeriodDaily:
			o.cron = "0 0 * * *"
		case PeriodWeekly:
			o.cron = "0 0 * * 0"
		case PeriodMonthly:
			o.cron = "0 0 1 * *"
		default:
			return errors.New("invalid rotate period")
		}
	}
	if o.RotateSize != "" || o.RotatePeriod != "" {
		if o.ArchiveTimeFormat == "" {
			o.ArchiveTimeFormat = DefaultArchiveTimeFormat
		}
		if o.MaxArchives <= 0 {
			o.MaxArchives = DefaultMaxArchives
		}
		if o.MaxArchiveDays <= 0 {
			o.MaxArchiveDays = DefaultMaxArchiveDays
		}
		o.maxArchiveDuration = time.Duration(int64(24*time.Hour) * int64(o.MaxArchiveDays))
	}
	if o.File == "" {
		name := filepath.Base(os.Args[0]) + ".log"
		o.File = filepath.Join(os.TempDir(), name)
	}
	return nil
}

type Option func(*Options)

func File(name string) Option {
	return func(opts *Options) {
		opts.File = name
	}
}

func RotatePeriod(p period) Option {
	return func(opts *Options) {
		opts.RotatePeriod = string(p)
	}
}

func RotateSize(size string) Option {
	return func(opts *Options) {
		opts.RotateSize = size
	}
}

func ArchiveTimeFormat(format string) Option {
	return func(opts *Options) {
		opts.ArchiveTimeFormat = format
	}
}

func MaxArchives(number int) Option {
	return func(opts *Options) {
		opts.MaxArchives = number
	}
}

func MaxArchiveDays(days int) Option {
	return func(opts *Options) {
		opts.MaxArchiveDays = days
	}
}

func Compress(compress bool) Option {
	return func(opts *Options) {
		opts.Compress = compress
	}
}