package crontab

import (
	"errors"
	"fmt"
	"log"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/satori/go.uuid"

	"474420502.top/eson/structure/circular_linked"
	"github.com/Pallinder/go-randomdata"

	"github.com/davecgh/go-spew/spew"
)

// StatusType 设置状态的类型
type StatusType int

const (
	_ StatusType = iota
	// SExecuteOK 设置这个次成功或者失败的状态
	SExecuteOK
	// SExecuteSleep 设置下次Sleep时间, 只影响一次
	SExecuteSleep
	// SExecuteCrontab 设置改写Crontab, 覆盖以前的Crontab
	SExecuteCrontab
)

// Force 强制下次执行
type Force struct {
	sleep time.Duration
}

// NextTime 下次执行时间
func (force *Force) NextTime() time.Time {
	return time.Now().Add(force.sleep)
}

// Crontab 的string解析
type Crontab struct {
	crontab string

	uid   uuid.UUID
	force *Force

	min   []timePointer
	hour  []timePointer
	day   []timePointer
	month []timePointer
	week  []timePointer

	WillPlans []time.Time
	SkipPlans []time.Time

	YearPlan *trieYear

	interval     *clinked.CircularLinked
	lastStatus   bool
	isCalculated bool

	trueCount int
	failCount int
	nextTime  time.Time
}

// NewCrontab create 一个crontab
func NewCrontab(crontab string) *Crontab {
	cron := &Crontab{}
	cron.crontab = strings.TrimSpace(crontab)
	cron.FromString(cron.crontab)
	return cron
}

// UnmarshalYAML 添加序列化接口 Marshal没实现, 需要的时候可以自己添加
func (cron *Crontab) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var buf string
	err := unmarshal(&buf)
	if err != nil {
		return nil
	}

	if err := cron.FromString(buf); err != nil {
		return err
	}

	return nil
}

// SetStatus 设置上次状态 true false
func (cron *Crontab) SetStatus(statusType StatusType, statusValue ...interface{}) {

	switch statusType {
	case SExecuteOK:
		if cron.interval != nil {
			cron.lastStatus = statusValue[0].(bool)
		}
	case SExecuteSleep:
		force := new(Force)
		ivalue := statusValue[0]
		switch value := ivalue.(type) {
		case int:
			force.sleep = time.Duration(value) * time.Second
		case int64:
			force.sleep = time.Duration(value) * time.Second
		case time.Duration:
			force.sleep = value
		default:
			panic(errors.New("statusValue type is error, the type is" + reflect.TypeOf(ivalue).String()))
		}
		cron.force = force
	case SExecuteCrontab:
		crontab := statusValue[0].(string)
		cron.crontab = strings.TrimSpace(crontab)
		cron.FromString(cron.crontab)
	default:
		panic(errors.New("StatusType is unknown, the type " + reflect.TypeOf(statusType).String()))
	}

}

// // GetStatus 获取上次状态 true false
// func (cron *Crontab) GetStatus() (status bool) {
// 	return cron.lastStatus
// }

// TimeUp 是否时间快到, 时间间隔调用完TimeUp后必须调用NextTime, 为了精准控制时间
func (cron *Crontab) TimeUp() bool {

	if cron.interval != nil {
		return cron.intervalTimeUp()
	}

	return cron.linuxTimeUp()
}

// NextTime 返回下次任务的时间
func (cron *Crontab) NextTime() time.Time {

	if cron.force != nil {
		nt := cron.force.NextTime()
		cron.force = nil
		return nt
	}

	if cron.interval != nil {
		if !cron.isCalculated {
			now := time.Now()
			cron.intervalCalculateNextTime(now)
		}
		return cron.nextTime
	}

	if len(cron.WillPlans) > 0 {
		return cron.WillPlans[0]
	}

	return time.Now().Add(time.Second * 2)
}

func (cron *Crontab) String() string {
	return fmt.Sprintf("min:%s\nhour:%s\nday:%s\nmonth:%s\nweek:%s\n", spew.Sdump(cron.min), spew.Sdump(cron.hour), spew.Sdump(cron.day), spew.Sdump(cron.month), spew.Sdump(cron.week))
}

