package main

import (
	"fmt"
	"plugin"
	"time"
)

type FeedSource struct {
	Feeds []*Feed

	// Plugin Functions
	getSourceName   func() string
	refreshFeedList func() []map[string]string
	getFeedUrl      func(string, time.Time) (string, error)
	getFeedDesc     func(string, time.Time) (string, error)
}

func LoadFeedPlugin(p *plugin.Plugin) (*FeedSource, error) {
	ret := new(FeedSource)
	feedSourceSymbol, err := p.Lookup("GetSourceName")
	if err != nil {
		return nil, err
	}
	ret.getSourceName = feedSourceSymbol.(func() string)

	feedListSymbol, err := p.Lookup("GetFeedList")
	if err != nil {
		return nil, err
	}
	ret.refreshFeedList = feedListSymbol.(func() []map[string]string)
	feeds := ret.refreshFeedList()
	// `feeds` is a []map, each map should contain keys: `slug`, `name`, `author`, `source`
	for _, f := range feeds {
		var slug, name, author, source string
		var ok bool
		if slug, ok = f["slug"]; !ok {
			continue
		}
		if name, ok = f["name"]; !ok {
			continue
		}
		if author, ok = f["author"]; !ok {
			continue
		}
		if source, ok = f["source"]; !ok {
			continue
		}
		ret.Feeds = append(ret.Feeds, NewFeed(slug, name, author, source))
	}

	feedUrlSymbol, err := p.Lookup("GetFeedUrl")
	if err != nil {
		return nil, err
	}
	ret.getFeedUrl = feedUrlSymbol.(func(string, time.Time) (string, error))

	feedDescSymbol, err := p.Lookup("GetFeedDesc")
	if err != nil {
		return nil, err
	}
	ret.getFeedDesc = feedDescSymbol.(func(string, time.Time) (string, error))

	return ret, nil
}

func (f *FeedSource) getFeed(slug string) *Feed {
	for i := range f.Feeds {
		if f.Feeds[i].Slug == slug {
			return f.Feeds[i]
		}
	}
	return nil
}

type Feed struct {
	Name       string
	Author     string
	Slug       string
	Source     string
	Desc       string
	LastUpdate time.Time
}

func NewFeed(s, n, a, source string) *Feed {
	return &Feed{
		Name:   n,
		Author: a,
		Slug:   s,
		Source: source,
	}
}

func (f *Feed) GetBucket() []string {
	return []string{"feeds", f.Source, f.Slug}
}

func (f *Feed) Update() error {
	/*
		dt := time.Now()
		desc, err := getFeedDesc(f.Source, f.Slug, dt)
		if err != nil {
			return err
		}
		if desc == f.Desc {
			return errors.New("Feed didn't change")
		}
		f.Desc = desc
		f.LastUpdate = dt
	*/
	return nil
}

func (f *Feed) GetUrl(dt time.Time) string {
	var v string
	/*
		var e error
		if v, e = getFeedUrl(f.Source, f.Slug, dt); e != nil {
			return ""
		}
	*/
	return v
}

func (f *Feed) GetDesc(dt time.Time) string {
	var v string
	/*
		var e error
		if v, e = getFeedDesc(f.Source, f.Slug, dt); e != nil {
			return ""
		}
	*/
	return v
}

func (f *Feed) GetRssItem() string {
	var v string
	/*
		var e error
		if v, e = getRssItem(f.Source, f.Slug); e != nil {
			return ""
		}
	*/
	return v
}

// DB Function to save a feed
func (m *model) SaveFeed(f *Feed) error {
	var err error
	if err = m.openDB(); err != nil {
		return err
	}
	defer m.closeDB()
	bkt := f.GetBucket()
	if err = m.bolt.MkBucketPath(bkt); err != nil {
		return err
	}
	if err = m.bolt.SetValue(bkt, "name", f.Name); err != nil {
		return err
	}
	if err = m.bolt.SetValue(bkt, "author", f.Author); err != nil {
		return err
	}
	if err = m.bolt.SetValue(bkt, "desc", f.Desc); err != nil {
		return err
	}
	if err = m.bolt.SetTimestamp(bkt, "lastupdate", f.LastUpdate); err != nil {
		return err
	}
	// Add it to the cached list
	for i, v := range m.Feeds {
		if v.Source == f.Source && v.Slug == f.Slug {
			m.Feeds[i] = *f
			return nil
		}
	}
	m.Feeds = append(m.Feeds, *f)
	return nil
}

// DB Function to get a feed
func (m *model) GetFeed(source, slug string) (*Feed, error) {
	var err error
	if err = m.openDB(); err != nil {
		return nil, err
	}
	defer m.closeDB()
	ret := new(Feed)
	ret.Source = source
	ret.Slug = slug
	bkt := ret.GetBucket()
	if ret.Name, err = m.bolt.GetValue(bkt, "name"); err != nil {
		return nil, err
	}
	if ret.Author, err = m.bolt.GetValue(bkt, "author"); err != nil {
		return nil, err
	}
	if ret.Desc, err = m.bolt.GetValue(bkt, "desc"); err != nil {
		return nil, err
	}
	if ret.LastUpdate, err = m.bolt.GetTimestamp(bkt, "lastupdate"); err != nil {
		return nil, err
	}
	return ret, nil
}

// Load all feeds into the model
func (m *model) LoadFeeds() error {
	m.Feeds = m.GetAllFeeds()
	return nil
}

// Save all feeds to the DB
func (m *model) SaveAllFeeds(feedSources []FeedSource) {
	var err error
	if err = m.openDB(); err != nil {
		return
	}
	defer m.closeDB()
	for _, v := range feedSources {
		for j := range v.Feeds {
			fmt.Printf("Saving Feed to DB (%s;%s)\n", v.Feeds[j].Source, v.Feeds[j].Slug)
			m.SaveFeed(v.Feeds[j])
		}
	}
}

// Get all feeds from the db
func (m *model) GetAllFeeds() []Feed {
	var ret []Feed
	var err error
	if err = m.openDB(); err != nil {
		return ret
	}
	defer m.closeDB()
	var srcs []string
	bkt := []string{"feeds"}
	if srcs, err = m.bolt.GetBucketList(bkt); err != nil {
		return ret
	}
	for _, src := range srcs {
		srcBkt := append(bkt, src)
		var slugs []string
		if slugs, err = m.bolt.GetBucketList(srcBkt); err != nil {
			return ret
		}
		for _, slg := range slugs {
			c, err := m.GetFeed(src, slg)
			if err == nil {
				ret = append(ret, *c)
			}
		}
	}
	return ret
}

// Delete a feed from the DB
func (m *model) DeleteFeed(slug string) error {
	var err error
	if err = m.openDB(); err != nil {
		return err
	}
	defer m.closeDB()

	return m.bolt.DeleteBucket([]string{"feeds"}, slug)
}

func (m *model) RemoveMissingFeeds(feeds []Feed) {
	for _, c := range m.Feeds {
		var fnd bool
		for _, nc := range feeds {
			if nc.Slug == c.Slug {
				fnd = true
				break
			}
		}
		if !fnd {
			m.DeleteFeed(c.Slug)
		}
	}
}
