package log

import (
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type Writer struct {
	pre  string
	suf  string
	path string

	date string

	cur *os.File
	mu  sync.Mutex
}

// NewRawWriter 新建日志写入接口
// per 和 suf 分别作为文件名的前缀和后缀, path 为文件所存放的目录(e.g. /var/log)
func NewRawWriter(pre, suf, path string) (io.WriteCloser, error) {
	if err := handlePath(path); err != nil {
		return nil, err
	}
	w := new(Writer)
	w.pre = pre
	w.suf = suf
	w.path = filepath.Join(path)

	w.date = getDate()
	w.cur = (*os.File)(nil)
	return w, nil
}

// NewWriter 新建日志写入接口
// per 和 suf 分别作为文件名的前缀和后缀, path 为文件所存放的目录(e.g. /var/log)
// 与 NewRawWriter 不同: 通过 NewWriter 创建的文件会被缓存文件句柄. 对于通过 NewWriter 已创建的文件, 当再次通过 NewWriter 创建相同的
// 文件时会返回之前已创建的句柄. 可以缓解 Too many open files 的问题
func NewWriter(pre, suf, path string) (io.WriteCloser, error) {
	return _socketCache.Get(pre, suf, path)
}

func (w *Writer) Write(p []byte) (n int, err error) {
	if date := getDate(); date != w.date {
		if err = w.Close(); err != nil {
			return 0, err
		}
		if err = w.open(); err != nil {
			return 0, err
		}
		w.date = date
	}

	if w.cur == nil {
		if err = w.open(); err != nil {
			return 0, err
		}
		return w.Write(p)
	}

	w.mu.Lock()
	n, err = w.cur.Write(p)
	w.mu.Unlock()
	return
}

// Close 关闭 socket
func (w *Writer) Close() error {
	w.mu.Lock()
	err := w.cur.Close()
	w.cur = (*os.File)(nil)
	w.mu.Unlock()
	return err
}

func (w *Writer) open() error {
	fi, err := os.OpenFile(w.curName(), os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm)
	if err != nil {
		return err
	}
	w.mu.Lock()
	w.cur = fi
	w.mu.Unlock()
	return nil
}

func (w *Writer) curName() string {
	return filepath.Join(w.path, w.pre+"_"+w.date+w.suf)
}

func getDate() string {
	return time.Now().Format("2006_01_02")
}

func handlePath(path string) error {
	if _, err := os.Stat(path); err != nil {
		if err = os.MkdirAll(path, os.ModePerm); err != nil {
			return err
		}
		return err
	}
	return nil
}

// TODO 潜在的安全风险: 文件句柄会被保存在 map 中, 即使调用 Close 关闭文件句柄后 map 内依然会保存指针
// TODO 随着程序长时间不间断运行, 因此会导致内存泄露. 但由于每日仅创建一个文件, 内存泄露在可控范围内. 因此 此问题暂不做处理
type socketCache struct {
	cache map[string]io.WriteCloser
	mu    sync.Mutex
}

func (s *socketCache) Get(pre, suf, path string) (io.WriteCloser, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	name := pre + suf + path

	if cache, ok := s.cache[name]; ok {
		return cache, nil
	}

	w, err := NewRawWriter(pre, suf, path)
	if err != nil {
		return nil, err
	}
	s.cache[name] = w

	return w, nil
}

var (
	_socketCache = socketCache{cache: make(map[string]io.WriteCloser)}
)
