package activityserve

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/gologme/log"

	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"

	"github.com/dchest/uniuri"
	"github.com/go-fed/httpsig"
)

// Actor represents a local actor we can act on
// behalf of.
type Actor struct {
	Name, summary, actorType, iri  string
	followersIRI                   string
	nuIri                          *url.URL
	followers, following, rejected map[string]interface{}
	requested                      map[string]interface{}
	posts                          map[int]map[string]interface{}
	publicKey                      crypto.PublicKey
	privateKey                     crypto.PrivateKey
	publicKeyPem                   string
	privateKeyPem                  string
	publicKeyID                    string
	OnFollow                       func(map[string]interface{})
	OnReceiveContent               func(map[string]interface{})
}

// ActorToSave is a stripped down actor representation
// with exported properties in order for json to be
// able to marshal it.
// see https://stackoverflow.com/questions/26327391/json-marshalstruct-returns
type ActorToSave struct {
	Name, Summary, ActorType, IRI, PublicKey, PrivateKey string
	Followers, Following, Rejected, Requested            map[string]interface{}
}

// MakeActor creates and returns a new local actor we can act
// on behalf of. It also creates its files on disk
func MakeActor(name, summary, actorType string) (Actor, error) {
	followers := make(map[string]interface{})
	following := make(map[string]interface{})
	rejected := make(map[string]interface{})
	requested := make(map[string]interface{})
	followersIRI := baseURL + name + "/followers"
	publicKeyID := baseURL + name + "#main-key"
	iri := baseURL + name
	nuIri, err := url.Parse(iri)
	if err != nil {
		log.Info("Something went wrong when parsing the local actor uri into net/url")
		return Actor{}, err
	}
	actor := Actor{
		Name:         name,
		summary:      summary,
		actorType:    actorType,
		iri:          iri,
		nuIri:        nuIri,
		followers:    followers,
		following:    following,
		rejected:     rejected,
		requested:    requested,
		followersIRI: followersIRI,
		publicKeyID:  publicKeyID,
	}

	// set auto accept by default (this could be a configuration value)
	actor.OnFollow = func(activity map[string]interface{}) { actor.Accept(activity) }
	actor.OnReceiveContent = func(activity map[string]interface{}) {}

	// create actor's keypair
	rng := rand.Reader
	privateKey, err := rsa.GenerateKey(rng, 2048)
	publicKey := privateKey.PublicKey

	actor.publicKey = publicKey
	actor.privateKey = privateKey

	// marshal the crypto to pem
	privateKeyDer := x509.MarshalPKCS1PrivateKey(privateKey)
	privateKeyBlock := pem.Block{
		Type:    "RSA PRIVATE KEY",
		Headers: nil,
		Bytes:   privateKeyDer,
	}
	actor.privateKeyPem = string(pem.EncodeToMemory(&privateKeyBlock))

	publicKeyDer, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err != nil {
		log.Info("Can't marshal public key")
		return Actor{}, err
	}

	publicKeyBlock := pem.Block{
		Type:    "PUBLIC KEY",
		Headers: nil,
		Bytes:   publicKeyDer,
	}
	actor.publicKeyPem = string(pem.EncodeToMemory(&publicKeyBlock))

	err = actor.save()
	if err != nil {
		return actor, err
	}

	return actor, nil
}

// GetOutboxIRI returns the outbox iri in net/url format
func (a *Actor) GetOutboxIRI() *url.URL {
	iri := a.iri + "/outbox"
	nuiri, _ := url.Parse(iri)
	return nuiri
}

