package cbconvert

import (
	"bytes"
	"fmt"
	"image"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/fvbommel/sortorder"
	folio "github.com/gen2brain/folio/doc"
)

// coverArchive extracts cover from archive.
func (c *Converter) coverArchive(fileName string) (image.Image, error) {
	var images []string

	contents, err := c.archiveList(fileName)
	if err != nil {
		return nil, fmt.Errorf("coverArchive: %w", err)
	}

	for _, ct := range contents {
		if isImage(ct) {
			images = append(images, ct)
		}
	}

	cover := c.coverName(images)

	data, err := c.archiveFile(fileName, cover)
	if err != nil {
		return nil, fmt.Errorf("coverArchive: %w", err)
	}

	var img image.Image
	img, err = c.imageDecode(bytes.NewReader(data))
	if err != nil {
		return nil, fmt.Errorf("coverArchive: %w", err)
	}

	return img, nil
}

// coverDocument extracts cover from document.
func (c *Converter) coverDocument(fileName string) (image.Image, error) {
	doc, err := folio.Open(fileName)
	if err != nil {
		return nil, fmt.Errorf("coverDocument: %w", err)
	}
	defer doc.Close()

	img, err := c.renderPage(doc, 0)
	if err != nil {
		return nil, fmt.Errorf("coverDocument: %w", err)
	}

	return img, nil
}

// coverDirectory extracts cover from directory.
func (c *Converter) coverDirectory(dir string) (image.Image, error) {
	contents, err := imagesFromPath(dir)
	if err != nil {
		return nil, fmt.Errorf("coverDirectory: %w", err)
	}

	images := imagesFromSlice(contents)
	cover := c.coverName(images)

	file, err := os.Open(cover)
	if err != nil {
		return nil, fmt.Errorf("coverDirectory: %w", err)
	}
	defer file.Close()

	var img image.Image
	img, err = c.imageDecode(file)
	if err != nil {
		return nil, fmt.Errorf("coverDirectory: %w", err)
	}

	return img, nil
}

// pageArchive extracts the page-th image (natural reading order) from an archive.
func (c *Converter) pageArchive(fileName string, page int) (image.Image, error) {
	contents, err := c.archiveList(fileName)
	if err != nil {
		return nil, fmt.Errorf("pageArchive: %w", err)
	}

	images := imagesFromSlice(contents)
	sort.Sort(sortorder.Natural(images))

	if page < 0 || page >= len(images) {
		return nil, fmt.Errorf("pageArchive: page %d out of range (%d pages)", page+1, len(images))
	}

	data, err := c.archiveFile(fileName, images[page])
	if err != nil {
		return nil, fmt.Errorf("pageArchive: %w", err)
	}

	img, err := c.imageDecode(bytes.NewReader(data))
	if err != nil {
		return nil, fmt.Errorf("pageArchive: %w", err)
	}

	return img, nil
}

// pageDocument extracts the page-th rendered page from a document.
func (c *Converter) pageDocument(fileName string, page int) (image.Image, error) {
	doc, err := folio.Open(fileName)
	if err != nil {
		return nil, fmt.Errorf("pageDocument: %w", err)
	}
	defer doc.Close()

	if page < 0 || page >= doc.NumPages() {
		return nil, fmt.Errorf("pageDocument: page %d out of range (%d pages)", page+1, doc.NumPages())
	}

	img, err := c.renderPage(doc, page)
	if err != nil {
		return nil, fmt.Errorf("pageDocument: %w", err)
	}

	return img, nil
}

// pageDirectory extracts the page-th image (natural reading order) from a directory.
func (c *Converter) pageDirectory(dir string, page int) (image.Image, error) {
	contents, err := imagesFromPath(dir)
	if err != nil {
		return nil, fmt.Errorf("pageDirectory: %w", err)
	}

	images := imagesFromSlice(contents)
	sort.Sort(sortorder.Natural(images))

	if page < 0 || page >= len(images) {
		return nil, fmt.Errorf("pageDirectory: page %d out of range (%d pages)", page+1, len(images))
	}

	file, err := os.Open(images[page])
	if err != nil {
		return nil, fmt.Errorf("pageDirectory: %w", err)
	}
	defer file.Close()

	img, err := c.imageDecode(file)
	if err != nil {
		return nil, fmt.Errorf("pageDirectory: %w", err)
	}

	return img, nil
}

// pageImage returns the page-th image of a comic file, document or directory.
func (c *Converter) pageImage(fileName string, fileInfo os.FileInfo, page int) (image.Image, error) {
	var err error
	var img image.Image

	switch {
	case fileInfo.IsDir():
		img, err = c.pageDirectory(fileName, page)
	case isDocument(fileName):
		img, err = c.pageDocument(fileName, page)
	case isArchive(fileName):
		img, err = c.pageArchive(fileName, page)
	}

	if err != nil {
		return nil, fmt.Errorf("pageImage: %w", err)
	}

	return img, nil
}

// PageCount returns the number of pages (images) in a comic file, document or directory.
func (c *Converter) PageCount(fileName string, fileInfo os.FileInfo) (int, error) {
	switch {
	case fileInfo.IsDir():
		contents, err := imagesFromPath(fileName)
		if err != nil {
			return 0, fmt.Errorf("PageCount: %w", err)
		}

		return len(imagesFromSlice(contents)), nil
	case isDocument(fileName):
		doc, err := folio.Open(fileName)
		if err != nil {
			return 0, fmt.Errorf("PageCount: %w", err)
		}
		defer doc.Close()

		return doc.NumPages(), nil
	case isArchive(fileName):
		contents, err := c.archiveList(fileName)
		if err != nil {
			return 0, fmt.Errorf("PageCount: %w", err)
		}

		return len(imagesFromSlice(contents)), nil
	}

	return 0, nil
}

// coverName returns the filename that is the most likely to be the cover.
func (c *Converter) coverName(images []string) string {
	if len(images) == 0 {
		return ""
	}

	lower := make([]string, 0)
	for idx, img := range images {
		img = strings.ToLower(img)
		lower = append(lower, img)
		ext := baseNoExt(img)

		if strings.HasPrefix(img, "cover") || strings.HasPrefix(img, "front") ||
			strings.HasSuffix(ext, "cover") || strings.HasSuffix(ext, "front") {
			return filepath.ToSlash(images[idx])
		}
	}

	sort.Sort(sortorder.Natural(lower))
	cover := lower[0]

	for idx, img := range images {
		img = strings.ToLower(img)
		if img == cover {
			return filepath.ToSlash(images[idx])
		}
	}

	return ""
}

// coverImage returns cover as image.Image.
func (c *Converter) coverImage(fileName string, fileInfo os.FileInfo) (image.Image, error) {
	var err error
	var cover image.Image

	switch {
	case fileInfo.IsDir():
		cover, err = c.coverDirectory(fileName)
	case isDocument(fileName):
		cover, err = c.coverDocument(fileName)
	case isArchive(fileName):
		cover, err = c.coverArchive(fileName)
	}

	if c.OnProgress != nil {
		c.OnProgress()
	}

	if err != nil {
		return nil, fmt.Errorf("coverImage: %w", err)
	}

	return cover, nil
}
