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

package tracers

import (
	"code.wuban.net.cn/cmpchain/ethtracer/evm"
	"code.wuban.net.cn/cmpchain/ethtracer/evm/evmtypes"
	"code.wuban.net.cn/cmpchain/ethtracer/evm/vm"
	"code.wuban.net.cn/cmpchain/ethtracer/params"
	"code.wuban.net.cn/cmpchain/ethtracer/tracers/logger"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	metatypes "github.com/CaduceusMetaverseProtocol/MetaTypes/types"
	"time"

	"github.com/CaduceusMetaverseProtocol/MetaTypes/common/hexutil"
)

const (
	// defaultTraceTimeout is the amount of time a single transaction can execute
	// by default before being forcefully aborted.
	defaultTraceTimeout = 5 * time.Second

	// defaultTraceReexec is the number of blocks the tracer is willing to go back
	// and reexecute to produce missing historical state necessary to run a specific
	// trace.
	defaultTraceReexec = uint64(128)

	// defaultTracechainMemLimit is the size of the triedb, at which traceChain
	// switches over and tries to use a disk-backed database instead of building
	// on top of memory.
	// For non-archive nodes, this limit _will_ be overblown, as disk-backed tries
	// will only be found every ~15K blocks or so.
	defaultTracechainMemLimit = metatypes.StorageSize(500 * 1024 * 1024)

	// maximumPendingTraceStates is the maximum number of states allowed waiting
	// for tracing. The creation of trace state will be paused if the unused
	// trace states exceed this limit.
	maximumPendingTraceStates = 128
)

var errTxNotFound = errors.New("transaction not found")

// StateReleaseFunc is used to deallocate resources held by constructing a
// historical state for tracing purposes.
type StateReleaseFunc func()

// TraceConfig holds extra parameters to trace functions.
type TraceConfig struct {
	*logger.Config
	Tracer  *string
	Timeout *string
	Reexec  *uint64
	// Config specific to given tracer. Note struct logger
	// config are historically embedded in main object.
	TracerConfig json.RawMessage
}

// TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
	TraceConfig
	StateOverrides *StateOverride
	BlockOverrides *BlockOverrides
	TxIndex        *hexutil.Uint
}

// StdTraceConfig holds extra parameters to standard-json trace functions.
type StdTraceConfig struct {
	logger.Config
	Reexec *uint64
	TxHash metatypes.Hash
}

// txTraceResult is the result of a single transaction trace.
type txTraceResult struct {
	TxHash metatypes.Hash `json:"txHash"`           // transaction hash
	Result interface{}    `json:"result,omitempty"` // Trace results produced by the tracer
	Error  string         `json:"error,omitempty"`  // Trace failure produced by the tracer
}

// blockTraceTask represents a single block trace task when an entire chain is
// being traced.
type blockTraceTask struct {
	statedb vm.StateDB       // Intermediate state prepped for tracing
	block   *evmtypes.Block  // Block to trace the transactions from
	release StateReleaseFunc // The function to release the held resource for this task
	results []*txTraceResult // Trace results produced by the task
}

// blockTraceResult represents the results of tracing a single block when an entire
// chain is being traced.
type blockTraceResult struct {
	Block  hexutil.Uint64   `json:"block"`  // Block number corresponding to this trace
	Hash   metatypes.Hash   `json:"hash"`   // Block hash corresponding to this trace
	Traces []*txTraceResult `json:"traces"` // Trace results produced by the task
}

// txTraceTask represents a single transaction trace task when an entire block
// is being traced.
type txTraceTask struct {
	statedb vm.StateDB // Intermediate state prepped for tracing
	index   int        // Transaction offset in the block
}

// DoTraceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will
// be tracer dependent.
// func DoTraceTx(ctx context.Context, chainConf *params.ChainConfig, message *evm.Message,
//
//	txctx *Context, vmctx vm.BlockContext, statedb globaldb.GlobalDB, configData []byte) (interface{}, error) {
func DoTraceTx(ctx context.Context, chainConf *params.ChainConfig, message *evm.Message,
	txctx *Context, vmctx vm.BlockContext, statedb vm.StateDB, precompiles vm.PrecompiledContracts, configData []byte) (interface{}, error) {
	var (
		tracer  *Tracer
		err     error
		timeout = defaultTraceTimeout
	)
	var config *TraceConfig
	if configData == nil {
		config = &TraceConfig{}
	} else {
		if err := json.Unmarshal(configData, &config); err != nil {
			return nil, fmt.Errorf("failed to unmarshal trace config: %w", err)
		}
	}
	// Default tracer is the struct logger
	if config.Tracer == nil {
		logger := logger.NewStructLogger(config.Config)
		tracer = &Tracer{
			Hooks:     logger.Hooks(),
			GetResult: logger.GetResult,
			Stop:      logger.Stop,
		}
	} else {
		tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig, chainConf)
		if err != nil {
			return nil, err
		}
	}
	tracingStateDB := NewHookedState(statedb, tracer.Hooks)
	vmenv := vm.NewEVM(vmctx, tracingStateDB, chainConf, vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
	if precompiles != nil {
		vmenv.SetPrecompiles(precompiles)
	}

	// Define a meaningful timeout of a single transaction trace
	if config.Timeout != nil {
		if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
			return nil, err
		}
	}
	deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
	go func() {
		<-deadlineCtx.Done()
		if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
			tracer.Stop(errors.New("execution timeout"))
			// Stop evm execution. Note cancellation is not necessarily immediate.
			vmenv.Cancel()
		}
	}()
	defer cancel()

	// Call Prepare to clear out the statedb access list
	statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)

	_, err = evm.ApplyMessage(vmenv, message, new(evm.GasPool).AddGas(message.GasLimit))
	if err != nil {
		return nil, fmt.Errorf("tracing failed: %w", err)
	}
	return tracer.GetResult()
}

// overrideConfig returns a copy of original with forks enabled by override enabled,
// along with a boolean that indicates whether the copy is canonical (equivalent to the original).
// Note: the Clique-part is _not_ deep copied
func overrideConfig(original *params.ChainConfig, override *params.ChainConfig) (*params.ChainConfig, bool) {
	copy := new(params.ChainConfig)
	*copy = *original
	canon := true

	// Apply forks (after Berlin) to the copy.
	if block := override.BerlinBlock; block != nil {
		copy.BerlinBlock = block
		canon = false
	}
	if block := override.LondonBlock; block != nil {
		copy.LondonBlock = block
		canon = false
	}
	if block := override.ArrowGlacierBlock; block != nil {
		copy.ArrowGlacierBlock = block
		canon = false
	}
	if block := override.GrayGlacierBlock; block != nil {
		copy.GrayGlacierBlock = block
		canon = false
	}
	if block := override.MergeNetsplitBlock; block != nil {
		copy.MergeNetsplitBlock = block
		canon = false
	}
	if timestamp := override.ShanghaiTime; timestamp != nil {
		copy.ShanghaiTime = timestamp
		canon = false
	}
	if timestamp := override.CancunTime; timestamp != nil {
		copy.CancunTime = timestamp
		canon = false
	}
	if timestamp := override.PragueTime; timestamp != nil {
		copy.PragueTime = timestamp
		canon = false
	}
	if timestamp := override.OsakaTime; timestamp != nil {
		copy.OsakaTime = timestamp
		canon = false
	}
	if timestamp := override.VerkleTime; timestamp != nil {
		copy.VerkleTime = timestamp
		canon = false
	}

	return copy, canon
}
