// Copyright 2018 The gVisor 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.

//go:build race
// +build race

package tests

import (
	"context"
	"testing"

	"gvisor.dev/gvisor/pkg/state"
)

// faker calls itself whatever is in the name field.
type faker struct {
	Name   string
	Fields []string
}

func (f *faker) StateTypeName() string {
	return f.Name
}

func (f *faker) StateFields() []string {
	return f.Fields
}

// fakerWithSaverLoader has all it needs.
type fakerWithSaverLoader struct {
	faker
}

func (f *fakerWithSaverLoader) StateSave(m state.Sink) {}

func (f *fakerWithSaverLoader) StateLoad(_ context.Context, m state.Source) {}

// fakerOther calls itself .. uh, itself?
type fakerOther string

func (f *fakerOther) StateTypeName() string {
	return string(*f)
}

func (f *fakerOther) StateFields() []string {
	return nil
}

func newFakerOther(name string) *fakerOther {
	f := fakerOther(name)
	return &f
}

// fakerOtherBadFields returns non-nil fields.
type fakerOtherBadFields string

func (f *fakerOtherBadFields) StateTypeName() string {
	return string(*f)
}

func (f *fakerOtherBadFields) StateFields() []string {
	return []string{string(*f)}
}

func newFakerOtherBadFields(name string) *fakerOtherBadFields {
	f := fakerOtherBadFields(name)
	return &f
}

// fakerOtherSaverLoader implements SaverLoader methods.
type fakerOtherSaverLoader string

func (f *fakerOtherSaverLoader) StateTypeName() string {
	return string(*f)
}

func (f *fakerOtherSaverLoader) StateFields() []string {
	return nil
}

func (f *fakerOtherSaverLoader) StateSave(m state.Sink) {}

func (f *fakerOtherSaverLoader) StateLoad(_ context.Context, m state.Source) {}

func newFakerOtherSaverLoader(name string) *fakerOtherSaverLoader {
	f := fakerOtherSaverLoader(name)
	return &f
}

func TestRegisterPrimitives(t *testing.T) {
	for _, typeName := range []string{
		"int",
		"int8",
		"int16",
		"int32",
		"int64",
		"uint",
		"uintptr",
		"uint8",
		"uint16",
		"uint32",
		"uint64",
		"float32",
		"float64",
		"complex64",
		"complex128",
		"string",
	} {
		t.Run("struct/"+typeName, func(t *testing.T) {
			defer func() {
				if r := recover(); r == nil {
					t.Errorf("Registering type %q did not panic", typeName)
				}
			}()
			state.Register(&faker{
				Name: typeName,
			})
		})
		t.Run("other/"+typeName, func(t *testing.T) {
			defer func() {
				if r := recover(); r == nil {
					t.Errorf("Registering type %q did not panic", typeName)
				}
			}()
			state.Register(newFakerOther(typeName))
		})
	}
}

func TestRegisterBad(t *testing.T) {
	const (
		goodName    = "foo"
		firstField  = "a"
		secondField = "b"
	)
	for name, object := range map[string]state.Type{
		"non-struct-with-fields":                  newFakerOtherBadFields(goodName),
		"non-struct-with-saverloader":             newFakerOtherSaverLoader(goodName),
		"struct-without-saverloader":              &faker{Name: goodName},
		"non-struct-duplicate-with-struct":        newFakerOther((new(alreadyRegisteredStruct)).StateTypeName()),
		"non-struct-duplicate-with-non-struct":    newFakerOther((new(alreadyRegisteredOther)).StateTypeName()),
		"struct-duplicate-with-struct":            &fakerWithSaverLoader{faker{Name: (new(alreadyRegisteredStruct)).StateTypeName()}},
		"struct-duplicate-with-non-struct":        &fakerWithSaverLoader{faker{Name: (new(alreadyRegisteredOther)).StateTypeName()}},
		"struct-with-empty-field":                 &fakerWithSaverLoader{faker{Name: goodName, Fields: []string{""}}},
		"struct-with-empty-field-and-non-empty":   &fakerWithSaverLoader{faker{Name: goodName, Fields: []string{firstField, ""}}},
		"struct-with-duplicate-field":             &fakerWithSaverLoader{faker{Name: goodName, Fields: []string{firstField, firstField}}},
		"struct-with-duplicate-field-and-non-dup": &fakerWithSaverLoader{faker{Name: goodName, Fields: []string{firstField, secondField, firstField}}},
	} {
		t.Run(name, func(t *testing.T) {
			defer func() {
				if r := recover(); r == nil {
					t.Errorf("Registering object %#v did not panic", object)
				}
			}()
			state.Register(object)
		})

	}
}

func TestRegisterTypeOnlyStruct(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Errorf("Register did not panic")
		}
	}()
	state.Register((*typeOnlyEmptyStruct)(nil))
}
