/*
  trader  strategy
*/

package strategy

import (
	"common"
	"container/list"
	"db"
	"fmt"
	"logger"
	"trade_service"
)

type ExchangeOrder struct {
	Exchange string
	Amount   float64
	Price    float64
}

type SumExchangeOrder struct {
	Amount        float64
	Price         float64
	ExchangeOrder map[string]*ExchangeOrder //key is exchange
}

func mergeDepth(exchange, depth_type string, marketOrders []common.MarketOrder, depthList *list.List) (err error) {
	size := len(marketOrders)

	for i := 0; i < size; i++ {
		marketOrder := marketOrders[i]

		e := depthList.Front()
		for nil != e {
			if e.Value.(*SumExchangeOrder).Price == marketOrder.Price {
				//combine to the depth list item, just needd update the list item, so just break in the end.
				sumExchangeOrder := e.Value.(*SumExchangeOrder)

				if sumExchangeOrder.ExchangeOrder == nil {
					//cannot arrive here, just in case
					logger.Errorln("mergeDepth exception.")
					sumExchangeOrder.ExchangeOrder = make(map[string]*ExchangeOrder)
				}

				if sumExchangeOrder.ExchangeOrder[exchange] == nil {
					sumExchangeOrder.ExchangeOrder[exchange] = new(ExchangeOrder)
				}

				sumExchangeOrder.ExchangeOrder[exchange].Exchange = exchange
				sumExchangeOrder.ExchangeOrder[exchange].Price = marketOrder.Price
				sumExchangeOrder.ExchangeOrder[exchange].Amount = marketOrder.Amount
				sumExchangeOrder.Amount += marketOrder.Amount
				sumExchangeOrder.Price = marketOrder.Price

				break
			} else if (depth_type == "asks" && marketOrder.Price < e.Value.(*SumExchangeOrder).Price) ||
				(depth_type == "bids" && marketOrder.Price > e.Value.(*SumExchangeOrder).Price) {

				sumExchangeOrder := new(SumExchangeOrder)
				sumExchangeOrder.ExchangeOrder = make(map[string]*ExchangeOrder)
				sumExchangeOrder.ExchangeOrder[exchange] = new(ExchangeOrder)
				sumExchangeOrder.ExchangeOrder[exchange].Exchange = exchange
				sumExchangeOrder.ExchangeOrder[exchange].Price = marketOrder.Price
				sumExchangeOrder.ExchangeOrder[exchange].Amount = marketOrder.Amount
				sumExchangeOrder.Amount += marketOrder.Amount
				sumExchangeOrder.Price = marketOrder.Price

				depthList.InsertBefore(sumExchangeOrder, e)

				break
			}
			e = e.Next()
		}

		//the biggest,put @v on the back of the list
		if nil == e {
			sumExchangeOrder := new(SumExchangeOrder)
			sumExchangeOrder.ExchangeOrder = make(map[string]*ExchangeOrder)
			sumExchangeOrder.ExchangeOrder[exchange] = new(ExchangeOrder)
			sumExchangeOrder.ExchangeOrder[exchange].Exchange = exchange
			sumExchangeOrder.ExchangeOrder[exchange].Price = marketOrder.Price
			sumExchangeOrder.ExchangeOrder[exchange].Amount = marketOrder.Amount
			sumExchangeOrder.Amount += marketOrder.Amount
			sumExchangeOrder.Price = marketOrder.Price

			depthList.PushBack(sumExchangeOrder)
		}
	}

	return
}

func GetMergeDepth(markets []string) (asks, bids *list.List, newMarkets []string, err error) {
	asks = list.New()
	bids = list.New()

	errcount := 0
	for i := 0; i < len(markets); i++ {
		exchange := markets[i]
		orderbook, _err := db.GetDepth(exchange)
		if _err != nil {
			errcount++
			err = _err
			logger.Errorln("GetMergeDepth err:", exchange, err, len(markets), errcount)
			continue
		}

		// okcoin except special progress
		size := len(orderbook.Asks)
		if size > 0 && orderbook.Asks[common.DEPTH-1].Price < 0.000001 {
			logger.Errorln("GetMergeDepth exception orderbook:", exchange, orderbook)
			continue
		}

		newMarkets = append(newMarkets, exchange)
		mergeDepth(exchange, "asks", orderbook.Asks[:], asks)
		mergeDepth(exchange, "bids", orderbook.Bids[:], bids)
	}

	if errcount < len(markets) {
		err = nil
	} else {
		logger.Errorln("GetMergeDepth failed, restart", len(markets), errcount)
		return
		// os.Exit(-1) //triger supervisor restart to fix the broken net connection.
	}

	return
}

