package recorder

import (
	"context"
	"sync"

	"github.com/pingcap-inc/alioth/pkg/util"
	logpattern_go_proto "github.com/pingcap-inc/alioth/proto"
	"github.com/pingcap-inc/alioth/storage"
)

// LogPattern used to record the log pattern by code analysis
type LogPattern struct {
	sync.RWMutex
	logs  map[string]*logpattern_go_proto.LogPattern
	store *storage.LogPatternStore
}

func NewLogPattern(store *storage.LogPatternStore) *LogPattern {
	return &LogPattern{
		logs:  make(map[string]*logpattern_go_proto.LogPattern),
		store: store,
	}
}

// Record add one unknow log record
func (r *LogPattern) Record(lg *logpattern_go_proto.LogPattern) {
	pos := util.PosToStr(lg.GetPos())

	r.Lock()
	log := r.logs[pos]
	if log == nil {
		r.logs[pos] = util.CloneLogPattern(lg)
	}
	r.Unlock()
}

// Flush
// - flushs unknown log data from memory to store
// - output unknown log in log files
func (r *LogPattern) Flush(ctx context.Context) error {
	r.Lock()
	defer r.Unlock()

	if len(r.logs) == 0 {
		return nil
	}

	for _, lg := range r.logs {
		err := r.store.WriteLogPattern(ctx, lg)
		if err != nil {
			return err
		}
	}

	return nil
}

// Forach supports visit all unknown log dara and run process logic of caller
// Limit: caller makes sure doesn't change this unknow log data
func (r *LogPattern) Forach(ctx context.Context, fn func(ctx context.Context, record *logpattern_go_proto.LogPattern) error) error {
	r.RLock()
	defer r.RUnlock()
	for _, record := range r.logs {
		if err := fn(ctx, record); err != nil {
			return err
		}
	}
	return nil
}

// LoadPattern loads log pattern from store into memory
func (r *LogPattern) LoadPattern(ctx context.Context) error {
	r.Lock()
	defer r.Unlock()

	r.logs = make(map[string]*logpattern_go_proto.LogPattern)
	err := r.store.ScanLogPattern(ctx, func(key, value []byte) error {
		lp := &logpattern_go_proto.LogPattern{}
		err := lp.Unmarshal(value)
		if err != nil {
			return err
		}
		pos := util.PosToStr(lp.Pos)
		r.logs[pos] = lp
		return nil
	})
	if err != nil {
		return err
	}

	return nil
}
