// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package cmd

import (
	"context"
	"time"

	"github.com/minio/minio/internal/dsync"
	"github.com/minio/minio/internal/grid"
	"github.com/minio/minio/internal/logger"
)

// To abstract a node over network.
type lockRESTServer struct {
	ll *localLocker
}

// RefreshHandler - refresh the current lock
func (l *lockRESTServer) RefreshHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
	resp := lockRPCRefresh.NewResponse()
	refreshed, err := l.ll.Refresh(context.Background(), *args)
	if err != nil {
		return l.makeResp(resp, err)
	}
	if !refreshed {
		return l.makeResp(resp, errLockNotFound)
	}
	return l.makeResp(resp, err)
}

// LockHandler - Acquires a lock.
func (l *lockRESTServer) LockHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
	resp := lockRPCLock.NewResponse()
	success, err := l.ll.Lock(context.Background(), *args)
	if err == nil && !success {
		return l.makeResp(resp, errLockConflict)
	}
	return l.makeResp(resp, err)
}

// UnlockHandler - releases the acquired lock.
func (l *lockRESTServer) UnlockHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
	resp := lockRPCUnlock.NewResponse()
	_, err := l.ll.Unlock(context.Background(), *args)
	// Ignore the Unlock() "reply" return value because if err == nil, "reply" is always true
	// Consequently, if err != nil, reply is always false
	return l.makeResp(resp, err)
}

// RLockHandler - Acquires an RLock.
func (l *lockRESTServer) RLockHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
	resp := lockRPCRLock.NewResponse()
	success, err := l.ll.RLock(context.Background(), *args)
	if err == nil && !success {
		err = errLockConflict
	}
	return l.makeResp(resp, err)
}

// RUnlockHandler - releases the acquired read lock.
func (l *lockRESTServer) RUnlockHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
	resp := lockRPCRUnlock.NewResponse()

	// Ignore the RUnlock() "reply" return value because if err == nil, "reply" is always true.
	// Consequently, if err != nil, reply is always false
	_, err := l.ll.RUnlock(context.Background(), *args)
	return l.makeResp(resp, err)
}

// ForceUnlockHandler - query expired lock status.
func (l *lockRESTServer) ForceUnlockHandler(args *dsync.LockArgs) (*dsync.LockResp, *grid.RemoteErr) {
	resp := lockRPCForceUnlock.NewResponse()

	_, err := l.ll.ForceUnlock(context.Background(), *args)
	return l.makeResp(resp, err)
}

var (
	// Static lock handlers.
	// All have the same signature.
	lockRPCForceUnlock = newLockHandler(grid.HandlerLockForceUnlock)
	lockRPCRefresh     = newLockHandler(grid.HandlerLockRefresh)
	lockRPCLock        = newLockHandler(grid.HandlerLockLock)
	lockRPCUnlock      = newLockHandler(grid.HandlerLockUnlock)
	lockRPCRLock       = newLockHandler(grid.HandlerLockRLock)
	lockRPCRUnlock     = newLockHandler(grid.HandlerLockRUnlock)
)

func newLockHandler(h grid.HandlerID) *grid.SingleHandler[*dsync.LockArgs, *dsync.LockResp] {
	return grid.NewSingleHandler[*dsync.LockArgs, *dsync.LockResp](h, func() *dsync.LockArgs {
		return &dsync.LockArgs{}
	}, func() *dsync.LockResp {
		return &dsync.LockResp{}
	})
}

// registerLockRESTHandlers - register lock rest router.
func registerLockRESTHandlers() {
	lockServer := &lockRESTServer{
		ll: newLocker(),
	}

	logger.FatalIf(lockRPCForceUnlock.Register(globalGrid.Load(), lockServer.ForceUnlockHandler), "unable to register handler")
	logger.FatalIf(lockRPCRefresh.Register(globalGrid.Load(), lockServer.RefreshHandler), "unable to register handler")
	logger.FatalIf(lockRPCLock.Register(globalGrid.Load(), lockServer.LockHandler), "unable to register handler")
	logger.FatalIf(lockRPCUnlock.Register(globalGrid.Load(), lockServer.UnlockHandler), "unable to register handler")
	logger.FatalIf(lockRPCRLock.Register(globalGrid.Load(), lockServer.RLockHandler), "unable to register handler")
	logger.FatalIf(lockRPCRUnlock.Register(globalGrid.Load(), lockServer.RUnlockHandler), "unable to register handler")

	globalLockServer = lockServer.ll

	go lockMaintenance(GlobalContext)
}

func (l *lockRESTServer) makeResp(dst *dsync.LockResp, err error) (*dsync.LockResp, *grid.RemoteErr) {
	*dst = dsync.LockResp{Code: dsync.RespOK}
	switch err {
	case nil:
	case errLockNotInitialized:
		dst.Code = dsync.RespLockNotInitialized
	case errLockConflict:
		dst.Code = dsync.RespLockConflict
	case errLockNotFound:
		dst.Code = dsync.RespLockNotFound
	default:
		dst.Code = dsync.RespErr
		dst.Err = err.Error()
	}
	return dst, nil
}

const (
	// Lock maintenance interval.
	lockMaintenanceInterval = 1 * time.Minute

	// Lock validity duration
	lockValidityDuration = 1 * time.Minute
)

// lockMaintenance loops over all locks and discards locks
// that have not been refreshed for some time.
func lockMaintenance(ctx context.Context) {
	if !globalIsDistErasure {
		return
	}

	// Initialize a new ticker with 1 minute between each ticks.
	lkTimer := time.NewTimer(lockMaintenanceInterval)
	// Stop the timer upon returning.
	defer lkTimer.Stop()

	for {
		// Verifies every minute for locks held more than 2 minutes.
		select {
		case <-ctx.Done():
			return
		case <-lkTimer.C:
			globalLockServer.expireOldLocks(lockValidityDuration)

			// Reset the timer for next cycle.
			lkTimer.Reset(lockMaintenanceInterval)
		}
	}
}
