package log

import (
	"io"
	"log"
	"net"
	"os"
	"path/filepath"
	"sync"
)

const (
	ServerMaxSize = 4194304 // 4MB
)

type ServerWriter struct {
	Filepath string
	W        map[string]io.Writer
	Run      io.Writer
	Err      io.Writer

	mu sync.Mutex
}

func (l *ServerWriter) Write(p []byte) (n int, err error) {
	l.mu.Lock()
	defer l.mu.Unlock()

	prefix := spitPrefix(string(p))
	if lg, ok := l.W[prefix]; ok {
		return lg.Write(p)
	}

	level := buildPrefix(prefix)
	switch level {
	case LevelsError, LevelsWarn, LevelsInfo, LevelsDebug:
		if level == LevelsError || level == LevelsWarn {
			n, err = l.Err.Write(p)
		}
		n, err = l.Run.Write(p)
	default:
		w := NewFileWriter(prefix, filepath.Join(l.Filepath, prefix))
		n, err = w.Write(p)
		l.W[prefix] = w

	}
	return
}

func NewServerWriter() *ServerWriter {
	sw := new(ServerWriter)
	sw.W = make(map[string]io.Writer)
	return sw
}

type Server struct {
	W    io.Writer
	Conn net.PacketConn
}

func (c *Server) handle(b []byte) {
	_, err := c.W.Write(b)
	if err != nil {
		_, _ = os.Stdout.Write(b)
	}
}

func (c *Server) Close() error {
	return c.Conn.Close()
}

func (c *Server) ListenAndServe() error {
	defer func() {
		_ = c.Close()
	}()
	for {
		b := make([]byte, ServerMaxSize)
		n, _, err := c.Conn.ReadFrom(b)
		if err != nil {
			log.Println("ReadFrom:", err)
			continue
		}
		go c.handle(b[:n])
	}
}

func NewServer(address, path string) (*Server, error) {
	sw := NewServerWriter()
	sw.Filepath = path
	sw.Run = NewFileWriter("r", filepath.Join(path, "run"))
	sw.Err = NewFileWriter("e", filepath.Join(path, "err"))
	s := new(Server)
	s.W = sw
	var err error
	s.Conn, err = net.ListenPacket("udp", address)
	if err != nil {
		return nil, err
	}
	return s, nil
}

func NewClientLogger(address string) (Logger, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		return nil, err
	}
	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		return nil, err
	}
	return NewLog(LevelDebug, []io.Writer{conn}, "", 1, 0), nil
}

func NewClientPrinter(prefix, address string) (Printer, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		return nil, err
	}
	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		return nil, err
	}
	return NewLog(LevelDebug, []io.Writer{conn}, prefix, 1, 0), nil
}
