package main

import (
	"crypto/md5"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"time"
)

var db *DB

type Book struct {
	Id          string `bson:"_id"`
	Title       string
	Author      []string
	Contributor string
	Publisher   string
	Description string
	Subject     []string
	Date        string
	Lang        []string
	Isbn        string
	Type        string
	Format      string
	Source      string
	Relation    string
	Coverage    string
	Rights      string
	Meta        string
	File        bson.ObjectId
	Cover       bson.ObjectId
	CoverSmall  bson.ObjectId
	Active      bool
	Keywords    []string
}

type News struct {
	Date time.Time
	Text string
}

type DB struct {
	session *mgo.Session
	books   *mgo.Collection
	user    *mgo.Collection
	news    *mgo.Collection
	stats   *mgo.Collection
	mr      *MR
}

func initDB() *DB {
	var err error
	d := new(DB)
	d.session, err = mgo.Dial(DB_IP)
	if err != nil {
		panic(err)
	}

	database := d.session.DB(DB_NAME)
	d.books = database.C(BOOKS_COLL)
	d.user = database.C(USERS_COLL)
	d.news = database.C(NEWS_COLL)
	d.stats = database.C(STATS_COLL)
	d.mr = NewMR(database)
	return d
}

func (d *DB) Close() {
	d.session.Close()
}

func md5Pass(pass string) []byte {
	h := md5.New()
	hash := h.Sum(([]byte)(PASS_SALT + pass))
	return hash
}

func (d *DB) SetPassword(user string, pass string) error {
	hash := md5Pass(pass)
	return d.user.Update(bson.M{"user": user}, bson.M{"$set": bson.M{"pass": hash}})
}

func (d *DB) UserValid(user string, pass string) bool {
	hash := md5Pass(pass)
	n, err := d.user.Find(bson.M{"user": user, "pass": hash}).Count()
	if err != nil {
		return false
	}
	return n != 0
}

func (d *DB) UserRole(user string) string {
	type result struct {
		Role string
	}
	res := result{}
	err := d.user.Find(bson.M{"user": user}).One(&res)
	if err != nil {
		return ""
	}
	return res.Role
}

func (d *DB) AddNews(text string) error {
	var news News
	news.Text = text
	news.Date = time.Now()
	return d.news.Insert(news)
}

func (d *DB) GetNews(num int, days int) (news []News, err error) {
	query := bson.M{}
	if days != 0 {
		duration := time.Duration(-24*days) * time.Hour
		date := time.Now().Add(duration)
		query = bson.M{"date": bson.M{"$gt": date}}
	}
	q := d.news.Find(query).Sort("-date").Limit(num)
	err = q.All(&news)
	return
}

func (d *DB) InsertStats(stats interface{}) error {
	return d.stats.Insert(stats)
}

func (d *DB) InsertBook(book interface{}) error {
	return d.books.Insert(book)
}

func (d *DB) RemoveBook(id bson.ObjectId) error {
	return d.books.Remove(bson.M{"_id": id})
}

func (d *DB) UpdateBook(id bson.ObjectId, data interface{}) error {
	return d.books.Update(bson.M{"_id": id}, bson.M{"$set": data})
}

/* optional parameters: length and start index
 *
 * Returns: list of books, number found and err
 */
func (d *DB) GetBooks(query bson.M, r ...int) (books []Book, num int, err error) {
	var start, length int
	if len(r) > 0 {
		length = r[0]
		if len(r) > 1 {
			start = r[1]
		}
	}
	q := d.books.Find(query).Sort("-_id")
	num, err = q.Count()
	if err != nil {
		return
	}
	if start != 0 {
		q = q.Skip(start)
	}
	if length != 0 {
		q = q.Limit(length)
	}

	err = q.All(&books)
	for i, b := range books {
		books[i].Id = bson.ObjectId(b.Id).Hex()
	}
	return
}

/* Get the most visited books
 */
func (d *DB) GetVisitedBooks(num int) (books []Book, err error) {
	bookId, err := d.mr.GetMostVisited(num, d.stats)
	if err != nil {
		return nil, err
	}

	books = make([]Book, num)
	for i, id := range bookId {
		d.books.Find(bson.M{"_id": id}).One(&books[i])
		books[i].Id = bson.ObjectId(books[i].Id).Hex()
	}
	return
}

/* Get the most downloaded books
 */
func (d *DB) GetDownloadedBooks(num int) (books []Book, err error) {
	bookId, err := d.mr.GetMostDownloaded(num, d.stats)
	if err != nil {
		return nil, err
	}

	books = make([]Book, num)
	for i, id := range bookId {
		d.books.Find(bson.M{"_id": id}).One(&books[i])
		books[i].Id = bson.ObjectId(books[i].Id).Hex()
	}
	return
}

/* optional parameters: length and start index
 *
 * Returns: list of books, number found and err
 */
func (d *DB) GetNewBooks(r ...int) (books []Book, num int, err error) {
	return d.GetBooks(bson.M{"$nor": []bson.M{{"active": true}}}, r...)
}

func (d *DB) BookActive(id bson.ObjectId) bool {
	var book Book
	err := d.books.Find(bson.M{"_id": id}).One(&book)
	if err != nil {
		return false
	}
	return book.Active
}

func (d *DB) GetFS(prefix string) *mgo.GridFS {
	return d.session.DB(DB_NAME).GridFS(prefix)
}

func (d *DB) GetTags(numTags int) ([]string, error) {
	return d.mr.GetTags(numTags, d.books)
}

type Visits struct {
	Date  int64 "_id"
	Count int   "value"
}

func (d *DB) GetHourVisits(start time.Time) ([]Visits, error) {
	return d.mr.GetHourVisits(start, d.stats)
}

func (d *DB) GetDayVisits(start time.Time) ([]Visits, error) {
	return d.mr.GetDayVisits(start, d.stats)
}

func (d *DB) GetMonthVisits(start time.Time) ([]Visits, error) {
	return d.mr.GetMonthVisits(start, d.stats)
}