// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows

package logging

import (
	"fmt"
	"log"
	"strconv"

	syslog "github.com/RackSec/srslog"
)

// Errors related to parsing priority
var (
	ErrPriorityNoStart  = fmt.Errorf("No start char found for priority")
	ErrPriorityEmpty    = fmt.Errorf("Priority field empty")
	ErrPriorityNoEnd    = fmt.Errorf("No end char found for priority")
	ErrPriorityTooShort = fmt.Errorf("Priority field too short")
	ErrPriorityTooLong  = fmt.Errorf("Priority field too long")
	ErrPriorityNonDigit = fmt.Errorf("Non digit found in priority")
)

// Priority header and ending characters
const (
	PRI_PART_START = '<'
	PRI_PART_END   = '>'
)

// SyslogMessage represents a log line received
type SyslogMessage struct {
	Message  []byte
	Severity syslog.Priority
}

// Priority holds all the priority bits in a syslog log line
type Priority struct {
	Pri      int
	Facility syslog.Priority
	Severity syslog.Priority
}

// DockerLogParser parses a line of log message that the docker daemon ships
type DockerLogParser struct {
	logger *log.Logger
}

// NewDockerLogParser creates a new DockerLogParser
func NewDockerLogParser(logger *log.Logger) *DockerLogParser {
	return &DockerLogParser{logger: logger}
}

// Parse parses a syslog log line
func (d *DockerLogParser) Parse(line []byte) *SyslogMessage {
	pri, _, _ := d.parsePriority(line)
	msgIdx := d.logContentIndex(line)

	// Create a copy of the line so that subsequent Scans do not override the
	// message
	lineCopy := make([]byte, len(line[msgIdx:]))
	copy(lineCopy, line[msgIdx:])

	return &SyslogMessage{
		Severity: pri.Severity,
		Message:  lineCopy,
	}
}

// logContentIndex finds out the index of the start index of the content in a
// syslog line
func (d *DockerLogParser) logContentIndex(line []byte) int {
	cursor := 0
	numSpace := 0
	numColons := 0
	// first look for at least 2 colons. This matches into the date that has no more spaces in it
	// DefaultFormatter log line look: '<30>2016-07-06T15:13:11Z00:00 hostname docker/9648c64f5037[16200]'
	// UnixFormatter log line look: '<30>Jul  6 15:13:11 docker/9648c64f5037[16200]'
	for i := 0; i < len(line); i++ {
		if line[i] == ':' {
			numColons += 1
			if numColons == 2 {
				cursor = i
				break
			}
		}
	}
	// then look for the next space
	for i := cursor; i < len(line); i++ {
		if line[i] == ' ' {
			numSpace += 1
			if numSpace == 1 {
				cursor = i
				break
			}
		}
	}
	// then the colon is what separates it, followed by a space
	for i := cursor; i < len(line); i++ {
		if line[i] == ':' && i+1 < len(line) && line[i+1] == ' ' {
			cursor = i + 1
			break
		}
	}
	// return the cursor to the next character
	return cursor + 1
}

// parsePriority parses the priority in a syslog message
func (d *DockerLogParser) parsePriority(line []byte) (Priority, int, error) {
	cursor := 0
	pri := d.newPriority(0)
	if len(line) <= 0 {
		return pri, cursor, ErrPriorityEmpty
	}
	if line[cursor] != PRI_PART_START {
		return pri, cursor, ErrPriorityNoStart
	}
	i := 1
	priDigit := 0
	for i < len(line) {
		if i >= 5 {
			return pri, cursor, ErrPriorityTooLong
		}
		c := line[i]
		if c == PRI_PART_END {
			if i == 1 {
				return pri, cursor, ErrPriorityTooShort
			}
			cursor = i + 1
			return d.newPriority(priDigit), cursor, nil
		}
		if d.isDigit(c) {
			v, e := strconv.Atoi(string(c))
			if e != nil {
				return pri, cursor, e
			}
			priDigit = (priDigit * 10) + v
		} else {
			return pri, cursor, ErrPriorityNonDigit
		}
		i++
	}
	return pri, cursor, ErrPriorityNoEnd
}

// isDigit checks if a byte is a numeric char
func (d *DockerLogParser) isDigit(c byte) bool {
	return c >= '0' && c <= '9'
}

// newPriority creates a new default priority
func (d *DockerLogParser) newPriority(p int) Priority {
	// The Priority value is calculated by first multiplying the Facility
	// number by 8 and then adding the numerical value of the Severity.
	return Priority{
		Pri:      p,
		Facility: syslog.Priority(p / 8),
		Severity: syslog.Priority(p % 8),
	}
}
