// Copyright 2016 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

package infohash

import (
	"github.com/chihaya/chihaya"
	"github.com/chihaya/chihaya/tracker"
)

func init() {
	tracker.RegisterAnnounceMiddleware("infohash_whitelist", whitelistAnnounceInfohash)
	tracker.RegisterScrapeMiddlewareConstructor("infohash_whitelist", whitelistScrapeInfohash)
}

// PrefixInfohash is the prefix to be used for infohashes.
const PrefixInfohash = "ih-"

// whitelistAnnounceInfohash provides a middleware that only allows announces
// for infohashes that are not stored in a StringStore
func whitelistAnnounceInfohash(next tracker.AnnounceHandler) tracker.AnnounceHandler {
	return func(cfg *chihaya.TrackerConfig, req *chihaya.AnnounceRequest, resp *chihaya.AnnounceResponse) (err error) {
		whitelisted, err := mustGetStore().HasString(PrefixInfohash + string(req.InfoHash[:]))

		if err != nil {
			return err
		} else if !whitelisted {
			return ErrBlockedInfohash
		}
		return next(cfg, req, resp)
	}
}

// whitelistScrapeInfohash provides a middleware constructor for a middleware
// that blocks or filters scrape requests based on the infohashes scraped.
//
// The middleware works in two modes: block and filter.
// The block mode blocks a scrape completely if any of the infohashes is
// disallowed.
// The filter mode filters any disallowed infohashes from the scrape,
// potentially leaving an empty scrape.
//
// ErrUnknownMode is returned if the Mode specified in the config is unknown.
func whitelistScrapeInfohash(c chihaya.MiddlewareConfig) (tracker.ScrapeMiddleware, error) {
	cfg, err := newConfig(c)
	if err != nil {
		return nil, err
	}

	switch cfg.Mode {
	case ModeFilter:
		return whitelistFilterScrape, nil
	case ModeBlock:
		return whitelistBlockScrape, nil
	default:
		panic("unknown mode")
	}
}

func whitelistFilterScrape(next tracker.ScrapeHandler) tracker.ScrapeHandler {
	return func(cfg *chihaya.TrackerConfig, req *chihaya.ScrapeRequest, resp *chihaya.ScrapeResponse) (err error) {
		whitelisted := false
		storage := mustGetStore()
		infohashes := req.InfoHashes

		for i, ih := range infohashes {
			whitelisted, err = storage.HasString(PrefixInfohash + string(ih[:]))

			if err != nil {
				return err
			} else if !whitelisted {
				req.InfoHashes[i] = req.InfoHashes[len(req.InfoHashes)-1]
				req.InfoHashes = req.InfoHashes[:len(req.InfoHashes)-1]
			}
		}

		return next(cfg, req, resp)
	}
}

func whitelistBlockScrape(next tracker.ScrapeHandler) tracker.ScrapeHandler {
	return func(cfg *chihaya.TrackerConfig, req *chihaya.ScrapeRequest, resp *chihaya.ScrapeResponse) (err error) {
		whitelisted := false
		storage := mustGetStore()

		for _, ih := range req.InfoHashes {
			whitelisted, err = storage.HasString(PrefixInfohash + string(ih[:]))

			if err != nil {
				return err
			} else if !whitelisted {
				return ErrBlockedInfohash
			}
		}

		return next(cfg, req, resp)
	}
}