// Copyright 2015 Light Code Labs, LLC
//
// 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 caddytls

import "net/url"

// StorageConstructor is a function type that is used in the Config to
// instantiate a new Storage instance. This function can return a nil
// Storage even without an error.
type StorageConstructor func(caURL *url.URL) (Storage, error)

// SiteData contains persisted items pertaining to an individual site.
type SiteData struct {
	// Cert is the public cert byte array.
	Cert []byte
	// Key is the private key byte array.
	Key []byte
	// Meta is metadata about the site used by Caddy.
	Meta []byte
}

// UserData contains persisted items pertaining to a user.
type UserData struct {
	// Reg is the user registration byte array.
	Reg []byte
	// Key is the user key byte array.
	Key []byte
}

// Storage is an interface abstracting all storage used by Caddy's TLS
// subsystem. Implementations of this interface store both site and
// user data.
type Storage interface {
	// SiteExists returns true if this site exists in storage.
	// Site data is considered present when StoreSite has been called
	// successfully (without DeleteSite having been called, of course).
	SiteExists(domain string) (bool, error)

	// TryLock is called before Caddy attempts to obtain or renew a
	// certificate for a certain name and store it. From the perspective
	// of this method and its companion Unlock, the actions of
	// obtaining/renewing and then storing the certificate are atomic,
	// and both should occur within a lock. This prevents multiple
	// processes -- maybe distributed ones -- from stepping on each
	// other's space in the same shared storage, and from spamming
	// certificate providers with multiple, redundant requests.
	//
	// If a lock could be obtained, (nil, nil) is returned and you may
	// continue normally. If not (meaning another process is already
	// working on that name), a Waiter value will be returned upon
	// which you can Wait() until it is finished, and then return
	// when it unblocks. If waiting, do not unlock!
	//
	// To prevent deadlocks, all implementations (where this concern
	// is relevant) should put a reasonable expiration on the lock in
	// case Unlock is unable to be called due to some sort of storage
	// system failure or crash.
	TryLock(name string) (Waiter, error)

	// Unlock unlocks the mutex for name. Only callers of TryLock who
	// successfully obtained the lock (no Waiter value was returned)
	// should call this method, and it should be called only after
	// the obtain/renew and store are finished, even if there was
	// an error (or a timeout).
	Unlock(name string) error

	// LoadSite obtains the site data from storage for the given domain and
	// returns it. If data for the domain does not exist, an error value
	// of type ErrNotExist is returned. For multi-server storage, care
	// should be taken to make this load atomic to prevent race conditions
	// that happen with multiple data loads.
	LoadSite(domain string) (*SiteData, error)

	// StoreSite persists the given site data for the given domain in
	// storage. For multi-server storage, care should be taken to make this
	// call atomic to prevent half-written data on failure of an internal
	// intermediate storage step. Implementers can trust that at runtime
	// this function will only be invoked after LockRegister and before
	// UnlockRegister of the same domain.
	StoreSite(domain string, data *SiteData) error

	// DeleteSite deletes the site for the given domain from storage.
	// Multi-server implementations should attempt to make this atomic. If
	// the site does not exist, an error value of type ErrNotExist is returned.
	DeleteSite(domain string) error

	// LoadUser obtains user data from storage for the given email and
	// returns it. If data for the email does not exist, an error value
	// of type ErrNotExist is returned. Multi-server implementations
	// should take care to make this operation atomic for all loaded
	// data items.
	LoadUser(email string) (*UserData, error)

	// StoreUser persists the given user data for the given email in
	// storage. Multi-server implementations should take care to make this
	// operation atomic for all stored data items.
	StoreUser(email string, data *UserData) error

	// MostRecentUserEmail provides the most recently used email parameter
	// in StoreUser. The result is an empty string if there are no
	// persisted users in storage.
	MostRecentUserEmail() string
}

// ErrNotExist is returned by Storage implementations when
// a resource is not found. It is similar to os.ErrNotExist
// except this is a type, not a variable.
type ErrNotExist interface {
	error
}

// Waiter is a type that can block until a storage lock is released.
type Waiter interface {
	Wait()
}
