package atomfeed

import (
	"encoding/xml"
	"errors"
	"fmt"
	"time"
)

type Feed struct {
	XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
	*CommonAttributes
	Authors      []*Person           `xml:"author,omitempty"`
	Categories   []*Category         `xml:"category,omitempty"`
	Contributors []*Person           `xml:"contributor,omitempty"`
	Generator    *Generator          `xml:"generator,omitempty"`
	Icon         *Icon               `xml:"icon,omitempty"`
	ID           *ID                 `xml:"id"`
	Links        []*Link             `xml:"link,omitempty"`
	Logo         *Logo               `xml:"logo,omitempty"`
	Rights       Text                `xml:"rights,omitempty"`
	Subtitle     Text                `xml:"subtitle,omitempty"`
	Title        Text                `xml:"title"`
	Updated      *Date               `xml:"updated"`
	Extensions   []*ExtensionElement `xml:",any,omitempty"`
	Entries      []*Entry            `xml:"entry,omitempty"`
}

// NewFeed creates a new feed.
func NewFeed(title string) (*Feed, error) {
	text, err := NewText("text", title)
	if err != nil {
		return nil, fmt.Errorf("error creating new feed: %v", err)
	}

	return &Feed{
		ID:      NewID(),
		Title:   text,
		Updated: NewDate(time.Now()),
	}, nil
}

// AddAuthor adds the person as an author to the feed.
func (f *Feed) AddAuthor(p *Person) {
	if f.Authors == nil {
		f.Authors = make([]*Person, 1)
		f.Authors[0] = p
	} else {
		f.Authors = append(f.Authors, p)
	}

	f.Updated.DateTime = DateTime(time.Now())
}

// AddCategory adds the category to the feed.
func (f *Feed) AddCategory(c *Category) {
	if f.Categories == nil {
		f.Categories = make([]*Category, 1)
		f.Categories[0] = c
	} else {
		f.Categories = append(f.Categories, c)
	}

	f.Updated.DateTime = DateTime(time.Now())
}

// AddContributor adds the contributor to the feed.
func (f *Feed) AddContributor(c *Person) {
	if f.Contributors == nil {
		f.Contributors = make([]*Person, 1)
		f.Contributors[0] = c
	} else {
		f.Contributors = append(f.Contributors, c)
	}

	f.Updated.DateTime = DateTime(time.Now())
}

// AddLink adds the link to the feed.
// There should be one link with rel "self".
func (f *Feed) AddLink(l *Link) {
	if f.Links == nil {
		f.Links = make([]*Link, 1)
		f.Links[0] = l
	} else {
		f.Links = append(f.Links, l)
	}

	f.Updated.DateTime = DateTime(time.Now())
}

// AddExtension adds the extension to the feed.
func (f *Feed) AddExtension(e *ExtensionElement) {
	if f.Extensions == nil {
		f.Extensions = make([]*ExtensionElement, 1)
		f.Extensions[0] = e
	} else {
		f.Extensions = append(f.Extensions, e)
	}

	f.Updated.DateTime = DateTime(time.Now())
}

// AddEntry adds the entry to the feed.
func (f *Feed) AddEntry(e *Entry) {
	if f.Entries == nil {
		f.Entries = make([]*Entry, 1)
		f.Entries[0] = e
	} else {
		f.Entries = append(f.Entries, e)
	}

	f.Updated.DateTime = DateTime(time.Now())
}

// Check checks the feed for incompatibilities with RFC4287.
func (f *Feed) Check() error {
	if f.ID == nil {
		return errors.New("no id element of feed")
	} else {
		if err := f.ID.Check(); err != nil {
			return fmt.Errorf("id element of feed: %v", err)
		}
	}

	// atom:feed elements MUST contain one or more atom:author elements, unless
	// all of the atom:feed element's child atom:entry elements contain at
	// least one atom:author element.
	if f.Authors == nil {
		for _, e := range f.Entries {
			if err := e.checkAuthors(); err != nil {
				return fmt.Errorf("no authors set in feed %v: %v", f.ID.URI, err)
			}
		}
	} else {
		for i, a := range f.Authors {
			if err := a.Check(); err != nil {
				return fmt.Errorf("author element %v of feed %v: %v", i, f.ID.URI, err)
			}
		}
	}

	for i, c := range f.Categories {
		if err := c.Check(); err != nil {
			return fmt.Errorf("category element %v of feed %v: %v", i, f.ID.URI, err)
		}
	}

	for i, c := range f.Contributors {
		if err := c.Check(); err != nil {
			return fmt.Errorf("contributor element %v of feed %v: %v", i, f.ID.URI, err)
		}
	}

	if f.Generator != nil {
		if err := f.Generator.Check(); err != nil {
			return fmt.Errorf("generator element of feed %v: %v", f.ID.URI, err)
		}
	}

	if f.Icon != nil {
		if err := f.Icon.Check(); err != nil {
			return fmt.Errorf("icon element of feed %v: %v", f.ID.URI, err)
		}
	}

	for i, l := range f.Links {
		if err := l.Check(); err != nil {
			return fmt.Errorf("link element %v of feed %v: %v", i, f.ID.URI, err)
		}
	}
	if hasAlternateDuplicateLinks(f.Links) {
		return fmt.Errorf("links with with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in feed %v", f.ID.URI)
	}

	if f.Logo != nil {
		if err := f.Logo.Check(); err != nil {
			return fmt.Errorf("logo element of feed %v: %v", f.ID.URI, err)
		}
	}

	if f.Rights != nil {
		if err := f.Rights.Check(); err != nil {
			return fmt.Errorf("rights element of feed %v: %v", f.ID.URI, err)
		}
	}

	if f.Subtitle != nil {
		if err := f.Subtitle.Check(); err != nil {
			return fmt.Errorf("subtitle element of feed %v: %v", f.ID.URI, err)
		}
	}

	if f.Title == nil {
		return fmt.Errorf("no title element of feed %v", f.ID.URI)
	} else {
		if err := f.Title.Check(); err != nil {
			return fmt.Errorf("title element of feed %v: %v", f.ID.URI, err)
		}
	}

	if f.Updated == nil {
		return fmt.Errorf("no updated element of feed %v", f.ID)
	} else {
		if err := f.Updated.Check(); err != nil {
			return fmt.Errorf("updated element of feed %v: %v", f.ID.URI, err)
		}
	}

	for i, x := range f.Extensions {
		if err := x.Check(); err != nil {
			return fmt.Errorf("extension element %v of feed %v: %v", i, f.ID.URI, err)
		}
	}

	for i, n := range f.Entries {
		if err := n.Check(); err != nil {
			return fmt.Errorf("entry element %v of feed %v: %v", i, f.ID.URI, err)
		}
	}

	return nil
}

// TODO: Create complete link or delete
func (f *Feed) Standardize() {
	if f.Links == nil {
		f.Links = make([]*Link, 1)
		f.Links[0] = &Link{Rel: "self"}
	} else {
		selfExists := false
		for _, l := range f.Links {
			if l.Rel == "self" {
				selfExists = true
				break
			}
		}
		if !selfExists {
			f.Links = append(f.Links, &Link{Rel: "self"})
		}
	}
}

// ToXML converts the feed to XML.
func (f *Feed) ToXML(encoding string) (string, error) {
	xml, err := xml.MarshalIndent(f, "", "  ")
	if err != nil {
		return "", fmt.Errorf("error xml encoding feed: %v", err)
	}

	return fmt.Sprintln(`<?xml version="1.0" encoding="`+encoding+`"?>`) + string(xml), nil
}