func (c *IrcClient) extractMessages(buf []byte) (int, bool) {
	send := func(chunk []byte) bool {
		cpy := make([]byte, len(chunk)-2)
		copy(cpy, chunk[:len(chunk)-2])
		select {
		case c.siphonchan <- cpy:
			c.log.Debug(string(cpy), "direction", "recv")
			return false
		case c.killsiphon <- nil:
			return true
		}
	}
	start, remaining, abort := findChunks(buf, send)
	if abort {
		return 0, true
	} else if remaining {
		copy(buf[:len(buf)-start], buf[start:])
		return len(buf) - start, false
	}
	return 0, false
}
extractMessages takes the information in a buffer and splits on \r\n pairs.
When it encounters a pair, it creates a copy of the data from start to the
pair and passes it into the readchan from the IrcClient. If no \r\n is found
but data is still present in the buffer, it moves this data to the front of
the buffer and returns an index from which the next read should be started at

Note:
the reason for the copy is because of the threadedness, the buffer pointed to
by the slice can be immediately filled with new information once this
function returns and therefore a copy must be made for thread safety.
package inet
import (
	"bytes"
	"io"
	"net"
	"sync"
	"time"
	"gopkg.in/inconshreveable/log15.v2"
)
const (
	bufferSize = 16348
	nBufferedWrites = 25
	defaultTimeScale = time.Second
)
var (
	pong = []byte("PONG")
	ping = []byte("PING :ping\r\n")
)
type ClientError struct {
	Socket error
	Pump   error
	Siphon error
}
func (c ClientError) CheckNeeded() (err error) {
	if c.Pump != nil || c.Siphon != nil || c.Socket != nil {
		err = c
	}
	return
}
func (c ClientError) Error() string {
	var b bytes.Buffer
	var wrote bool
	if c.Socket != nil {
		b.WriteString("Socket: ")
		b.WriteString(c.Socket.Error())
		wrote = true
	}
	if c.Pump != nil {
		if wrote {
			b.WriteString(" || ")
		}
		b.WriteString("Pump: ")
		b.WriteString(c.Pump.Error())
		wrote = true
	}
	if c.Siphon != nil {
		if wrote {
			b.WriteString(" || ")
		}
		b.WriteString("Siphon: ")
		b.WriteString(c.Siphon.Error())
	}
	return b.String()
}
type IrcClient struct {
	isShutdown        bool
	isShutdownProtect sync.RWMutex
	conn        net.Conn
	siphonchan  chan []byte
	pumpchan    chan []byte
	pumpservice chan chan []byte
	killpump    chan error
	killsiphon  chan error
	queue       Queue
	log log15.Logger
	lastwrite        time.Time
	penalty          time.Time
	timeout          time.Duration
	basestep         time.Duration
	lenPenaltyFactor float64
	scale time.Duration
	keepalive time.Duration
	readbuf []byte
	pos     int
}
func createIrcClient(conn net.Conn, logger log15.Logger) *IrcClient {
	if logger == nil {
		logger = log15.New()
		logger.SetHandler(log15.DiscardHandler())
	}
	return &IrcClient{
		conn:        conn,
		siphonchan:  make(chan []byte),
		pumpchan:    make(chan []byte),
		pumpservice: make(chan chan []byte),
		log:         logger,
		lastwrite:   time.Time{},
		scale:       defaultTimeScale,
	}
}
func NewIrcClient(conn net.Conn, logger log15.Logger, lenPenaltyFactor int,
	timeout, basestep, keepalive, scale time.Duration) *IrcClient {
	c := createIrcClient(conn, logger)
	if scale != 0 {
		c.scale = scale
	}
	if lenPenaltyFactor > 0 {
		c.lenPenaltyFactor = 1.0 / float64(lenPenaltyFactor)
	}
	c.timeout = timeout
	c.basestep = basestep
	c.keepalive = keepalive
	return c
}
func (c *IrcClient) SpawnWorkers(pump, siphon bool) {
	c.isShutdownProtect.Lock()
	defer c.isShutdownProtect.Unlock()
	c.isShutdown = false
	if pump {
		c.killpump = make(chan error)
		go c.pump()
	}
	if siphon {
		c.killsiphon = make(chan error)
		go c.siphon()
	}
}
func (c *IrcClient) calcSleepTime(t time.Time, msgLen int) time.Duration {
	roundToScale := func(in time.Duration) time.Duration {
		return ((in + (c.scale / 2)) / c.scale) * c.scale
	}
	if c.lastwrite.After(c.penalty) {
		c.penalty = c.lastwrite
	}
	applyPenalty := roundToScale(c.penalty.Sub(t)) >= c.timeout
	c.penalty = c.penalty.Add(c.basestep + roundToScale(
		time.Duration(float64(c.scale)*float64(msgLen)*c.lenPenaltyFactor)))
	if applyPenalty {
		sleep := roundToScale(c.penalty.Sub(t) - c.timeout)
		if sleep > c.timeout {
			sleep = c.timeout
		}
		return sleep
	}
	return 0
}
func (c *IrcClient) pump() {
	var err error
	var sleeper <-chan time.Time
	var pinger <-chan time.Time
	if c.keepalive != 0 {
		pinger = time.After(c.keepalive)
	}
	defer close(c.pumpservice)
	for err == nil {
		select {
		case c.pumpservice <- c.pumpchan:
			message := <-c.pumpchan
			if len(message) == 0 {
				break
			}
			if bytes.HasPrefix(message, pong) {
				if err = c.writeMessage(message); err != nil {
					break
				}
			} else if sleeper == nil {
				sleepTime := c.calcSleepTime(time.Now(), len(message))
				if sleepTime == 0 {
					if err = c.writeMessage(message); err != nil {
						break
					}
				} else {
					c.queue.Enqueue(message)
					sleeper = time.After(sleepTime)
				}
			} else {
				c.queue.Enqueue(message)
			}
		case <-sleeper:
			message := c.queue.Dequeue()
			if err = c.writeMessage(message); err != nil {
				break
			}
			if c.queue.length > 0 {
				sleepTime := c.calcSleepTime(time.Now(), len(message))
				sleeper = time.After(sleepTime)
			} else {
				sleeper = nil
			}
		case <-pinger:
			if sleeper != nil {
				c.queue.Enqueue(ping)
			} else {
				if err = c.writeMessage(ping); err != nil {
					break
				}
			}
			pinger = time.After(c.keepalive)
		case c.killpump <- nil:
			return
		}
	}
	c.killpump <- err
}
func (c *IrcClient) writeMessage(msg []byte) error {
	var n int
	var err error
	for written := 0; written < len(msg); written += n {
		n, err = c.conn.Write(msg[written:])
		wrote := msg[written : len(msg)-2]
		if err != nil {
			c.log.Error("Write error", "err", err, "msg", wrote)
			return err
		}
		c.log.Debug(string(wrote), "direction", "send")
		c.lastwrite = time.Now()
	}
	return nil
}
func (c *IrcClient) siphon() {
	buf := make([]byte, bufferSize)
	var err error
	var shutdown bool
	var position, n int
	for err == nil {
		n, err = c.conn.Read(buf[position:])
		if n > 0 {
			position, shutdown = c.extractMessages(buf[:n+position])
			if shutdown {
				return
			}
		}
	}
	close(c.siphonchan)
	c.killsiphon <- err
}