// Copyright 2014 Vic Demuzere
//
// Use of this source code is governed by the MIT license.

package irc

import (
	"bufio"
	"io"
	"net"
	"sync"
)

// Messages are delimited with CR and LF line endings,
// we're using the last one to split the stream. Both are removed
// during message parsing.
const delim byte = '\n'

var endline = []byte("\r\n")

// A Conn represents an IRC network protocol connection.
// It consists of an Encoder and Decoder to manage I/O.
type Conn struct {
	Encoder
	Decoder

	conn io.ReadWriteCloser
}

// NewConn returns a new Conn using rwc for I/O.
func NewConn(rwc io.ReadWriteCloser) *Conn {
	return &Conn{
		Encoder: Encoder{
			writer: rwc,
		},
		Decoder: Decoder{
			reader: bufio.NewReader(rwc),
		},
		conn: rwc,
	}
}

// Dial connects to the given address using net.Dial and
// then returns a new Conn for the connection.
func Dial(addr string) (*Conn, error) {
	c, err := net.Dial("tcp", addr)

	if err != nil {
		return nil, err
	}

	return NewConn(c), nil
}

// Close closes the underlying ReadWriteCloser.
func (c *Conn) Close() error {
	return c.conn.Close()
}

// A Decoder reads Message objects from an input stream.
type Decoder struct {
	reader *bufio.Reader
	line   string
	mu     sync.Mutex
}

// NewDecoder returns a new Decoder that reads from r.
func NewDecoder(r io.Reader) *Decoder {
	return &Decoder{
		reader: bufio.NewReader(r),
	}
}

// Decode attempts to read a single Message from the stream.
//
// Returns a non-nil error if the read failed.
func (dec *Decoder) Decode() (m *Message, err error) {

	dec.mu.Lock()
	dec.line, err = dec.reader.ReadString(delim)
	dec.mu.Unlock()

	if err != nil {
		return nil, err
	}

	return ParseMessage(dec.line), nil
}

// An Encoder writes Message objects to an output stream.
type Encoder struct {
	writer io.Writer
	mu     sync.Mutex
}

// NewEncoder returns a new Encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{
		writer: w,
	}
}

// Encode writes the IRC encoding of m to the stream.
//
// This method may be used from multiple goroutines.
//
// Returns an non-nil error if the write to the underlying stream stopped early.
func (enc *Encoder) Encode(m *Message) (err error) {

	_, err = enc.Write(m.Bytes())

	return
}

// Write writes len(p) bytes from p followed by CR+LF.
//
// This method can be used simultaneously from multiple goroutines,
// it guarantees to serialize access. However, writing a single IRC message
// using multiple Write calls will cause corruption.
func (enc *Encoder) Write(p []byte) (n int, err error) {

	enc.mu.Lock()
	n, err = enc.writer.Write(p)

	if err != nil {
		enc.mu.Unlock()
		return
	}

	_, err = enc.writer.Write(endline)
	enc.mu.Unlock()

	return
}