{{define "wrapper"}}
package {{.Name}}

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/thrasher-corp/gocryptotrader/common"
	"github.com/thrasher-corp/gocryptotrader/config"
	"github.com/thrasher-corp/gocryptotrader/currency"
	exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
	"github.com/thrasher-corp/gocryptotrader/exchanges/account"
	"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
	"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
	"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
	"github.com/thrasher-corp/gocryptotrader/exchanges/order"
	"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
	"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
	"github.com/thrasher-corp/gocryptotrader/exchanges/request"
	"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
	"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
	"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
	"github.com/thrasher-corp/gocryptotrader/log"
	"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)

// GetDefaultConfig returns a default exchange config
func ({{.Variable}} *{{.CapitalName}}) GetDefaultConfig(ctx context.Context) (*config.Exchange, error) {
	{{.Variable}}.SetDefaults()
	exchCfg := new(config.Exchange)
	exchCfg.Name = {{.Variable}}.Name
	exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout
	exchCfg.BaseCurrencies = {{.Variable}}.BaseCurrencies

	{{.Variable}}.SetupDefaults(exchCfg)

	if {{.Variable}}.Features.Supports.RESTCapabilities.AutoPairUpdates {
		err := {{.Variable}}.UpdateTradablePairs(ctx, true)
		if err != nil {
			return nil, err
		}
	}
	return exchCfg, nil
}

// SetDefaults sets the basic defaults for {{.CapitalName}}
func ({{.Variable}} *{{.CapitalName}}) SetDefaults() {
	{{.Variable}}.Name = "{{.CapitalName}}"
	{{.Variable}}.Enabled = true
	{{.Variable}}.Verbose = true
	{{.Variable}}.API.CredentialsValidator.RequiresKey = true
	{{.Variable}}.API.CredentialsValidator.RequiresSecret = true

	// If using only one pair format for request and configuration, across all 
	// supported asset types either SPOT and FUTURES etc. You can use the 
	// example below:

	// Request format denotes what the pair as a string will be, when you send
	// a request to an exchange.
	requestFmt := &currency.PairFormat{/*Set pair request formatting details here for e.g.*/ Uppercase: true, Delimiter: ":"}
	// Config format denotes what the pair as a string will be, when saved to 
	// the config.json file.
	configFmt := &currency.PairFormat{/*Set pair request formatting details here*/}
	err := {{.Variable}}.SetGlobalPairsManager(requestFmt, configFmt, /*multiple assets can be set here using the asset package ie asset.Spot*/)
	if err != nil {
		log.Errorln(log.ExchangeSys, err)
	}

	// If assets require multiple differences in formatting for request and 
	// configuration, another exchange method can be be used e.g. futures 
	// contracts require a dash as a delimiter rather than an underscore. You 
	// can use this example below:

	fmt1 := currency.PairStore{
		RequestFormat: &currency.PairFormat{Uppercase: true},
		ConfigFormat:  &currency.PairFormat{Uppercase: true},
	}

	fmt2 := currency.PairStore{
		RequestFormat: &currency.PairFormat{Uppercase: true},
		ConfigFormat:  &currency.PairFormat{Uppercase: true, Delimiter: ":"},
	}

	err = {{.Variable}}.StoreAssetPairFormat(asset.Spot, fmt1)
	if err != nil {
		log.Errorln(log.ExchangeSys, err)
	}
	err = {{.Variable}}.StoreAssetPairFormat(asset.Margin, fmt2)
	if err != nil {
		log.Errorln(log.ExchangeSys, err)
	}

	// Fill out the capabilities/features that the exchange supports
	{{.Variable}}.Features = exchange.Features{
		Supports: exchange.FeaturesSupported{
			{{ if .REST }} REST: true, {{ end }}
			{{ if .WS }} Websocket: true, {{ end }}
			RESTCapabilities: protocol.Features{
				TickerFetching: true,
				OrderbookFetching: true,
			},
			WebsocketCapabilities: protocol.Features{
				TickerFetching: true,
				OrderbookFetching: true,
			},
			WithdrawPermissions: exchange.AutoWithdrawCrypto | 
				exchange.AutoWithdrawFiat,
		},
		Enabled: exchange.FeaturesEnabled{
			AutoPairUpdates: true,
		},
	}
	// NOTE: SET THE EXCHANGES RATE LIMIT HERE
	{{.Variable}}.Requester, err = request.New({{.Variable}}.Name,
		common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
		if err != nil {
		log.Errorln(log.ExchangeSys, err)
	}

	// NOTE: SET THE URLs HERE
	{{.Variable}}.API.Endpoints = {{.Variable}}.NewEndpoints()
	{{.Variable}}.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{
		exchange.RestSpot:  {{.Name}}APIURL,
		// exchange.WebsocketSpot: {{.Name}}WSAPIURL,
	})
	{{.Variable}}.Websocket = stream.New()
	{{.Variable}}.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
	{{.Variable}}.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
	{{.Variable}}.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}

