// Copyright 2016-2018, Pulumi Corporation.
//
// 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 deploytest

import (
	"fmt"

	"github.com/blang/semver"
	uuid "github.com/satori/go.uuid"

	"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
	"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
	"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
	"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
	"github.com/pulumi/pulumi/sdk/v2/go/common/workspace"
)

type Provider struct {
	Name    string
	Package tokens.Package
	Version semver.Version

	Config     resource.PropertyMap
	configured bool

	GetSchemaF func(version int) ([]byte, error)

	CheckConfigF func(urn resource.URN, olds,
		news resource.PropertyMap, allowUnknowns bool) (resource.PropertyMap, []plugin.CheckFailure, error)
	DiffConfigF func(urn resource.URN, olds, news resource.PropertyMap,
		ignoreChanges []string) (plugin.DiffResult, error)
	ConfigureF func(news resource.PropertyMap) error

	CheckF func(urn resource.URN,
		olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error)
	DiffF func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
		ignoreChanges []string) (plugin.DiffResult, error)
	CreateF func(urn resource.URN,
		inputs resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error)
	UpdateF func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
		timeout float64, ignoreChanges []string) (resource.PropertyMap, resource.Status, error)
	DeleteF func(urn resource.URN, id resource.ID, olds resource.PropertyMap, timeout float64) (resource.Status, error)

	ReadF func(urn resource.URN, id resource.ID,
		inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error)
	InvokeF func(tok tokens.ModuleMember,
		inputs resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error)

	CancelF func() error
}

func (prov *Provider) SignalCancellation() error {
	if prov.CancelF == nil {
		return nil
	}
	return prov.CancelF()
}

func (prov *Provider) Close() error {
	return nil
}

func (prov *Provider) Pkg() tokens.Package {
	return prov.Package
}

func (prov *Provider) GetPluginInfo() (workspace.PluginInfo, error) {
	return workspace.PluginInfo{
		Name:    prov.Name,
		Version: &prov.Version,
	}, nil
}

func (prov *Provider) GetSchema(version int) ([]byte, error) {
	if prov.GetSchemaF == nil {
		return []byte("{}"), nil
	}
	return prov.GetSchemaF(version)
}

func (prov *Provider) CheckConfig(urn resource.URN, olds,
	news resource.PropertyMap, allowUnknowns bool) (resource.PropertyMap, []plugin.CheckFailure, error) {
	if prov.CheckConfigF == nil {
		return news, nil, nil
	}
	return prov.CheckConfigF(urn, olds, news, allowUnknowns)
}
func (prov *Provider) DiffConfig(urn resource.URN, olds, news resource.PropertyMap, _ bool,
	ignoreChanges []string) (plugin.DiffResult, error) {
	if prov.DiffConfigF == nil {
		return plugin.DiffResult{}, nil
	}
	return prov.DiffConfigF(urn, olds, news, ignoreChanges)
}
func (prov *Provider) Configure(inputs resource.PropertyMap) error {
	contract.Assert(!prov.configured)
	prov.configured = true

	if prov.ConfigureF == nil {
		prov.Config = inputs
		return nil
	}
	return prov.ConfigureF(inputs)
}

func (prov *Provider) Check(urn resource.URN,
	olds, news resource.PropertyMap, _ bool) (resource.PropertyMap, []plugin.CheckFailure, error) {
	if prov.CheckF == nil {
		return news, nil, nil
	}
	return prov.CheckF(urn, olds, news)
}
func (prov *Provider) Create(urn resource.URN, props resource.PropertyMap, timeout float64) (resource.ID,
	resource.PropertyMap, resource.Status, error) {
	if prov.CreateF == nil {
		return resource.ID(uuid.NewV4().String()), resource.PropertyMap{}, resource.StatusOK, nil
	}
	return prov.CreateF(urn, props, timeout)
}
func (prov *Provider) Diff(urn resource.URN, id resource.ID,
	olds resource.PropertyMap, news resource.PropertyMap, _ bool, ignoreChanges []string) (plugin.DiffResult, error) {
	if prov.DiffF == nil {
		return plugin.DiffResult{}, nil
	}
	return prov.DiffF(urn, id, olds, news, ignoreChanges)
}
func (prov *Provider) Update(urn resource.URN, id resource.ID, olds resource.PropertyMap, news resource.PropertyMap,
	timeout float64, ignoreChanges []string) (resource.PropertyMap, resource.Status, error) {
	if prov.UpdateF == nil {
		return news, resource.StatusOK, nil
	}
	return prov.UpdateF(urn, id, olds, news, timeout, ignoreChanges)
}
func (prov *Provider) Delete(urn resource.URN,
	id resource.ID, props resource.PropertyMap, timeout float64) (resource.Status, error) {
	if prov.DeleteF == nil {
		return resource.StatusOK, nil
	}
	return prov.DeleteF(urn, id, props, timeout)
}

func (prov *Provider) Read(urn resource.URN, id resource.ID,
	inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
	if prov.ReadF == nil {
		return plugin.ReadResult{
			Outputs: resource.PropertyMap{},
			Inputs:  resource.PropertyMap{},
		}, resource.StatusUnknown, nil
	}
	return prov.ReadF(urn, id, inputs, state)
}
func (prov *Provider) Invoke(tok tokens.ModuleMember,
	args resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
	if prov.InvokeF == nil {
		return resource.PropertyMap{}, nil, nil
	}
	return prov.InvokeF(tok, args)
}

func (prov *Provider) StreamInvoke(
	tok tokens.ModuleMember, args resource.PropertyMap,
	onNext func(resource.PropertyMap) error) ([]plugin.CheckFailure, error) {

	return nil, fmt.Errorf("not implemented")
}