func PrintDepthList(depthList *list.List, markets []string) {
	return

	logger.Infoln("analyzeDepth depthList", depthList)
	depthCount := 0
	for e := depthList.Front(); e != nil; e = e.Next() {
		sumExchangeOrder := e.Value.(*SumExchangeOrder)
		depthCount++
		logger.Infoln(depthCount, sumExchangeOrder.Amount, sumExchangeOrder.Price)
		for i := 0; i < len(markets); i++ {
			exchange := markets[i]
			if sumExchangeOrder.ExchangeOrder[exchange] != nil {
				logger.Infoln(depthCount, sumExchangeOrder.ExchangeOrder[exchange])
			}
		}
	}

	logger.Infoln("depthCount:", depthCount)
}

func analyzeDepth(nbtc float64, depthList *list.List, markets []string) (tradeOrders map[string]*trade_service.TradeOrder) {
	sum_btc := 0.0

	tradeOrders = make(map[string]*trade_service.TradeOrder)
	for e := depthList.Front(); e != nil; e = e.Next() {
		sumExchangeOrder := e.Value.(*SumExchangeOrder)
		price := sumExchangeOrder.Price
		// amount := sumExchangeOrder.Amount

		for i := 0; i < len(markets); i++ {
			exchange := markets[i]
			//logger.Infoln(i, exchange)

			if sumExchangeOrder.ExchangeOrder[exchange] == nil {
				continue
			}

			if tradeOrders[exchange] == nil {
				tradeOrders[exchange] = new(trade_service.TradeOrder)
			}

			full_fill := false
			left_vol := sumExchangeOrder.ExchangeOrder[exchange].Amount
			if sum_btc+left_vol > nbtc {
				left_vol = nbtc - sum_btc
				full_fill = true
			}

			tradeOrders[exchange].EstimateBtc += left_vol
			tradeOrders[exchange].EstimatePrice = price
			tradeOrders[exchange].EstimateCny += left_vol * price
			sum_btc += left_vol

			if full_fill {
				return
			}
		}
	}

	return
}

func analyzeAskDepth(nbtc float64, markets []string) (tradeOrders map[string]*trade_service.TradeOrder, err error) {
	asks, _, newMarkets, err := GetMergeDepth(markets)
	if err != nil {
		logger.Errorln(err)
		return
	}

	PrintDepthList(asks, newMarkets)

	tradeOrders = analyzeDepth(nbtc, asks, newMarkets)

	return
}

func analyzeBidDepth(nbtc float64, markets []string) (tradeOrders map[string]*trade_service.TradeOrder, err error) {
	_, bids, newMarkets, err := GetMergeDepth(markets)
	if err != nil {
		logger.Errorln(err)
		return
	}

	PrintDepthList(bids, newMarkets)

	tradeOrders = analyzeDepth(nbtc, bids, newMarkets)

	return
}

