package activitypub

import (
	"bytes"
	"encoding/gob"
	"errors"
	"time"
)

func GobDecode(data []byte) (Item, error) {
	return gobDecodeItem(data)
}

func gobDecodeUint(i *uint, data []byte) error {
	g := gob.NewDecoder(bytes.NewReader(data))
	return g.Decode(i)
}

func gobDecodeFloat64(f *float64, data []byte) error {
	g := gob.NewDecoder(bytes.NewReader(data))
	return g.Decode(f)
}

func gobDecodeInt64(i *int64, data []byte) error {
	g := gob.NewDecoder(bytes.NewReader(data))
	return g.Decode(i)
}

func gobDecodeBool(b *bool, data []byte) error {
	g := gob.NewDecoder(bytes.NewReader(data))
	return g.Decode(b)
}

func unmapActorProperties(mm map[string][]byte, a *Actor) error {
	err := OnObject(a, func(ob *Object) error {
		return unmapObjectProperties(mm, ob)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["inbox"]; ok {
		if a.Inbox, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["outbox"]; ok {
		if a.Outbox, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["following"]; ok {
		if a.Following, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["followers"]; ok {
		if a.Followers, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["liked"]; ok {
		if a.Liked, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["preferredUsername"]; ok {
		if a.PreferredUsername, err = gobDecodeNaturalLanguageValues(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["endpoints"]; ok {
		if err = a.Endpoints.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["streams"]; ok {
		if a.Streams, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["publicKey"]; ok {
		if err = a.PublicKey.GobDecode(raw); err != nil {
			return err
		}
	}
	return nil
}

func unmapIntransitiveActivityProperties(mm map[string][]byte, act *IntransitiveActivity) error {
	err := OnObject(act, func(ob *Object) error {
		return unmapObjectProperties(mm, ob)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["actor"]; ok {
		if act.Actor, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["target"]; ok {
		if act.Target, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["result"]; ok {
		if act.Result, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["origin"]; ok {
		if act.Origin, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["instrument"]; ok {
		if act.Instrument, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	return nil
}

func unmapActivityProperties(mm map[string][]byte, act *Activity) error {
	err := OnIntransitiveActivity(act, func(act *IntransitiveActivity) error {
		return unmapIntransitiveActivityProperties(mm, act)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["object"]; ok {
		if act.Object, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	return nil
}

func unmapLinkProperties(mm map[string][]byte, l *Link) error {
	if raw, ok := mm["id"]; ok {
		if err := l.ID.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["type"]; ok {
		if err := l.Type.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["mediaType"]; ok {
		if err := l.MediaType.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["href"]; ok {
		if err := l.Href.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["hrefLang"]; ok {
		if err := l.HrefLang.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["name"]; ok {
		if err := l.Name.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["rel"]; ok {
		if err := l.Rel.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["width"]; ok {
		if err := gobDecodeUint(&l.Width, raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["height"]; ok {
		if err := gobDecodeUint(&l.Height, raw); err != nil {
			return err
		}
	}
	return nil
}

func unmapObjectProperties(mm map[string][]byte, o *Object) error {
	var err error
	if raw, ok := mm["id"]; ok {
		if err = o.ID.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["type"]; ok {
		if err = o.Type.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["name"]; ok {
		if err = o.Name.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["attachment"]; ok {
		if o.Attachment, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["attributedTo"]; ok {
		if o.AttributedTo, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["audience"]; ok {
		if o.Audience, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["content"]; ok {
		if o.Content, err = gobDecodeNaturalLanguageValues(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["context"]; ok {
		if o.Context, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["mediaType"]; ok {
		if err = o.MediaType.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["endTime"]; ok {
		if err = o.EndTime.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["generator"]; ok {
		if o.Generator, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["icon"]; ok {
		if o.Icon, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["image"]; ok {
		if o.Image, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["inReplyTo"]; ok {
		if o.InReplyTo, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["location"]; ok {
		if o.Location, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["preview"]; ok {
		if o.Preview, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["published"]; ok {
		if err = o.Published.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["replies"]; ok {
		if o.Replies, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["startTime"]; ok {
		if err = o.StartTime.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["summary"]; ok {
		if o.Summary, err = gobDecodeNaturalLanguageValues(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["tag"]; ok {
		if o.Tag, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["updated"]; ok {
		if err = o.Updated.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["url"]; ok {
		if o.URL, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["to"]; ok {
		if o.To, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["bto"]; ok {
		if o.Bto, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["cc"]; ok {
		if o.CC, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["bcc"]; ok {
		if o.BCC, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["duration"]; ok {
		if o.Duration, err = gobDecodeDuration(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["likes"]; ok {
		if o.Likes, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["shares"]; ok {
		if o.Shares, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["source"]; ok {
		if err := o.Source.GobDecode(raw); err != nil {
			return err
		}
	}
	return nil
}

func tryDecodeItems(items *ItemCollection, data []byte) error {
	tt := make([][]byte, 0)
	g := gob.NewDecoder(bytes.NewReader(data))
	if err := g.Decode(&tt); err != nil {
		return err
	}
	for _, it := range tt {
		ob, err := gobDecodeItem(it)
		if err != nil {
			return err
		}
		*items = append(*items, ob)
	}
	return nil
}

func tryDecodeIRIs(iris *IRIs, data []byte) error {
	return iris.GobDecode(data)
}

func tryDecodeIRI(iri *IRI, data []byte) error {
	return iri.GobDecode(data)
}

func gobDecodeDuration(data []byte) (time.Duration, error) {
	var d time.Duration
	err := gob.NewDecoder(bytes.NewReader(data)).Decode(&d)
	return d, err
}

func gobDecodeNaturalLanguageValues(data []byte) (NaturalLanguageValues, error) {
	n := make(NaturalLanguageValues, 0)
	err := n.GobDecode(data)
	return n, err
}

func gobDecodeItems(data []byte) (ItemCollection, error) {
	items := make(ItemCollection, 0)
	if err := tryDecodeItems(&items, data); err != nil {
		return nil, err
	}
	return items, nil
}

func gobDecodeItem(data []byte) (Item, error) {
	items := make(ItemCollection, 0)
	if err := tryDecodeItems(&items, data); err == nil {
		return items, nil
	}
	iris := make(IRIs, 0)
	if err := tryDecodeIRIs(&iris, data); err == nil {
		return iris, nil
	}
	isObject := false
	typ := ActivityVocabularyType("")
	mm, err := gobDecodeObjectAsMap(data)
	if err == nil {
		var sTyp []byte
		sTyp, isObject = mm["type"]
		if isObject {
			typ = ActivityVocabularyType(sTyp)
		} else {
			_, isObject = mm["id"]
		}
	}
	if isObject {
		it, err := ItemTyperFunc(typ)
		if err != nil {
			return nil, err
		}
		switch it.GetType() {
		case IRIType:
		case "", ObjectType, ArticleType, AudioType, DocumentType, EventType, ImageType, NoteType, PageType, VideoType:
			err = OnObject(it, func(ob *Object) error {
				return unmapObjectProperties(mm, ob)
			})
		case LinkType, MentionType:
			err = OnLink(it, func(l *Link) error {
				return unmapLinkProperties(mm, l)
			})
		case ActivityType, AcceptType, AddType, AnnounceType, BlockType, CreateType, DeleteType, DislikeType,
			FlagType, FollowType, IgnoreType, InviteType, JoinType, LeaveType, LikeType, ListenType, MoveType, OfferType,
			RejectType, ReadType, RemoveType, TentativeRejectType, TentativeAcceptType, UndoType, UpdateType, ViewType:
			err = OnActivity(it, func(act *Activity) error {
				return unmapActivityProperties(mm, act)
			})
		case IntransitiveActivityType, ArriveType, TravelType:
			err = OnIntransitiveActivity(it, func(act *IntransitiveActivity) error {
				return unmapIntransitiveActivityProperties(mm, act)
			})
		case ActorType, ApplicationType, GroupType, OrganizationType, PersonType, ServiceType:
			err = OnActor(it, func(a *Actor) error {
				return unmapActorProperties(mm, a)
			})
		case CollectionType:
			err = OnCollection(it, func(c *Collection) error {
				return unmapCollectionProperties(mm, c)
			})
		case OrderedCollectionType:
			err = OnOrderedCollection(it, func(c *OrderedCollection) error {
				return unmapOrderedCollectionProperties(mm, c)
			})
		case CollectionPageType:
			err = OnCollectionPage(it, func(p *CollectionPage) error {
				return unmapCollectionPageProperties(mm, p)
			})
		case OrderedCollectionPageType:
			err = OnOrderedCollectionPage(it, func(p *OrderedCollectionPage) error {
				return unmapOrderedCollectionPageProperties(mm, p)
			})
		case PlaceType:
			err = OnPlace(it, func(p *Place) error {
				return unmapPlaceProperties(mm, p)
			})
		case ProfileType:
			err = OnProfile(it, func(p *Profile) error {
				return unmapProfileProperties(mm, p)
			})
		case RelationshipType:
			err = OnRelationship(it, func(r *Relationship) error {
				return unmapRelationshipProperties(mm, r)
			})
		case TombstoneType:
			err = OnTombstone(it, func(t *Tombstone) error {
				return unmapTombstoneProperties(mm, t)
			})
		case QuestionType:
			err = OnQuestion(it, func(q *Question) error {
				return unmapQuestionProperties(mm, q)
			})
		}
		return it, err
	}
	iri := IRI("")
	if err := tryDecodeIRI(&iri, data); err == nil {
		return iri, err
	}

	return nil, errors.New("unable to gob decode to any known ActivityPub types")
}

func gobDecodeObjectAsMap(data []byte) (map[string][]byte, error) {
	mm := make(map[string][]byte)
	g := gob.NewDecoder(bytes.NewReader(data))
	if err := g.Decode(&mm); err != nil {
		return nil, err
	}
	return mm, nil
}

func unmapIncompleteCollectionProperties(mm map[string][]byte, c *Collection) error {
	err := OnObject(c, func(ob *Object) error {
		return unmapObjectProperties(mm, ob)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["current"]; ok {
		if c.Current, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["first"]; ok {
		if c.First, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["last"]; ok {
		if c.Last, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["totalItems"]; ok {
		if err = gobDecodeUint(&c.TotalItems, raw); err != nil {
			return err
		}
	}
	return nil
}

func unmapCollectionProperties(mm map[string][]byte, c *Collection) error {
	err := unmapIncompleteCollectionProperties(mm, c)
	if err != nil {
		return err
	}
	if raw, ok := mm["items"]; ok {
		if c.Items, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	return err
}

func unmapCollectionPageProperties(mm map[string][]byte, c *CollectionPage) error {
	err := OnCollection(c, func(c *Collection) error {
		return unmapCollectionProperties(mm, c)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["partOf"]; ok {
		if c.PartOf, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["next"]; ok {
		if c.Next, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["prev"]; ok {
		if c.Prev, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	return err
}

func unmapOrderedCollectionProperties(mm map[string][]byte, o *OrderedCollection) error {
	err := OnCollection(o, func(c *Collection) error {
		return unmapIncompleteCollectionProperties(mm, c)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["orderedItems"]; ok {
		if o.OrderedItems, err = gobDecodeItems(raw); err != nil {
			return err
		}
	}
	return err
}

func unmapOrderedCollectionPageProperties(mm map[string][]byte, c *OrderedCollectionPage) error {
	err := OnOrderedCollection(c, func(c *OrderedCollection) error {
		return unmapOrderedCollectionProperties(mm, c)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["partOf"]; ok {
		if c.PartOf, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["next"]; ok {
		if c.Next, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["prev"]; ok {
		if c.Prev, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	return err
}

func unmapPlaceProperties(mm map[string][]byte, p *Place) error {
	err := OnObject(p, func(ob *Object) error {
		return unmapObjectProperties(mm, ob)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["accuracy"]; ok {
		if err = gobDecodeFloat64(&p.Accuracy, raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["altitude"]; ok {
		if err = gobDecodeFloat64(&p.Altitude, raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["latitude"]; ok {
		if err = gobDecodeFloat64(&p.Latitude, raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["radius"]; ok {
		if err = gobDecodeInt64(&p.Radius, raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["units"]; ok {
		p.Units = string(raw)
	}
	return nil
}

func unmapProfileProperties(mm map[string][]byte, p *Profile) error {
	err := OnObject(p, func(ob *Object) error {
		return unmapObjectProperties(mm, ob)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["Describes"]; ok {
		if p.Describes, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	return nil
}

func unmapRelationshipProperties(mm map[string][]byte, r *Relationship) error {
	err := OnObject(r, func(ob *Object) error {
		return unmapObjectProperties(mm, ob)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["subject"]; ok {
		if r.Subject, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["object"]; ok {
		if r.Object, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["relationship"]; ok {
		if r.Relationship, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	return nil
}

func unmapTombstoneProperties(mm map[string][]byte, t *Tombstone) error {
	err := OnObject(t, func(ob *Object) error {
		return unmapObjectProperties(mm, ob)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["formerType"]; ok {
		if err = t.FormerType.GobDecode(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["deleted"]; ok {
		if err = t.Deleted.GobDecode(raw); err != nil {
			return err
		}
	}
	return nil
}

func unmapQuestionProperties(mm map[string][]byte, q *Question) error {
	err := OnIntransitiveActivity(q, func(act *IntransitiveActivity) error {
		return unmapIntransitiveActivityProperties(mm, act)
	})
	if err != nil {
		return err
	}
	if raw, ok := mm["oneOf"]; ok {
		if q.OneOf, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["anyOf"]; ok {
		if q.AnyOf, err = gobDecodeItem(raw); err != nil {
			return err
		}
	}
	if raw, ok := mm["closed"]; ok {
		if err = gobDecodeBool(&q.Closed, raw); err != nil {
			return err
		}
	}
	return nil
}
