/*
 *
 * Copyright 2018 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// Package channelz defines internal APIs for enabling channelz service, entry
// registration/deletion, and accessing channelz data. It also defines channelz
// metric struct formats.
package channelz

import (
	"sync/atomic"
	"time"

	"google.golang.org/grpc/internal"
)

var (
	// IDGen is the global channelz entity ID generator.  It should not be used
	// outside this package except by tests.
	IDGen IDGenerator

	db *channelMap = newChannelMap()
	// EntriesPerPage defines the number of channelz entries to be shown on a web page.
	EntriesPerPage = 50
	curState       int32
)

// TurnOn turns on channelz data collection.
func TurnOn() {
	atomic.StoreInt32(&curState, 1)
}

func init() {
	internal.ChannelzTurnOffForTesting = func() {
		atomic.StoreInt32(&curState, 0)
	}
}

// IsOn returns whether channelz data collection is on.
func IsOn() bool {
	return atomic.LoadInt32(&curState) == 1
}

// GetTopChannels returns a slice of top channel's ChannelMetric, along with a
// boolean indicating whether there's more top channels to be queried for.
//
// The arg id specifies that only top channel with id at or above it will be
// included in the result. The returned slice is up to a length of the arg
// maxResults or EntriesPerPage if maxResults is zero, and is sorted in ascending
// id order.
func GetTopChannels(id int64, maxResults int) ([]*Channel, bool) {
	return db.getTopChannels(id, maxResults)
}

// GetServers returns a slice of server's ServerMetric, along with a
// boolean indicating whether there's more servers to be queried for.
//
// The arg id specifies that only server with id at or above it will be included
// in the result. The returned slice is up to a length of the arg maxResults or
// EntriesPerPage if maxResults is zero, and is sorted in ascending id order.
func GetServers(id int64, maxResults int) ([]*Server, bool) {
	return db.getServers(id, maxResults)
}

// GetServerSockets returns a slice of server's (identified by id) normal socket's
// SocketMetrics, along with a boolean indicating whether there's more sockets to
// be queried for.
//
// The arg startID specifies that only sockets with id at or above it will be
// included in the result. The returned slice is up to a length of the arg maxResults
// or EntriesPerPage if maxResults is zero, and is sorted in ascending id order.
func GetServerSockets(id int64, startID int64, maxResults int) ([]*Socket, bool) {
	return db.getServerSockets(id, startID, maxResults)
}

// GetChannel returns the Channel for the channel (identified by id).
func GetChannel(id int64) *Channel {
	return db.getChannel(id)
}

// GetSubChannel returns the SubChannel for the subchannel (identified by id).
func GetSubChannel(id int64) *SubChannel {
	return db.getSubChannel(id)
}

// GetSocket returns the Socket for the socket (identified by id).
func GetSocket(id int64) *Socket {
	return db.getSocket(id)
}

// GetServer returns the ServerMetric for the server (identified by id).
func GetServer(id int64) *Server {
	return db.getServer(id)
}

// RegisterChannel registers the given channel c in the channelz database with
// target as its target and reference name, and adds it to the child list of its
// parent.  parent == nil means no parent.
//
// Returns a unique channelz identifier assigned to this channel.
//
// If channelz is not turned ON, the channelz database is not mutated.
func RegisterChannel(parent *Channel, target string) *Channel {
	id := IDGen.genID()

	if !IsOn() {
		return &Channel{ID: id}
	}

	isTopChannel := parent == nil

	cn := &Channel{
		ID:          id,
		RefName:     target,
		nestedChans: make(map[int64]string),
		subChans:    make(map[int64]string),
		Parent:      parent,
		trace:       &ChannelTrace{CreationTime: time.Now(), Events: make([]*traceEvent, 0, getMaxTraceEntry())},
	}
	cn.ChannelMetrics.Target.Store(&target)
	db.addChannel(id, cn, isTopChannel, cn.getParentID())
	return cn
}

// RegisterSubChannel registers the given subChannel c in the channelz database
// with ref as its reference name, and adds it to the child list of its parent
// (identified by pid).
//
// Returns a unique channelz identifier assigned to this subChannel.
//
// If channelz is not turned ON, the channelz database is not mutated.
func RegisterSubChannel(parent *Channel, ref string) *SubChannel {
	id := IDGen.genID()
	sc := &SubChannel{
		ID:      id,
		RefName: ref,
		parent:  parent,
	}

	if !IsOn() {
		return sc
	}

	sc.sockets = make(map[int64]string)
	sc.trace = &ChannelTrace{CreationTime: time.Now(), Events: make([]*traceEvent, 0, getMaxTraceEntry())}
	db.addSubChannel(id, sc, parent.ID)
	return sc
}

// RegisterServer registers the given server s in channelz database. It returns
// the unique channelz tracking id assigned to this server.
//
// If channelz is not turned ON, the channelz database is not mutated.
func RegisterServer(ref string) *Server {
	id := IDGen.genID()
	if !IsOn() {
		return &Server{ID: id}
	}

	svr := &Server{
		RefName:       ref,
		sockets:       make(map[int64]string),
		listenSockets: make(map[int64]string),
		ID:            id,
	}
	db.addServer(id, svr)
	return svr
}

// RegisterSocket registers the given normal socket s in channelz database
// with ref as its reference name, and adds it to the child list of its parent
// (identified by skt.Parent, which must be set). It returns the unique channelz
// tracking id assigned to this normal socket.
//
// If channelz is not turned ON, the channelz database is not mutated.
func RegisterSocket(skt *Socket) *Socket {
	skt.ID = IDGen.genID()
	if IsOn() {
		db.addSocket(skt)
	}
	return skt
}

// RemoveEntry removes an entry with unique channelz tracking id to be id from
// channelz database.
//
// If channelz is not turned ON, this function is a no-op.
func RemoveEntry(id int64) {
	if !IsOn() {
		return
	}
	db.removeEntry(id)
}

// IDGenerator is an incrementing atomic that tracks IDs for channelz entities.
type IDGenerator struct {
	id int64
}

// Reset resets the generated ID back to zero.  Should only be used at
// initialization or by tests sensitive to the ID number.
func (i *IDGenerator) Reset() {
	atomic.StoreInt64(&i.id, 0)
}

func (i *IDGenerator) genID() int64 {
	return atomic.AddInt64(&i.id, 1)
}

// Identifier is an opaque channelz identifier used to expose channelz symbols
// outside of grpc.  Currently only implemented by Channel since no other
// types require exposure outside grpc.
type Identifier interface {
	Entity
	channelzIdentifier()
}