func estimateOrder(siteOrder *db.SiteOrder, markets []string) (tradeOrders map[string]*trade_service.TradeOrder, tradeResult_ *trade_service.TradeException) {
	amount := siteOrder.Amount
	tradeType := siteOrder.TradeType

	logger.Infoln("estimateOrder markets:", tradeType, amount, markets)
	if len(markets) == 0 {
		tradeResult_ = trade_service.NewTradeException()
		tradeResult_.Reason = trade_service.EX_NO_USABLE_FUND

		logger.Errorln(tradeType.String(), tradeResult_)
		return
	}

	var err error
	if tradeType == trade_service.TradeType_BUY {
		tradeOrders, err = analyzeAskDepth(amount, markets)
	} else {
		tradeOrders, err = analyzeBidDepth(amount, markets)
	}

	if err != nil {
		logger.Errorln(err)
		tradeResult_ = trade_service.NewTradeException()
		tradeResult_.Reason = trade_service.EX_NO_USABLE_DEPTH
		return
	}

	estimate_btc := 0.0
	estimate_cny := 0.0
	estimate_price := 0.0
	for exchange, _ := range tradeOrders {
		tradeOrder := tradeOrders[exchange]
		tradeOrder.SiteOrderID = siteOrder.ID
		tradeOrder.Exchange = exchange
		tradeOrder.TradeType = tradeType

		if use_time_weighted_algorithm {
			tradeOrder.OrderStatus = trade_service.OrderStatus_TIME_WEIGHTED
		} else {
			tradeOrder.OrderStatus = trade_service.OrderStatus_READY
		}
		estimate_cny += tradeOrder.EstimateCny
		estimate_btc += tradeOrder.EstimateBtc

		tradePrice := fmt.Sprintf("%0.2f", tradeOrder.EstimatePrice)
		tradeBTC := fmt.Sprintf("%0.4f", tradeOrder.EstimateBtc)
		tradeCNY := fmt.Sprintf("%0.2f", tradeOrder.EstimateCny)

		logger.Infoln("estimateOrder trade:", exchange, tradePrice, tradeBTC, tradeCNY)
	}

	if len(tradeOrders) > 1 {
		toExchange := ""
		for exchange, _ := range tradeOrders {
			tradeOrder := tradeOrders[exchange]
			if tradeOrder.EstimateBtc > unit_min_amount {
				toExchange = exchange
				break
			}
		}

		if toExchange != "" {
			for exchange, _ := range tradeOrders {
				tradeOrder := tradeOrders[exchange]
				if tradeOrder.EstimateBtc <= unit_min_amount {
					if toExchange != exchange {
						logger.Debugln("combine trade begin", tradeType.String(), tradeOrders[toExchange].EstimatePrice, tradeOrders)
						logger.Debugln("to add trade", toExchange, tradeOrders[toExchange])
						logger.Debugln("to del trade", exchange, tradeOrder)
						tradeOrder.Exchange = toExchange
						tradeOrders[toExchange].EstimateBtc += tradeOrders[exchange].EstimateBtc
						if tradeType == trade_service.TradeType_BUY {
							if tradeOrders[toExchange].EstimatePrice < tradeOrders[exchange].EstimatePrice {
								tradeOrders[toExchange].EstimatePrice = tradeOrders[exchange].EstimatePrice
							}
						} else {
							if tradeOrders[toExchange].EstimatePrice > tradeOrders[exchange].EstimatePrice {
								tradeOrders[toExchange].EstimatePrice = tradeOrders[exchange].EstimatePrice
							}
						}

						tradeOrders[toExchange].EstimateCny += tradeOrders[exchange].EstimateCny

						delete(tradeOrders, exchange)

						logger.Debugln("combine trade end", tradeOrders[toExchange].EstimatePrice, tradeOrders)
					}
				}
			}
		}
	}

	if estimate_btc+0.01 < amount {
		tradeResult_ = trade_service.NewTradeException()
		tradeResult_.Reason = trade_service.EX_DEPTH_INSUFFICIENT

		logger.Errorln(tradeResult_, estimate_btc, amount)
		return
	}

	if estimate_btc > 0.01 {
		estimate_price = estimate_cny / estimate_btc
	} else {
		tradeResult_ = trade_service.NewTradeException()
		tradeResult_.Reason = trade_service.EX_INTERNAL_ERROR

		logger.Errorln(tradeResult_, estimate_btc, siteOrder)
		return
	}

	logger.Infoln("estimateOrder result:", amount, siteOrder.TradeType, estimate_cny, estimate_btc, estimate_price)

	siteOrder.EstimatePrice = estimate_price
	siteOrder.EstimateCny = estimate_cny
	siteOrder.EstimateBtc = estimate_btc

	if use_time_weighted_algorithm {
		siteOrder.OrderStatus = trade_service.OrderStatus_TIME_WEIGHTED
	} else {
		siteOrder.OrderStatus = trade_service.OrderStatus_READY
	}

	tradeResult_ = nil

	return
}