// Setup takes in the supplied exchange configuration details and sets params
func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.Exchange) error {
	err := exch.Validate()
	if err != nil {
		return err
	}
	if !exch.Enabled {
		{{.Variable}}.SetEnabled(false)
		return nil
	}
	err = {{.Variable}}.SetupDefaults(exch)
	if err != nil {
		return err
	}

	/*
	wsRunningEndpoint, err := {{.Variable}}.API.Endpoints.GetURL(exchange.WebsocketSpot)
	if err != nil {
		return err
	}

	// If websocket is supported, please fill out the following

	err = {{.Variable}}.Websocket.Setup(
		&stream.WebsocketSetup{
			ExchangeConfig:  exch,
			DefaultURL:      {{.Name}}WSAPIURL,
			RunningURL:      wsRunningEndpoint,
			Connector:       {{.Variable}}.WsConnect,
			Subscriber:      {{.Variable}}.Subscribe,
			UnSubscriber:    {{.Variable}}.Unsubscribe,
			Features:        &{{.Variable}}.Features.Supports.WebsocketCapabilities,
		})
	if err != nil {
		return err
	}

	{{.Variable}}.WebsocketConn = &stream.WebsocketConnection{
		ExchangeName:         {{.Variable}}.Name,
		URL:                  {{.Variable}}.Websocket.GetWebsocketURL(),
		ProxyURL:             {{.Variable}}.Websocket.GetProxyAddress(),
		Verbose:              {{.Variable}}.Verbose,
		ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
		ResponseMaxLimit:     exch.WebsocketResponseMaxLimit,
	}
	*/
	return nil
}

// Start starts the {{.CapitalName}} go routine
func ({{.Variable}} *{{.CapitalName}}) Start(ctx context.Context, wg *sync.WaitGroup) error {
	if wg == nil {
		return fmt.Errorf("%T %w", wg, common.ErrNilPointer)
	}
	wg.Add(1)
	go func() {
		{{.Variable}}.Run(ctx)
		wg.Done()
	}()
	return nil
}

// Run implements the {{.CapitalName}} wrapper
func ({{.Variable}} *{{.CapitalName}}) Run(ctx context.Context) {
	if {{.Variable}}.Verbose {
	{{ if .WS }} log.Debugf(log.ExchangeSys,
			"%s Websocket: %s.",
			{{.Variable}}.Name,
			common.IsEnabled({{.Variable}}.Websocket.IsEnabled())) {{ end }}
		{{.Variable}}.PrintEnabledPairs()
	}

	if !{{.Variable}}.GetEnabledFeatures().AutoPairUpdates {
		return
	}

	err := {{.Variable}}.UpdateTradablePairs(ctx, false)
	if err != nil {
		log.Errorf(log.ExchangeSys,
			"%s failed to update tradable pairs. Err: %s",
			{{.Variable}}.Name,
			err)
	}
}