// LoadActor searches the filesystem and creates an Actor
// from the data in <name>.json
// This does not preserve events so use with caution
func LoadActor(name string) (Actor, error) {
	// make sure our users can't read our hard drive
	if strings.ContainsAny(name, "./ ") {
		log.Info("Illegal characters in actor name")
		return Actor{}, errors.New("Illegal characters in actor name")
	}
	jsonFile := storage + slash + "actors" + slash + name + slash + name + ".json"
	fileHandle, err := os.Open(jsonFile)
	if os.IsNotExist(err) {
		log.Info(name)
		log.Info("We don't have this kind of actor stored")
		return Actor{}, err
	}
	byteValue, err := ioutil.ReadAll(fileHandle)
	if err != nil {
		log.Info("Error reading actor file")
		return Actor{}, err
	}
	jsonData := make(map[string]interface{})
	json.Unmarshal(byteValue, &jsonData)

	nuIri, err := url.Parse(jsonData["IRI"].(string))
	if err != nil {
		log.Info("Something went wrong when parsing the local actor uri into net/url")
		return Actor{}, err
	}

	publicKeyDecoded, rest := pem.Decode([]byte(jsonData["PublicKey"].(string)))
	if publicKeyDecoded == nil {
		log.Info(rest)
		panic("failed to parse PEM block containing the public key")
	}
	publicKey, err := x509.ParsePKIXPublicKey(publicKeyDecoded.Bytes)
	if err != nil {
		log.Info("Can't parse public keys")
		log.Info(err)
		return Actor{}, err
	}
	privateKeyDecoded, rest := pem.Decode([]byte(jsonData["PrivateKey"].(string)))
	if privateKeyDecoded == nil {
		log.Info(rest)
		panic("failed to parse PEM block containing the private key")
	}
	privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes)
	if err != nil {
		log.Info("Can't parse private keys")
		log.Info(err)
		return Actor{}, err
	}

	actor := Actor{
		Name:          name,
		summary:       jsonData["Summary"].(string),
		actorType:     jsonData["ActorType"].(string),
		iri:           jsonData["IRI"].(string),
		nuIri:         nuIri,
		followers:     jsonData["Followers"].(map[string]interface{}),
		following:     jsonData["Following"].(map[string]interface{}),
		rejected:      jsonData["Rejected"].(map[string]interface{}),
		requested:     jsonData["Requested"].(map[string]interface{}),
		publicKey:     publicKey,
		privateKey:    privateKey,
		publicKeyPem:  jsonData["PublicKey"].(string),
		privateKeyPem: jsonData["PrivateKey"].(string),
		followersIRI:  baseURL + name + "/followers",
		publicKeyID:   baseURL + name + "#main-key",
	}

	actor.OnFollow = func(activity map[string]interface{}) { actor.Accept(activity) }
	actor.OnReceiveContent = func(activity map[string]interface{}) {}

	return actor, nil
}

// GetActor attempts to LoadActor and if it doesn't exist
// creates one
func GetActor(name, summary, actorType string) (Actor, error) {
	actor, err := LoadActor(name)

	if err != nil {
		log.Info("Actor doesn't exist, creating...")
		actor, err = MakeActor(name, summary, actorType)
		if err != nil {
			log.Info("Can't create actor!")
			return Actor{}, err
		}
	}

	// if the info provided for the actor is different
	// from what the actor has, edit the actor
	save := false
	if summary != actor.summary {
		actor.summary = summary
		save = true
	}
	if actorType != actor.actorType {
		actor.actorType = actorType
		save = true
	}
	// if anything changed write it to disk
	if save {
		actor.save()
	}

	return actor, nil
}

// func LoadActorFromIRI(iri string) a Actor{
// TODO, this should parse the iri and load the right actor
// }

// save the actor to file
func (a *Actor) save() error {

	// check if we already have a directory to save actors
	// and if not, create it
	dir := storage + slash + "actors" + slash + a.Name + slash + "items"
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		os.MkdirAll(dir, 0755)
	}

	actorToSave := ActorToSave{
		Name:       a.Name,
		Summary:    a.summary,
		ActorType:  a.actorType,
		IRI:        a.iri,
		Followers:  a.followers,
		Following:  a.following,
		Rejected:   a.rejected,
		Requested:  a.requested,
		PublicKey:  a.publicKeyPem,
		PrivateKey: a.privateKeyPem,
	}

	actorJSON, err := json.MarshalIndent(actorToSave, "", "\t")
	if err != nil {
		log.Info("error Marshalling actor json")
		return err
	}
	// log.Info(actorToSave)
	// log.Info(string(actorJSON))
	err = ioutil.WriteFile(storage+slash+"actors"+slash+a.Name+slash+a.Name+".json", actorJSON, 0644)
	if err != nil {
		log.Printf("WriteFileJson ERROR: %+v", err)
		return err
	}

	return nil
}

