func (rep ReputationImpl) appendDonation(user *userMeta, post Pid, amount LinoCoin) IF {
	reputation := user.Reputation
	pos := -1
	used := NewInt(0)
	for i, v := range user.Unsettled {
		used.Add(v.Impact)
		if v.Pid == post {
			pos = i
		}
	}
	if pos == -1 && len(user.Unsettled) >= rep.UserMaxN {
		return NewInt(0)
	}
	var available IF = IntMax(IntSub(reputation, used), NewInt(0))
	var impact IF = IntMin(available, amount)
	if pos != -1 {
		user.Unsettled[pos].Amount.Add(amount)
		user.Unsettled[pos].Impact.Add(impact)
	} else {
		user.Unsettled = append(user.Unsettled, Donation{
			Pid:    post,
			Amount: amount,
			Impact: impact,
		})
	}
	return impact
}
appendDonation: append a new donation to user's unsettled list, return the impact
factor of this donation.
contract: before calling this, user's reputation needs to
be updated by calling updateReputation.
package repv2
import (
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
)
type Reputation interface {
	DonateAt(u Uid, p Pid, s LinoCoin) IF
	IncFreeScore(u Uid, r Rep)
	Update(t Time)
	GetReputation(u Uid) Rep
	GetCurrentRound() (RoundId, Time)  
	ExportToFile(file string) error
	ImportFromFile(file string) error
}
type ReputationImpl struct {
	store                ReputationStore
	BestN                int
	UserMaxN             int
	RoundDurationSeconds int64
	SampleWindowSize     int64
	DecayFactor          int64
}
var _ Reputation = ReputationImpl{}
func NewReputation(s ReputationStore, bestN int, userMaxN int, roundDurationSeconds, sampleWindowSize, decayFactor int64) Reputation {
	return ReputationImpl{
		store:                s,
		BestN:                bestN,
		UserMaxN:             userMaxN,
		RoundDurationSeconds: roundDurationSeconds,
		SampleWindowSize:     sampleWindowSize,
		DecayFactor:          decayFactor,
	}
}
func (rep ReputationImpl) ExportToFile(file string) error {
	rep.store.IterateUsers(func(u Uid) bool {
		rep.GetReputation(u)
		return false
	})
	rst := rep.store.Export()
	f, err := os.Create(file)
	if err != nil {
		return fmt.Errorf("failed to create file: %s", err)
	}
	defer f.Close()
	jsonbytes, err := cdc.MarshalJSON(rst)
	if err != nil {
		return fmt.Errorf("failed to marshal json for " + file + " due to " + err.Error())
	}
	_, err = f.Write(jsonbytes)
	if err != nil {
		return err
	}
	err = f.Sync()
	if err != nil {
		return err
	}
	return nil
}
func (rep ReputationImpl) ImportFromFile(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return fmt.Errorf("failed to open " + err.Error())
	}
	defer f.Close()
	bytes, err := ioutil.ReadAll(f)
	if err != nil {
		return fmt.Errorf("failed to readall: " + err.Error())
	}
	dt := &UserReputationTable{}
	err = cdc.UnmarshalJSON(bytes, dt)
	if err != nil {
		return fmt.Errorf("failed to unmarshal: " + err.Error())
	}
	rep.store.Import(dt)
	return nil
}
type consumptionInfo struct {
	seed    LinoCoin
	other   LinoCoin
	seedIF  IF
	otherIF IF
}
func (rep ReputationImpl) extractConsumptionInfo(user *userMeta, seedSet map[Pid]bool) consumptionInfo {
	seed := NewInt(0)
	other := NewInt(0)
	seedIF := NewInt(0)
	otherIF := NewInt(0)
	for _, pd := range user.Unsettled {
		amount := pd.Amount
		pid := pd.Pid
		impact := pd.Impact
		if _, isTop := seedSet[pid]; isTop {
			seed.Add(amount)
			seedIF.Add(impact)
		} else {
			other.Add(amount)
			otherIF.Add(impact)
		}
	}
	return consumptionInfo{
		seed:    seed,
		other:   other,
		seedIF:  seedIF,
		otherIF: otherIF,
	}
}
func (rep ReputationImpl) getSeedSet(id RoundId) map[Pid]bool {
	result := rep.store.GetRoundMeta(id)
	tops := make(map[Pid]bool)
	for _, p := range result.Result {
		tops[p] = true
	}
	return tops
}
func (rep ReputationImpl) computeReputation(consumption LinoCoin, hold Rep) Rep {
	return IntMax(
		IntSub(consumption, IntMul(hold, NewInt(rep.SampleWindowSize))),
		NewInt(0),
	)
}
type reputationData struct {
	consumption LinoCoin
	hold        Rep
	reputation  Rep
}
func (rep ReputationImpl) computeNewRepData(repData reputationData, consumptions consumptionInfo) reputationData {
	seed := consumptions.seed
	other := consumptions.other
	seedIF := consumptions.seedIF
	otherIF := consumptions.otherIF
	adjustedConsumption := IntMin(
		IntMax(IntDivFrac(seedIF, 8, 10), seed),
		IntAdd(seed, other),
	)
	newConsumption := repData.consumption
	if IntGreater(adjustedConsumption, repData.consumption) {
		newConsumption = IntEMA(repData.consumption, adjustedConsumption, rep.SampleWindowSize)
	}
	otherLimit := IntDiv(IntAdd(seedIF, otherIF), NewInt(5))  
	if IntGreater(otherIF, otherLimit) {
		newConsumption = IntSub(newConsumption,
			IntMax(NewInt(1),
				IntMulFrac(IntSub(otherIF, otherLimit), rep.DecayFactor, 100)))
		newConsumption = IntMax(NewInt(0), newConsumption)
	}
	if IntGTE(newConsumption, repData.consumption) {
		delta := IntSub(newConsumption, repData.consumption)
		repData.hold = IntEMA(repData.hold, delta, rep.SampleWindowSize)
	}
	repData.consumption = newConsumption
	repData.reputation = rep.computeReputation(repData.consumption, repData.hold)
	return repData
}
func (rep ReputationImpl) updateReputation(user *userMeta, current RoundId) {
	if !(user.LastSettledRound < user.LastDonationRound && user.LastDonationRound < current) {
		return
	}
	seedset := rep.getSeedSet(user.LastDonationRound)
	consumptions := rep.extractConsumptionInfo(user, seedset)
	newrep := rep.computeNewRepData(reputationData{
		consumption: user.Consumption,
		hold:        user.Hold,
		reputation:  user.Reputation,
	}, consumptions)
	for _, pd := range user.Unsettled {
		rep.deleteRoumdPostSumImpact(user.LastDonationRound, pd.Pid)
	}
	user.Consumption = newrep.consumption
	user.Hold = newrep.hold
	user.Reputation = newrep.reputation
	user.LastSettledRound = user.LastDonationRound
	user.Unsettled = nil
}
func (rep ReputationImpl) GetReputation(u Uid) Rep {
	user := rep.store.GetUserMeta(u)
	defer func() {
		rep.store.SetUserMeta(u, user)
	}()
	current := rep.store.GetCurrentRound()
	rep.updateReputation(user, current)
	return user.Reputation
}
func (rep ReputationImpl) DonateAt(u Uid, p Pid, amount LinoCoin) IF {
	if len(u) == 0 {
		panic("Length of Uid must be longer than 0")
	}
	if len(p) == 0 {
		panic("Length of Pid must be longer than 0")
	}
	var current RoundId = rep.store.GetCurrentRound()
	user := rep.store.GetUserMeta(u)
	defer func() {
		rep.store.SetUserMeta(u, user)
	}()
	rep.updateReputation(user, current)
	user.LastDonationRound = current
	impact := rep.appendDonation(user, p, amount)
	rep.incRoundPostSumImpact(current, p, impact)
	return impact
}