// FetchTradablePairs returns a list of the exchanges tradable pairs
func ({{.Variable}} *{{.CapitalName}}) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.Pairs, error) {
	// Implement fetching the exchange available pairs if supported
	return nil, nil
}

// UpdateTradablePairs updates the exchanges available pairs and stores
// them in the exchanges config
func ({{.Variable}} *{{.CapitalName}}) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error {
	assetTypes := {{.Variable}}.GetAssetTypes(false)
	for x := range assetTypes {
		pairs, err := {{.Variable}}.FetchTradablePairs(ctx, assetTypes[x])
		if err != nil {
			return err
		}

		err = {{.Variable}}.UpdatePairs(pairs, assetTypes[x], false, forceUpdate)
		if err != nil {
			return err
		}
	}
	return nil
}


// UpdateTicker updates and returns the ticker for a currency pair
func ({{.Variable}} *{{.CapitalName}}) UpdateTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
		// NOTE: EXAMPLE FOR GETTING TICKER PRICE
	/*
	tickerPrice := new(ticker.Price)
	tick, err := {{.Variable}}.GetTicker(p.String())
	if err != nil {
		return tickerPrice, err
	}
	tickerPrice = &ticker.Price{
		High:    tick.High,
		Low:     tick.Low,
		Bid:     tick.Bid,
		Ask:     tick.Ask,
		Open:    tick.Open,
		Close:   tick.Close,
		Pair:    p,
	}
	err = ticker.ProcessTicker({{.Variable}}.Name, tickerPrice, assetType)
	if err != nil {
		return tickerPrice, err
	}
	*/
	return ticker.GetTicker({{.Variable}}.Name, p, assetType)
}

// UpdateTickers updates all currency pairs of a given asset type
func ({{.Variable}} *{{.CapitalName}}) UpdateTickers(ctx context.Context, assetType asset.Item) error {
		// NOTE: EXAMPLE FOR GETTING TICKER PRICE
	/*
	tick, err := {{.Variable}}.GetTickers()
	if err != nil {
		return err
	}
    for y := range tick {
        cp, err := currency.NewPairFromString(tick[y].Symbol)
        if err != nil {
            return err
        }
        err = ticker.ProcessTicker(&ticker.Price{
            Last:         tick[y].LastPrice,
            High:         tick[y].HighPrice,
            Low:          tick[y].LowPrice,
            Bid:          tick[y].BidPrice,
            Ask:          tick[y].AskPrice,
            Volume:       tick[y].Volume,
            QuoteVolume:  tick[y].QuoteVolume,
            Open:         tick[y].OpenPrice,
            Close:        tick[y].PrevClosePrice,
            Pair:         cp,
            ExchangeName: b.Name,
            AssetType:    assetType,
        })
        if err != nil {
            return err
        }
    }
	*/
    return nil
}

// FetchTicker returns the ticker for a currency pair
func ({{.Variable}} *{{.CapitalName}}) FetchTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
	tickerNew, err := ticker.GetTicker({{.Variable}}.Name, p, assetType)
	if err != nil {
		return {{.Variable}}.UpdateTicker(ctx, p, assetType)
	}
	return tickerNew, nil
}

// FetchOrderbook returns orderbook base on the currency pair
func ({{.Variable}} *{{.CapitalName}}) FetchOrderbook(ctx context.Context, pair currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
	ob, err := orderbook.Get({{.Variable}}.Name, pair, assetType)
	if err != nil {
		return {{.Variable}}.UpdateOrderbook(ctx, pair, assetType)
	}
	return ob, nil
}