func (a *Actor) whoAmI() string {

	self := make(map[string]interface{})
	self["@context"] = context()
	self["type"] = a.actorType
	self["id"] = baseURL + a.Name
	self["name"] = a.Name
	self["preferredUsername"] = a.Name
	self["summary"] = a.summary
	self["inbox"] = baseURL + a.Name + "/inbox"
	self["outbox"] = baseURL + a.Name + "/outbox"
	self["followers"] = baseURL + a.Name + "/peers/followers"
	self["following"] = baseURL + a.Name + "/peers/following"
	self["publicKey"] = map[string]string{
		"id":           baseURL + a.Name + "#main-key",
		"owner":        baseURL + a.Name,
		"publicKeyPem": a.publicKeyPem,
	}
	selfString, _ := json.Marshal(self)
	return string(selfString)
}

func (a *Actor) newItemID() (hash string, url string) {
	hash = uniuri.New()
	return hash, baseURL + a.Name + "/item/" + hash
}

func (a *Actor) newID() (hash string, url string) {
	hash = uniuri.New()
	return hash, baseURL + a.Name + "/" + hash
}

// TODO Reply(content string, inReplyTo string)

// ReplyNote sends a note to a specific actor in reply to
// a post
//TODO

// DM sends a direct message to a user
// TODO

// CreateNote posts an activityPub note to our followers
//
func (a *Actor) CreateNote(content, inReplyTo string) {
	// for now I will just write this to the outbox
	hash, id := a.newItemID()
	create := make(map[string]interface{})
	note := make(map[string]interface{})
	create["@context"] = context()
	create["actor"] = baseURL + a.Name
	create["cc"] = a.followersIRI
	create["id"] = id
	create["object"] = note
	note["attributedTo"] = baseURL + a.Name
	note["cc"] = a.followersIRI
	note["content"] = content
	if inReplyTo != "" {
		note["inReplyTo"] = inReplyTo
	}
	note["id"] = id
	note["published"] = time.Now().Format(time.RFC3339)
	note["url"] = create["id"]
	note["type"] = "Note"
	note["to"] = "https://www.w3.org/ns/activitystreams#Public"
	create["published"] = note["published"]
	create["type"] = "Create"
	go a.sendToFollowers(create)
	err := a.saveItem(hash, create)
	if err != nil {
		log.Info("Could not save note to disk")
	}
	err = a.appendToOutbox(id)
	if err != nil {
		log.Info("Could not append Note to outbox.txt")
	}
}

// saveItem saves an activity to disk under the actor and with the id as
// filename
func (a *Actor) saveItem(hash string, content map[string]interface{}) error {
	JSON, _ := json.MarshalIndent(content, "", "\t")

	dir := storage + slash + "actors" + slash + a.Name + slash + "items"
	err := ioutil.WriteFile(dir+slash+hash+".json", JSON, 0644)
	if err != nil {
		log.Printf("WriteFileJson ERROR: %+v", err)
		return err
	}
	return nil
}

func (a *Actor) loadItem(hash string) (item map[string]interface{}, err error) {
	dir := storage + slash + "actors" + slash + a.Name + slash + "items"
	jsonFile := dir + slash + hash + ".json"
	fileHandle, err := os.Open(jsonFile)
	if os.IsNotExist(err) {
		log.Info("We don't have this item stored")
		return
	}
	byteValue, err := ioutil.ReadAll(fileHandle)
	if err != nil {
		log.Info("Error reading item file")
		return
	}
	json.Unmarshal(byteValue, &item)

	return
}

// send is here for backward compatibility and maybe extra pre-processing
// not always required
func (a *Actor) send(content map[string]interface{}, to *url.URL) (err error) {
	return a.signedHTTPPost(content, to.String())
}

