package tracker

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"

	"github.com/anacrolix/missinggo/httptoo"

	"github.com/anacrolix/torrent/bencode"
	"github.com/anacrolix/torrent/util"
)

type httpResponse struct {
	FailureReason string      `bencode:"failure reason"`
	Interval      int32       `bencode:"interval"`
	TrackerId     string      `bencode:"tracker id"`
	Complete      int32       `bencode:"complete"`
	Incomplete    int32       `bencode:"incomplete"`
	Peers         interface{} `bencode:"peers"`
}

func (r *httpResponse) UnmarshalPeers() (ret []Peer, err error) {
	switch v := r.Peers.(type) {
	case string:
		var cps []util.CompactPeer
		cps, err = util.UnmarshalIPv4CompactPeers([]byte(v))
		if err != nil {
			return
		}
		ret = make([]Peer, 0, len(cps))
		for _, cp := range cps {
			ret = append(ret, Peer{
				IP:   cp.IP[:],
				Port: int(cp.Port),
			})
		}
		return
	case []interface{}:
		for _, i := range v {
			var p Peer
			p.fromDictInterface(i.(map[string]interface{}))
			ret = append(ret, p)
		}
		return
	default:
		err = fmt.Errorf("unsupported peers value type: %T", r.Peers)
		return
	}
}

func setAnnounceParams(_url *url.URL, ar *AnnounceRequest) {
	q := _url.Query()

	q.Set("info_hash", string(ar.InfoHash[:]))
	q.Set("peer_id", string(ar.PeerId[:]))
	q.Set("port", fmt.Sprintf("%d", ar.Port))
	q.Set("uploaded", strconv.FormatInt(ar.Uploaded, 10))
	q.Set("downloaded", strconv.FormatInt(ar.Downloaded, 10))
	q.Set("left", strconv.FormatUint(ar.Left, 10))
	if ar.Event != None {
		q.Set("event", ar.Event.String())
	}
	// http://stackoverflow.com/questions/17418004/why-does-tracker-server-not-understand-my-request-bittorrent-protocol
	q.Set("compact", "1")
	// According to https://wiki.vuze.com/w/Message_Stream_Encryption.
	q.Set("supportcrypto", "1")

	_url.RawQuery = q.Encode()
}

func announceHTTP(ar *AnnounceRequest, _url *url.URL, host string) (ret AnnounceResponse, err error) {
	_url = httptoo.CopyURL(_url)
	setAnnounceParams(_url, ar)
	req, err := http.NewRequest("GET", _url.String(), nil)
	req.Host = host
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	var buf bytes.Buffer
	io.Copy(&buf, resp.Body)
	if resp.StatusCode != 200 {
		err = fmt.Errorf("response from tracker: %s: %s", resp.Status, buf.String())
		return
	}
	var trackerResponse httpResponse
	err = bencode.Unmarshal(buf.Bytes(), &trackerResponse)
	if err != nil {
		err = fmt.Errorf("error decoding %q: %s", buf.Bytes(), err)
		return
	}
	if trackerResponse.FailureReason != "" {
		err = errors.New(trackerResponse.FailureReason)
		return
	}
	ret.Interval = trackerResponse.Interval
	ret.Leechers = trackerResponse.Incomplete
	ret.Seeders = trackerResponse.Complete
	ret.Peers, err = trackerResponse.UnmarshalPeers()
	return
}
