package main

import (
	// External
	"github.com/jmoiron/sqlx"

	// Standard
	"fmt"
	"os"
	"path"
	"time"
)

type File struct {
	ID             int
	UUID           string
	UserID         int    `db:"user_id"`
	Name           string `db:"name"`
	Size           int
	Type           string `db:"type"`
	Modified       time.Time
	UploadComplete bool `db:"upload_complete"`
}

var (
	fileUUIDCache map[string]int
)

func init() {
	fileUUIDCache = make(map[string]int)
}

func AddFileToDb(userID int, file *File) (err error) { // {{{
	file.UserID = userID

	var rows *sqlx.Rows
	rows, err = db.Queryx(`
		INSERT INTO public.file(user_id, uuid, name, size, type, modified, upload_complete)
		VALUES($1, $2, $3, $4, $5, $6, false)
		ON CONFLICT (user_id, uuid) DO UPDATE set name = public.file.name
		RETURNING id
		`,
		file.UserID,
		file.UUID,
		file.Name,
		file.Size,
		file.Type,
		file.Modified,
	)
	if err != nil {
		return
	}
	defer rows.Close()

	rows.Next()
	err = rows.Scan(&file.ID)
	return
} // }}}
func Files(userID int, nodeUUID string, fileID int) (files []File, err error) { // {{{
	var rows *sqlx.Rows
	rows, err = db.Queryx(
		`SELECT *
		FROM file
		WHERE
			user_id = $1 AND
			node_uuid = $2 AND
			CASE $3::int
				WHEN 0 THEN true
				ELSE id = $3
			END`,
		userID,
		nodeUUID,
		fileID,
	)
	if err != nil {
		return
	}
	defer rows.Close()

	files = []File{}
	for rows.Next() {
		file := File{}
		if err = rows.StructScan(&file); err != nil {
			return
		}
		files = append(files, file)
	}

	return
} // }}}
func FileIDToPath(id int) (string, string) { // {{{
	str := fmt.Sprintf("%012d", id)
	dir := path.Join(config.Storage.Files, str[0:4], str[4:8])
	fpath := path.Join(config.Storage.Files, str[0:4], str[4:8], str[8:12])
	return dir, fpath
} // }}}

func FileWriteData(userID int, dbID int, data []byte, create bool) (err error) { // {{{
	dir, fpath := FileIDToPath(dbID)
	err = os.MkdirAll(dir, 0700)
	if err != nil {
		Log.Error("file", "error", err)
		return
	}

	var f *os.File
	if create {
		f, err = os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
	} else {
		f, err = os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	}
	if err != nil {
		Log.Error("file", "error", err)
		return
	}
	defer f.Close()

	_, err = f.Write(data)
	if err != nil {
		Log.Error("file", "error", err)
		return
	}

	return
} // }}}

func FileCountForUser(userID int) (count int, err error) { // {{{
	row := db.QueryRowx(`SELECT COUNT(*) FROM public.file WHERE user_id = $1`, userID)
	err = row.Scan(&count)
	return
} // }}}
func FileServerUUIDsForUser(userID, page int) (uuids []string, more bool, err error) { // {{{
	uuids = []string{}

	var rows *sqlx.Rows
	rows, err = db.Queryx(`
		SELECT
			uuid
		FROM public.file
		WHERE
			user_id = $1
		ORDER BY
			uuid ASC
		LIMIT $2 OFFSET $3
		`,
		userID,
		SYNC_PAGINATION+1,
		page*SYNC_PAGINATION,
	)
	if err != nil {
		return
	}
	defer rows.Close()

	var uuid string
	for rows.Next() {
		err = rows.Scan(&uuid)
		if err != nil {
			return
		}
		uuids = append(uuids, uuid)
	}

	more = len(uuids) > SYNC_PAGINATION
	uuids = uuids[0:min(SYNC_PAGINATION, len(uuids))]

	return
} // }}}
func FileMetadata(userID int, uuid string) (f File, err error) { // {{{
	row := db.QueryRowx(`SELECT * FROM public.file WHERE user_id = $1 AND "uuid" = $2`, userID, uuid)
	err = row.StructScan(&f)
	return

} // }}}
func FileForUser(userID int, uuid string) (f *os.File, md File, err error) { // {{{
	row := db.QueryRowx(`SELECT * FROM public.file WHERE user_id = $1 AND "uuid" = $2`, userID, uuid)
	err = row.StructScan(&md)
	if err != nil {
		return
	}

	_, fpath := FileIDToPath(md.ID)
	f, err = os.OpenFile(fpath, os.O_RDONLY, 0600)
	return

} // }}}

// vim: foldmethod=marker