// getPeers gets followers or following depending on `who`
func (a *Actor) getPeers(page int, who string) (response []byte, err error) {
	// if there's no page parameter mastodon displays an
	// OrderedCollection with info of where to find orderedCollectionPages
	// with the actual information. We are mirroring that behavior

	var collection map[string]interface{}
	if who == "followers" {
		collection = a.followers
	} else if who == "following" {
		collection = a.following
	} else {
		return nil, errors.New("cannot find collection" + who)
	}
	themap := make(map[string]interface{})
	themap["@context"] = context()
	if page == 0 {
		themap["first"] = baseURL + a.Name + "/peers/" + who + "?page=1"
		themap["id"] = baseURL + a.Name + "/peers/" + who
		themap["totalItems"] = strconv.Itoa(len(collection))
		themap["type"] = "OrderedCollection"
	} else if page == 1 { // implement pagination
		themap["id"] = baseURL + a.Name + who + "?page=" + strconv.Itoa(page)
		items := make([]string, 0, len(collection))
		for k := range collection {
			items = append(items, k)
		}
		themap["orderedItems"] = items
		themap["partOf"] = baseURL + a.Name + "/peers/" + who
		themap["totalItems"] = len(collection)
		themap["type"] = "OrderedCollectionPage"
	}
	response, _ = json.Marshal(themap)
	return
}

// GetFollowers returns a list of people that follow us
func (a *Actor) GetFollowers(page int) (response []byte, err error) {
	return a.getPeers(page, "followers")
}

// GetFollowing returns a list of people that we follow
func (a *Actor) GetFollowing(page int) (response []byte, err error) {
	return a.getPeers(page, "following")
}

// signedHTTPPost performs an HTTP post on behalf of Actor with the
// request-target, date, host and digest headers signed
// with the actor's private key.
func (a *Actor) signedHTTPPost(content map[string]interface{}, to string) (err error) {
	b, err := json.Marshal(content)
	if err != nil {
		log.Info("Can't marshal JSON")
		log.Info(err)
		return
	}
	postSigner, _, _ := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, "SHA-256", []string{"(request-target)", "date", "host", "digest"}, httpsig.Signature)

	byteCopy := make([]byte, len(b))
	copy(byteCopy, b)
	buf := bytes.NewBuffer(byteCopy)
	req, err := http.NewRequest("POST", to, buf)
	if err != nil {
		log.Info(err)
		return
	}

	// I prefer to deal with strings and just parse to net/url if and when
	// needed, even if here we do one extra round trip
	iri, err := url.Parse(to)
	if err != nil {
		log.Error("cannot parse url for POST, check your syntax")
		return err
	}
	req.Header.Add("Accept-Charset", "utf-8")
	req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
	req.Header.Add("User-Agent", userAgent+" "+version)
	req.Header.Add("Host", iri.Host)
	req.Header.Add("Accept", "application/activity+json; charset=utf-8")
	req.Header.Add("Content-Type", "application/activity+json; charset=utf-8")
	err = postSigner.SignRequest(a.privateKey, a.publicKeyID, req, byteCopy)
	if err != nil {
		log.Info(err)
		return
	}
	resp, err := client.Do(req)
	if err != nil {
		log.Info(err)
		return
	}
	defer resp.Body.Close()
	if !isSuccess(resp.StatusCode) {
		responseData, _ := ioutil.ReadAll(resp.Body)
		err = fmt.Errorf("POST request to %s failed (%d): %s\nResponse: %s \nRequest: %s \nHeaders: %s", to, resp.StatusCode, resp.Status, FormatJSON(responseData), FormatJSON(byteCopy), FormatHeaders(req.Header))
		log.Info(err)
		return
	}
	responseData, _ := ioutil.ReadAll(resp.Body)
	log.Errorf("POST request to %s succeeded (%d): %s \nResponse: %s \nRequest: %s \nHeaders: %s", to, resp.StatusCode, resp.Status, FormatJSON(responseData), FormatJSON(byteCopy), FormatHeaders(req.Header))
	return
}