// FromString 解析crontab 的 表达式
func (cron *Crontab) FromString(crontab string) error {
	crontab = cron.crontab

	uid, err := uuid.NewV4()
	if err != nil {
		panic(err)
	}
	cron.uid = uid

	cron.interval = nil
	cron.min = nil
	cron.hour = nil
	cron.day = nil
	cron.month = nil
	cron.week = nil

	cron.WillPlans = nil
	cron.SkipPlans = nil

	cron.YearPlan = nil

	matches := regexp.MustCompile("[^ ]+").FindAllString(crontab, -1)
	mlen := len(matches)
	switch mlen {
	case 1:
		// "f1-2|5-10x5,f1|10m,10-15,f1"

		cron.lastStatus = true
		cron.isCalculated = true
		cron.nextTime = time.Now()

		cron.interval = clinked.NewCircularLinked()
		var intervalList []interface{}
		intervalList = parseIntervalString(matches[0])
		cron.interval.Append(intervalList...)

	case 5:
		cron.min = createTimePointer(matches[0], 0, 59, true)
		cron.hour = createTimePointer(matches[1], 0, 23, true)
		cron.day = createTimePointer(matches[2], 1, 31, false)
		cron.month = createTimePointer(matches[3], 1, 12, true)
		cron.week = createTimePointer(matches[4], 0, 6, true)

		cron.createYearPlan()
		cron.TimeUp()
	default:
		return errors.New("mathches len != want, check crontab string")
	}

	return nil
}

// createYearPlan 创建年度计划
func (cron *Crontab) createYearPlan() {
	cron.YearPlan = newTrieYear()
	cron.YearPlan.FromCrontab(cron)
}

func (cron *Crontab) linuxTimeUp() bool {
	now := time.Now()
	maxlen := 1000
	createlen := 500

	plen := len(cron.WillPlans)
	if plen <= createlen { // 如果当前生成的计划表少于 限制的500. 就生成新的计划表
		var lastplan time.Time
		if plen == 0 {
			lastplan = now
		} else {
			lastplan = cron.WillPlans[plen-1].Add(time.Minute)
		}
		if !cron.YearPlan.CheckYear() {
			cron.createYearPlan()
		}

		timeplans := cron.YearPlan.GetPlanTime(cron, lastplan, uint(maxlen-plen))
		cron.WillPlans = append(cron.WillPlans, timeplans...)
	}

	if len(cron.WillPlans) > 0 {
		istimeup := false
		for i := 0; i < maxlen; i++ {

			// 统计过了多少计划任务时间表 i - 1
			if now.Unix() >= cron.WillPlans[i].Unix() {
				istimeup = true
			} else {

				// 清除过时的计划任务时间表
				if istimeup {
					if i-1 > 0 {
						cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans[0:i-1]...)
						if len(cron.SkipPlans) >= maxlen+200 {
							cron.SkipPlans = cron.SkipPlans[200:]
						}
					}
					cron.WillPlans = cron.WillPlans[i:]
					return istimeup
				}

				return istimeup
			}
		}
		// 如果所有计划表都不符合, 全部放到忽略的计划表上, 这个表方便打印查看, 因为程序执行超时过了多少计划任务
		cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans...)
		cron.WillPlans = nil
		return istimeup
	}

	log.Panicln("error willplans range")
	return false
}

// IntervalCalculateNextTime  计算时间间隔的下次时间
func (cron *Crontab) intervalCalculateNextTime(now time.Time) {
	iv := cron.interval.Cursor().GetValue().(*hInterval)
	isecond := 0

	if iv.PlanFailCount.Size() == 0 && len(iv.PlanFail) == 0 {
		cron.lastStatus = true
	}

	if cron.lastStatus {

		cron.trueCount++
		cron.failCount = 0

		isecond = intervalPriorityListISecond(&iv.PlanTrueCount, cron.trueCount)
		if isecond == -1 {
			if len(iv.PlanTrue) > 0 {
				idx := randomdata.Number(len(iv.PlanTrue))
				lr := iv.PlanTrue[idx]
				isecond = randomdata.Number(lr.left, lr.right+1)

			} else {
				isecond = 0
			}

		}

		fmt.Println(time.Now().Format("01-02 15:04:05"), cron.uid.String(), "success:", cron.trueCount, " wait:", isecond)

	} else {

		cron.failCount++
		cron.trueCount = 0

		isecond = intervalPriorityListISecond(&iv.PlanFailCount, cron.failCount)
		if isecond == -1 {
			if len(iv.PlanFail) > 0 {
				idx := randomdata.Number(len(iv.PlanFail))
				lr := iv.PlanFail[idx]
				isecond = randomdata.Number(lr.left, lr.right+1)
			} else {
				isecond = 0
			}
		}

		fmt.Println(time.Now().Format("01-02 15:04:05"), cron.uid.String(), "fail:", cron.failCount, " wait:", isecond)
	}

	iv.Count--
	if iv.Count <= 0 {
		iv.reset()
		cron.interval.MoveNext()
	}

	cron.isCalculated = true
	cron.nextTime = now.Add(time.Duration(isecond) * time.Second)
}

func (cron *Crontab) intervalTimeUp() bool {

	if cron.isCalculated { // 需要调用nexttime()才能正常正常计算下次的时间
		now := time.Now()
		if now.Unix() >= cron.nextTime.Unix() {
			cron.isCalculated = false
			return true
		}
	}

	return false
}