// UpdateOrderbook updates and returns the orderbook for a currency pair
func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(ctx context.Context, pair currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
	book := &orderbook.Base{
		Exchange: {{.Variable}}.Name, 
		Pair: pair, 
		Asset: assetType,
		VerifyOrderbook: {{.Variable}}.CanVerifyOrderbook,
	}
	
	// NOTE: UPDATE ORDERBOOK EXAMPLE
	/* 
	orderbookNew, err := {{.Variable}}.GetOrderBook(exchange.FormatExchangeCurrency({{.Variable}}.Name, p).String(), 1000)
	if err != nil {
		return book, err
	}

	book.Bids = make([]orderbook.Item, len(orderbookNew.Bids))
	for x := range orderbookNew.Bids {
		book.Bids[x] = orderbook.Item{
			Amount: orderbookNew.Bids[x].Quantity, 
			Price: orderbookNew.Bids[x].Price,
		}
	}

	book.Asks = make([]orderbook.Item, len(orderbookNew.Asks))
	for x := range orderbookNew.Asks {
		book.Asks[x] = orderbook.Item{
			Amount: orderBookNew.Asks[x].Quantity,
			Price: orderBookNew.Asks[x].Price,
		}
	}
	*/

	err := book.Process()
	if err != nil {
		return book, err
	}

	return orderbook.Get({{.Variable}}.Name, pair, assetType)
}

// UpdateAccountInfo retrieves balances for all enabled currencies
func ({{.Variable}} *{{.CapitalName}}) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
	// If fetching requires more than one asset type please set 
	// HasAssetTypeAccountSegregation to true in RESTCapabilities above. 
	return account.Holdings{}, common.ErrNotYetImplemented
}

// FetchAccountInfo retrieves balances for all enabled currencies
func ({{.Variable}} *{{.CapitalName}}) FetchAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
	// Example implementation below:
	// 	creds, err := {{.Variable}}.GetCredentials(ctx)
	// 	if err != nil {
	// 		return account.Holdings{}, err
	// 	}
	// 	acc, err := account.GetHoldings({{.Variable}}.Name, creds, assetType)
	// 	if err != nil {
	// 		return {{.Variable}}.UpdateAccountInfo(ctx, assetType)
	// 	}
	// 	return acc, nil
	return account.Holdings{}, common.ErrNotYetImplemented
}

// GetFundingHistory returns funding history, deposits and
// withdrawals
func ({{.Variable}} *{{.CapitalName}}) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, error) {
	return nil, common.ErrNotYetImplemented
}

// GetWithdrawalsHistory returns previous withdrawals data
func ({{.Variable}} *{{.CapitalName}}) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
	return nil, common.ErrNotYetImplemented
}

// GetRecentTrades returns the most recent trades for a currency and asset
func ({{.Variable}} *{{.CapitalName}}) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
	return nil, common.ErrNotYetImplemented
}

// GetHistoricTrades returns historic trade data within the timeframe provided
func ({{.Variable}} *{{.CapitalName}}) GetHistoricTrades (ctx context.Context, p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
	return nil, common.ErrNotYetImplemented
}

// SubmitOrder submits a new order
func ({{.Variable}} *{{.CapitalName}}) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) {
	if err := s.Validate(); err != nil {
		return nil, err
	}
	// When an order has been submitted you can use this helpful constructor to
	// return. Please add any additional order details to the 
	// order.SubmitResponse if you think they are applicable.
	// resp, err := s.DeriveSubmitResponse( /*newOrderID*/)
	// if err != nil {
	// 	return nil, nil 
	// }
	// resp.Date = exampleTime // e.g. If this is supplied by the exchanges API.
	// return resp, nil 
	return nil, common.ErrNotYetImplemented
}

// ModifyOrder will allow of changing orderbook placement and limit to
// market conversion
func ({{.Variable}} *{{.CapitalName}}) ModifyOrder(ctx context.Context, action *order.Modify) (*order.ModifyResponse, error) {
	if err := action.Validate(); err != nil {
		return nil, err
	}
	// When an order has been modified you can use this helpful constructor to
	// return. Please add any additional order details to the
	// order.ModifyResponse if you think they are applicable.
	// resp, err := action.DeriveModifyResponse()
	// if err != nil {
	// 	return nil, nil
	// }
	// resp.OrderID = maybeANewOrderID // e.g. If this is supplied by the exchanges API.
	return nil, common.ErrNotYetImplemented
}