func (a *Actor) signedHTTPGet(address string) (string, error) {
	req, err := http.NewRequest("GET", address, nil)
	if err != nil {
		log.Error("cannot create new http.request")
		return "", err
	}

	iri, err := url.Parse(address)
	if err != nil {
		log.Error("cannot parse url for GET, check your syntax")
		return "", err
	}

	req.Header.Add("Accept-Charset", "utf-8")
	req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
	req.Header.Add("User-Agent", fmt.Sprintf("%s %s %s", userAgent, libName, version))
	req.Header.Add("host", iri.Host)
	req.Header.Add("digest", "")
	req.Header.Add("Accept", "application/activity+json; profile=\"https://www.w3.org/ns/activitystreams\"")

	// set up the http signer
	signer, _, _ := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, "SHA-256", []string{"(request-target)", "date", "host", "digest"}, httpsig.Signature)
	err = signer.SignRequest(a.privateKey, a.publicKeyID, req, nil)
	if err != nil {
		log.Error("Can't sign the request")
		return "", err
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Error("Cannot perform the GET request")
		log.Error(err)
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {

		responseData, _ := ioutil.ReadAll(resp.Body)
		return "", fmt.Errorf("GET request to %s failed (%d): %s \n%s", iri.String(), resp.StatusCode, resp.Status, FormatJSON(responseData))
	}

	responseData, _ := ioutil.ReadAll(resp.Body)
	fmt.Println("GET request succeeded:", iri.String(), req.Header, resp.StatusCode, resp.Status, "\n", FormatJSON(responseData))

	responseText := string(responseData)
	return responseText, nil
}

// NewFollower records a new follower to the actor file
func (a *Actor) NewFollower(iri string, inbox string) error {
	a.followers[iri] = inbox
	return a.save()
}

// appendToOutbox adds a new line with the id of the activity
// to outbox.txt
func (a *Actor) appendToOutbox(iri string) (err error) {
	// create outbox file if it doesn't exist
	var outbox *os.File

	outboxFilePath := storage + slash + "actors" + slash + a.Name + slash + "outbox.txt"
	outbox, err = os.OpenFile(outboxFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Info("Cannot create or open outbox file")
		log.Info(err)
		return err
	}
	defer outbox.Close()

	outbox.Write([]byte(iri + "\n"))

	return nil
}

// batchSend sends a batch of http posts to a list of recipients
func (a *Actor) batchSend(activity map[string]interface{}, recipients []string) (err error) {
	for _, v := range recipients {
		err := a.signedHTTPPost(activity, v)
		if err != nil {
			log.Info("Failed to deliver message to " + v)
		}
	}
	return
}

// send to followers sends a batch of http posts to each one of the followers
func (a *Actor) sendToFollowers(activity map[string]interface{}) (err error) {
	recipients := make([]string, len(a.followers))

	i := 0
	for _, inbox := range a.followers {
		recipients[i] = inbox.(string)
		i++
	}
	a.batchSend(activity, recipients)
	return
}

// Follow a remote user by their iri
func (a *Actor) Follow(user string) (err error) {
	remote, err := NewRemoteActor(user)
	if err != nil {
		log.Info("Can't contact " + user + " to get their inbox")
		return
	}

	follow := make(map[string]interface{})
	hash, id := a.newItemID()

	follow["@context"] = context()
	follow["actor"] = a.iri
	follow["id"] = id
	follow["object"] = user
	follow["type"] = "Follow"

	// if we are not already following them
	if _, ok := a.following[user]; !ok {
		// if we have not been rejected previously
		if _, ok := a.rejected[user]; !ok {
			go func() {
				err := a.signedHTTPPost(follow, remote.inbox)
				if err != nil {
					log.Info("Couldn't follow " + user)
					log.Info(err)
					return
				}
				// save the activity
				a.saveItem(hash, follow)
				a.requested[user] = hash
				a.save()
				// we are going to save the request here
				// and save the follow only on accept so look at
				// the http handler for the accept code
			}()
		}
	}

	return nil
}

