// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package public

import (
	"bytes"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"gogs.io/gogs/internal/assets"
)

//go:generate go-bindata -nomemcopy -pkg=public -ignore="\\.DS_Store|less" -prefix=../../../public -debug=false -o=public_gen.go ../../../public/...

/*
	This file is a modified version of https://github.com/go-bindata/go-bindata/pull/18.
*/

type fileInfo struct {
	name string
	size int64
}

func (d fileInfo) Name() string {
	return d.name
}

func (d fileInfo) Size() int64 {
	return d.size
}

func (d fileInfo) Mode() os.FileMode {
	return os.FileMode(0644) | os.ModeDir
}

func (d fileInfo) ModTime() time.Time {
	return time.Time{}
}

// IsDir return file whether a directory
func (d *fileInfo) IsDir() bool {
	return true
}

func (d fileInfo) Sys() interface{} {
	return nil
}

// file implements the http.File interface.
type file struct {
	name string
	*bytes.Reader

	children       []os.FileInfo
	childrenOffset int
}

func (f *file) Close() error {
	return nil
}

// ⚠️ WARNING: This method is not concurrent-safe.
func (f *file) Readdir(count int) ([]os.FileInfo, error) {
	if len(f.children) == 0 {
		return nil, os.ErrNotExist
	}

	if count <= 0 {
		return f.children, nil
	}

	if f.childrenOffset+count > len(f.children) {
		count = len(f.children) - f.childrenOffset
	}
	offset := f.childrenOffset
	f.childrenOffset += count
	return f.children[offset : offset+count], nil
}

func (f *file) Stat() (os.FileInfo, error) {
	childCount := len(f.children)
	if childCount != 0 {
		return &fileInfo{
			name: f.name,
			size: int64(childCount),
		}, nil
	}
	return AssetInfo(f.name)
}

// fileSystem implements the http.FileSystem interface.
type fileSystem struct{}

func (f *fileSystem) Open(name string) (http.File, error) {
	if len(name) > 0 && name[0] == '/' {
		name = name[1:]
	}

	// Attempt to get it as a file
	p, err := Asset(name)
	if err != nil && !assets.IsErrNotFound(err) {
		return nil, err
	} else if err == nil {
		return &file{
			name:   name,
			Reader: bytes.NewReader(p),
		}, nil
	}

	// Attempt to get it as a directory
	paths, err := AssetDir(name)
	if err != nil && !assets.IsErrNotFound(err) {
		return nil, err
	}

	infos := make([]os.FileInfo, len(paths))
	for i, path := range paths {
		path = filepath.Join(name, path)
		info, err := AssetInfo(path)
		if err != nil {
			if !assets.IsErrNotFound(err) {
				return nil, err
			}
			// Not found as a file, assume it's a directory.
			infos[i] = &fileInfo{name: path}
		} else {
			infos[i] = info
		}
	}
	return &file{
		name:     name,
		children: infos,
	}, nil
}

// NewFileSystem returns an http.FileSystem instance backed by embedded assets.
func NewFileSystem() http.FileSystem {
	return &fileSystem{}
}