package utils

import (
	"context"
	"sync"
	"sync/atomic"
)

type SegmentStatus int

const (
	SegmentStatusNormal  SegmentStatus = 0
	SegmentStatusOver    SegmentStatus = 1
	SegmentStatusLoading SegmentStatus = 2
)

type SegmentIdGenerator struct {
	segment     *Segment
	nextSegment *Segment

	ss SegmentService
	Config

	locker sync.Mutex
}

type Config struct {
	step int32
}

func NewConfig(step int32) Config {
	return Config{
		step: step,
	}
}

func NewIdGenerator(config Config, service SegmentService) *SegmentIdGenerator {
	g := &SegmentIdGenerator{
		Config: config,
		ss:     service,
	}

	err := g.loadCurrent(context.TODO())
	if err != nil {
		panic(err)
	}
	return g
}

func (p *SegmentIdGenerator) Next(ctx context.Context) (int32, error) {
	var next int32 = 0
	for next == 0 {
		id, status := p.segment.Next()
		switch status {
		case SegmentStatusOver:
			err := p.loadCurrent(ctx)
			if err != nil {
				return next, err
			}
		case SegmentStatusLoading:
			err := p.loadNext(ctx)
			if err != nil {
				return next, err
			}
			fallthrough
		case SegmentStatusNormal:
			next = id
		}
	}
	return next, nil
}

func (p *SegmentIdGenerator) loadNext(ctx context.Context) error {
	if p.nextSegment == nil {
		defer p.locker.Unlock()
		p.locker.Lock()

		if p.nextSegment == nil {
			s, err := p.ss.GetSegment(ctx, p.step)
			if err == nil {
				p.nextSegment = s
			} else {
				return err
			}
			return nil
		}
	}
	return nil
}

func (p *SegmentIdGenerator) loadCurrent(ctx context.Context) error {
	// 号段为空或已使用完
	if p.segment == nil || p.segment.Status() == SegmentStatusOver {
		defer p.locker.Unlock()
		p.locker.Lock()
		// 二次检查
		if p.segment == nil || p.segment.Status() == SegmentStatusOver {
			// 备用号段还有
			if p.nextSegment != nil {
				p.segment, p.nextSegment = p.nextSegment, nil
			} else {
				if s, err := p.ss.GetSegment(ctx, p.step); err == nil {
					p.segment = s
				} else {
					return err
				}
				return nil
			}
		}
		return nil
	}
	return nil
}

type Segment struct {
	currentId int32
	startId   int32
	maxId     int32
	step      int32
	status    SegmentStatus
}

func (i *Segment) Next() (int32, SegmentStatus) {
	if i.status == SegmentStatusOver {
		return -1, i.status
	}
	ret := atomic.AddInt32(&i.currentId, 1)
	if ret > i.maxId {
		i.status = SegmentStatusOver
		return -1, i.status
	}
	threshold := i.startId + i.step*8/10
	if ret >= threshold {
		i.status = SegmentStatusLoading
		return ret, i.status
	}
	return ret, i.status
}

func (i *Segment) Status() SegmentStatus {
	return i.status
}

func (i *Segment) CurrentId() int32 {
	return i.currentId
}

func (i *Segment) StartId() int32 {
	return i.startId
}

func (i *Segment) MaxId() int32 {
	return i.maxId
}

func (i *Segment) Step() int32 {
	return i.step
}

type SegmentService interface {
	GetSegment(ctx context.Context, step int32) (*Segment, error)
}

func NewSegment(startId, step int32) *Segment {
	currentId := startId

	return &Segment{
		currentId: currentId,
		startId:   startId,
		maxId:     startId + step,
		step:      step,
		status:    SegmentStatusNormal,
	}
}