// Unfollow the user declared by the iri in `user`
// this recreates the original follow activity
// , wraps it in an Undo activity, sets it's
// id to the id of the original Follow activity that
// was accepted when initially following that user
// (this is read from the `actor.following` map
func (a *Actor) Unfollow(user string) {
	// if we have a request to follow this user cancel it
	cancelRequest := false
	if _, ok := a.requested[user]; ok {
		log.Info("Cancelling follow request")
		cancelRequest = true
		// then continue to send the unfollow to the receipient
		// to inform them that the request is cancelled.
	} else if _, ok := a.following[user]; !ok {
		log.Info("We are not following this user, ignoring...")
		return
	}

	log.Info("Unfollowing " + user)

	var hash string
	// find the id of the original follow
	if cancelRequest {
		hash = a.requested[user].(string)
	} else {
		hash = a.following[user].(string)
	}

	// create an undo activiy
	undo := make(map[string]interface{})
	undo["@context"] = context()
	undo["actor"] = a.iri
	undo["id"] = baseURL + "item/" + hash + "/undo"
	undo["type"] = "Undo"

	follow := make(map[string]interface{})

	follow["@context"] = context()
	follow["actor"] = a.iri
	follow["id"] = baseURL + "item/" + hash
	follow["object"] = user
	follow["type"] = "Follow"

	// add the properties to the undo activity
	undo["object"] = follow

	// get the remote user's inbox
	remoteUser, err := NewRemoteActor(user)
	if err != nil {
		log.Info("Failed to contact remote actor")
		return
	}

	PrettyPrint(undo)
	go func() {
		err := a.signedHTTPPost(undo, remoteUser.inbox)
		if err != nil {
			log.Info("Couldn't unfollow " + user)
			log.Info(err)
			return
		}
		// if there was no error then delete the follow
		// from the list
		if cancelRequest {
			delete(a.requested, user)
		} else {
			delete(a.following, user)
		}
		a.save()
	}()
}

// Announce this activity to our followers
func (a *Actor) Announce(url string) {
	// our announcements are public. Public stuff have a "To" to the url below
	toURL := []string{"https://www.w3.org/ns/activitystreams#Public"}
	hash, id := a.newItemID()

	announce := make(map[string]interface{})

	announce["@context"] = context()
	announce["id"] = id
	announce["type"] = "Announce"
	announce["object"] = url
	announce["actor"] = a.iri
	announce["to"] = toURL

	// cc this to all our followers one by one
	// I've seen activities to just include the url of the
	// collection but for now this works.

	// It seems that sharedInbox will be deprecated
	// so this is probably a better idea anyway (#APConf)
	announce["cc"] = a.followersSlice()

	// add a timestamp
	announce["published"] = time.Now().Format(time.RFC3339)

	a.appendToOutbox(announce["id"].(string))
	a.saveItem(hash, announce)
	a.sendToFollowers(announce)
}

func (a *Actor) followersSlice() []string {
	followersSlice := make([]string, 0)
	followersSlice = append(followersSlice, a.followersIRI)
	for k := range a.followers {
		followersSlice = append(followersSlice, k)
	}
	return followersSlice
}

// Accept a follow request
func (a *Actor) Accept(follow map[string]interface{}) {
	// it's a follow, write it down
	newFollower := follow["actor"].(string)
	// check we aren't following ourselves
	if newFollower == follow["object"] {
		log.Info("You can't follow yourself")
		return
	}

	follower, err := NewRemoteActor(follow["actor"].(string))

	// check if this user is already following us
	if _, ok := a.followers[newFollower]; ok {
		log.Info("You're already following us, yay!")
		// do nothing, they're already following us
	} else {
		a.NewFollower(newFollower, follower.inbox)
	}
	// send accept anyway even if they are following us already
	// this is very verbose. I would prefer creating a map by hand

	// remove @context from the inner activity
	delete(follow, "@context")

	accept := make(map[string]interface{})

	accept["@context"] = "https://www.w3.org/ns/activitystreams"
	accept["to"] = follow["actor"]
	_, accept["id"] = a.newID()
	accept["actor"] = a.iri
	accept["object"] = follow
	accept["type"] = "Accept"

	if err != nil {
		log.Info("Couldn't retrieve remote actor info, maybe server is down?")
		log.Info(err)
	}

	// Maybe we need to save this accept?
	go a.signedHTTPPost(accept, follower.inbox)

}

// Followers returns the list of followers
func (a *Actor) Followers() map[string]string {
	f := make(map[string]string)
	for follower, inbox := range a.followers {
		f[follower] = inbox.(string)
	}
	return f
}

// Following returns the list of followers
func (a *Actor) Following() map[string]string {
	f := make(map[string]string)
	for followee, hash := range a.following {
		f[followee] = hash.(string)
	}
	return f
}