// CancelOrder cancels an order by its corresponding ID number
func ({{.Variable}} *{{.CapitalName}}) CancelOrder(ctx context.Context, ord *order.Cancel) error {
	// if err := ord.Validate(ord.StandardCancel()); err != nil {
	//	 return err
	// }
	return common.ErrNotYetImplemented
}

// CancelBatchOrders cancels orders by their corresponding ID numbers
func ({{.Variable}} *{{.CapitalName}}) CancelBatchOrders(ctx context.Context, orders []order.Cancel) (order.CancelBatchResponse, error) {
    return order.CancelBatchResponse{}, common.ErrNotYetImplemented
}

// CancelAllOrders cancels all orders associated with a currency pair
func ({{.Variable}} *{{.CapitalName}}) CancelAllOrders(ctx context.Context, orderCancellation *order.Cancel) (order.CancelAllResponse, error) {
	// if err := orderCancellation.Validate(); err != nil {
	//	 return err
	// }
	return order.CancelAllResponse{}, common.ErrNotYetImplemented
}

// GetOrderInfo returns order information based on order ID
func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetType asset.Item) (order.Detail, error) {
	return order.Detail{}, common.ErrNotYetImplemented
}

// GetDepositAddress returns a deposit address for a specified currency
func ({{.Variable}} *{{.CapitalName}}) GetDepositAddress(ctx context.Context, c currency.Code, accountID string, chain string) (*deposit.Address, error) {
	return nil, common.ErrNotYetImplemented
}

// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
// submitted
func ({{.Variable}} *{{.CapitalName}}) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
	// if err := withdrawRequest.Validate(); err != nil {
	//	return nil, err
	// }
	return nil, common.ErrNotYetImplemented
}

// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is
// submitted
func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
	// if err := withdrawRequest.Validate(); err != nil {
	//	return nil, err
	// }
	return nil, common.ErrNotYetImplemented
}

// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is
// submitted
func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFundsToInternationalBank(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
	// if err := withdrawRequest.Validate(); err != nil {
	//	return nil, err
	// }
	return nil, common.ErrNotYetImplemented
}

// GetActiveOrders retrieves any orders that are active/open
func ({{.Variable}} *{{.CapitalName}}) GetActiveOrders(ctx context.Context, getOrdersRequest *order.GetOrdersRequest) (order.FilteredOrders, error) {
	// if err := getOrdersRequest.Validate(); err != nil {
	//	return nil, err
	// }
	return nil, common.ErrNotYetImplemented
}

// GetOrderHistory retrieves account order information
// Can Limit response to specific order status
func ({{.Variable}} *{{.CapitalName}}) GetOrderHistory(ctx context.Context, getOrdersRequest *order.GetOrdersRequest) (order.FilteredOrders, error) {
	// if err := getOrdersRequest.Validate(); err != nil {
	//	return nil, err
	// }
	return nil, common.ErrNotYetImplemented
}

// GetFeeByType returns an estimate of fee based on the type of transaction
func ({{.Variable}} *{{.CapitalName}}) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) {
	return 0, common.ErrNotYetImplemented
}

// ValidateCredentials validates current credentials used for wrapper
func ({{.Variable}} *{{.CapitalName}}) ValidateCredentials(ctx context.Context, assetType asset.Item) error {
	_, err := {{.Variable}}.UpdateAccountInfo(ctx, assetType)
	return {{.Variable}}.CheckTransientError(err)
}

// GetHistoricCandles returns candles between a time period for a set time interval
func ({{.Variable}} *{{.CapitalName}}) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
	return nil, common.ErrNotYetImplemented
}

// GetHistoricCandlesExtended returns candles between a time period for a set time interval
func ({{.Variable}} *{{.CapitalName}}) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
	return nil, common.ErrNotYetImplemented
}

{{end}}
