package mongo

import (
	"github.com/dalu/wiki/storage"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const (
	databaseDefault          = "wiki"
	termCollectionDefault    = "term"
	revisionCollectionDefault = "revision"
)

type mongoStorage struct {
	ms                *mgo.Session
	database          string
	termCollection    string
	revisionCollection string
}

type Config struct {
	Database          string
	TermCollection    string
	RevisionCollection string
}

func New(ms *mgo.Session, config Config) mongoStorage {
	if config.Database == "" {
		config.Database = databaseDefault
	}
	if config.TermCollection == "" {
		config.TermCollection = termCollectionDefault
	}
	if config.RevisionCollection == "" {
		config.RevisionCollection = revisionCollectionDefault
	}

	return mongoStorage{
		ms:                ms.Clone(),
		database:          config.Database,
		termCollection:    config.TermCollection,
		revisionCollection: config.RevisionCollection,
	}
}

// --- Term ---

func (s mongoStorage) CreateTerm(term *storage.Term) error {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.termCollection)
	return c.Insert(term)
}

func (s mongoStorage) UpdateTerm(term *storage.Term) error {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.termCollection)
	return c.UpdateId(term.ID, term)
}

func (s mongoStorage) RemoveTerm(term *storage.Term) error {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.termCollection)
	return c.RemoveId(term.ID)
}

func (s mongoStorage) GetTermByName(name string) (*storage.Term, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.termCollection)
	term := new(storage.Term)
	if e := c.Find(bson.M{"name": name}).One(term); e != nil {
		return nil, e
	}
	return term, nil
}

func (s mongoStorage) GetTermBySlug(slug string) (*storage.Term, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.termCollection)
	term := new(storage.Term)
	if e := c.Find(bson.M{"slug": slug}).One(term); e != nil {
		return nil, e
	}
	return term, nil
}

func (s mongoStorage) GetTermByID(id string) (*storage.Term, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.termCollection)
	term := new(storage.Term)
	if e := c.FindId(bson.ObjectIdHex(id)).One(term); e != nil {
		return nil, e
	}
	return term, nil
}

func (s mongoStorage) GetTerms() ([]*storage.Term, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.termCollection)
	terms := []*storage.Term{}
	if e := c.Find(nil).All(&terms); e != nil {
		return terms, e
	}
	return terms, nil
}

// --- Revision ---

func (s mongoStorage) CreateRevision(Revision *storage.Revision) error {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.revisionCollection)
	return c.Insert(Revision)
}

func (s mongoStorage) RemoveRevision(Revision *storage.Revision) error {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.revisionCollection)
	return c.RemoveId(Revision.ID)
}

func (s mongoStorage) RemoveRevisionsByTermID(termID string) error {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.revisionCollection)
	_, e := c.RemoveAll(bson.M{"term_id": termID})
	if e != nil {
		return e
	}
	return nil
}

func (s mongoStorage) GetRevisionByTermName(termName string) (*storage.Revision, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.revisionCollection)
	term, e := s.GetTermByName(termName)
	if e != nil {
		return nil, e
	}
	Revision := new(storage.Revision)
	if e := c.Find(bson.M{"term_id": term.ID.Hex()}).One(Revision); e != nil {
		return nil, e
	}
	return Revision, nil
}

func (s mongoStorage) GetRevisionByTermSlug(termSlug string) (*storage.Revision, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.revisionCollection)
	term, e := s.GetTermByName(termSlug)
	if e != nil {
		return nil, e
	}
	Revision := new(storage.Revision)
	if e := c.Find(bson.M{"term_id": term.ID.Hex()}).One(Revision); e != nil {
		return nil, e
	}
	return Revision, nil
}

func (s mongoStorage) GetRevisionByID(id string) (*storage.Revision, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.revisionCollection)
	Revision := new(storage.Revision)
	if e := c.FindId(bson.ObjectIdHex(id)).One(Revision); e != nil {
		return nil, e
	}
	return Revision, nil
}

func (s mongoStorage) GetRevisionByTermID(termID string) (*storage.Revision, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.revisionCollection)
	term, e := s.GetTermByID(termID)
	if e != nil {
		return nil, e
	}
	Revision := new(storage.Revision)
	if e := c.Find(bson.M{"term_id": term.ID.Hex()}).One(Revision); e != nil {
		return nil, e
	}
	return Revision, nil
}

func (s mongoStorage) GetRevisions() ([]*storage.Revision, error) {
	ms := s.ms.Copy()
	defer ms.Close()
	c := ms.DB(s.database).C(s.revisionCollection)
	terms := []*storage.Revision{}
	if e := c.Find(nil).All(&terms); e != nil {
		return terms, e
	}
	return terms, nil
}
