// Copyright 2011 Xing Xing <mikespook@gmail.com>.
// All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package client

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

/*
The client side api for gearman

usage:
c := client.New("tcp4", "127.0.0.1:4730")
handle := c.Do("foobar", []byte("data here"), JOB_LOW | JOB_BG)

*/
type Client struct {
	net, addr, lastcall string
	respHandler         map[string]ResponseHandler
	innerHandler        map[string]ResponseHandler
	in                  chan []byte
	isConn              bool
	conn                net.Conn
	mutex               sync.RWMutex

	ErrorHandler ErrorHandler
}

// Create a new client.
// Connect to "addr" through "network"
// Eg.
//      client, err := client.New("127.0.0.1:4730")
func New(net, addr string) (client *Client, err error) {
	client = &Client{
		net:          net,
		addr:         addr,
		respHandler:  make(map[string]ResponseHandler, QUEUE_SIZE),
		innerHandler: make(map[string]ResponseHandler, QUEUE_SIZE),
		in:           make(chan []byte, QUEUE_SIZE),
	}
	if err = client.connect(); err != nil {
		return
	}
	go client.readLoop()
	go client.processLoop()
	return
}

// {{{ private functions

//
func (client *Client) connect() (err error) {
	client.conn, err = net.Dial(client.net, client.addr)
	if err != nil {
		return
	}
	client.isConn = true
	return
}

// Internal write
func (client *Client) write(req *request) (err error) {
	var n int
	buf := req.Encode()
	for i := 0; i < len(buf); i += n {
		n, err = client.conn.Write(buf[i:])
		if err != nil {
			return
		}
	}
	return
}

// read length bytes from the socket
func (client *Client) read(length int) (data []byte, err error) {
	n := 0
	buf := getBuffer(BUFFER_SIZE)
	// read until data can be unpacked
	for i := length; i > 0 || len(data) < MIN_PACKET_LEN; i -= n {
		if n, err = client.conn.Read(buf); err != nil {
			if !client.isConn {
				err = ErrConnClosed
				return
			}
			if err == io.EOF && n == 0 {
				if data == nil {
					err = ErrConnection
				}
			}
			return
		}
		data = append(data, buf[0:n]...)
		if n < BUFFER_SIZE {
			break
		}
	}
	return
}

// read data from socket
func (client *Client) readLoop() {
	var data []byte
	var err error
	for client.isConn {
		if data, err = client.read(BUFFER_SIZE); err != nil {
			if err == ErrConnClosed {
				break
			}
			client.err(err)
			continue
		}
		client.in <- data
	}
	close(client.in)
}

// decode data & process it
func (client *Client) processLoop() {
	var resp *Response
	var l int
	var err error
	var data, leftdata []byte
	for data = range client.in {
		if len(leftdata) > 0 { // some data left for processing
			data = append(leftdata, data...)
		}
		l = len(data)
		if l < MIN_PACKET_LEN { // not enough data
			leftdata = data
			continue
		}
		if resp, l, err = decodeResponse(data); err != nil {
			client.err(err)
			continue
		}
		leftdata = nil
		switch resp.DataType {
		case ERROR:
			if client.lastcall != "" {
				client.handleInner(client.lastcall, resp)
				client.lastcall = ""
			} else {
				client.err(GetError(resp.Data))
			}
		case STATUS_RES:
			client.handleInner("s"+resp.Handle, resp)
		case JOB_CREATED:
			client.handleInner("c", resp)
		case ECHO_RES:
			client.handleInner("e", resp)
		case WORK_DATA, WORK_WARNING, WORK_STATUS, WORK_COMPLETE,
			WORK_FAIL, WORK_EXCEPTION:
			client.handleResponse(resp.Handle, resp)
		}
		if len(data) > l {
			leftdata = data[l:]
		}
	}
}

// error handler
func (client *Client) err(e error) {
	if client.ErrorHandler != nil {
		client.ErrorHandler(e)
	}
}

// job handler
func (client *Client) handleResponse(key string, resp *Response) {
	client.mutex.RLock()
	defer client.mutex.RUnlock()
	if h, ok := client.respHandler[key]; ok {
		h(resp)
		delete(client.respHandler, key)
	}
}

// job handler
func (client *Client) handleInner(key string, resp *Response) {
	if h, ok := client.innerHandler[key]; ok {
		h(resp)
		delete(client.innerHandler, key)
	}
}

// Internal do
func (client *Client) do(funcname string, data []byte,
	flag uint32) (handle string, err error) {
	id := IdGen.Id()
	req := getJob(id, []byte(funcname), data)
	req.DataType = flag
	client.write(req)
	var wg sync.WaitGroup
	wg.Add(1)
	client.mutex.RLock()
	client.lastcall = "c"
	client.innerHandler["c"] = ResponseHandler(func(resp *Response) {
		defer wg.Done()
		defer client.mutex.RUnlock()
		if resp.DataType == ERROR {
			err = GetError(resp.Data)
			return
		}
		handle = resp.Handle
	})
	wg.Wait()
	return
}

// }}}

// Do the function.
// funcname is a string with function name.
// data is encoding to byte array.
// flag set the job type, include running level: JOB_LOW, JOB_NORMAL, JOB_HIGH
func (client *Client) Do(funcname string, data []byte,
	flag byte, h ResponseHandler) (handle string, err error) {
	var datatype uint32
	switch flag {
	case JOB_LOW:
		datatype = SUBMIT_JOB_LOW
	case JOB_HIGH:
		datatype = SUBMIT_JOB_HIGH
	default:
		datatype = SUBMIT_JOB
	}
	handle, err = client.do(funcname, data, datatype)
	client.mutex.Lock()
	defer client.mutex.Unlock()
	if h != nil {
		client.respHandler[handle] = h
	}
	return
}

// Do the function at background.
// funcname is a string with function name.
// data is encoding to byte array.
// flag set the job type, include running level: JOB_LOW, JOB_NORMAL, JOB_HIGH
func (client *Client) DoBg(funcname string, data []byte,
	flag byte) (handle string, err error) {
	var datatype uint32
	switch flag {
	case JOB_LOW:
		datatype = SUBMIT_JOB_LOW_BG
	case JOB_HIGH:
		datatype = SUBMIT_JOB_HIGH_BG
	default:
		datatype = SUBMIT_JOB_BG
	}
	handle, err = client.do(funcname, data, datatype)
	return
}

// Get job status from job server.
// !!!Not fully tested.!!!
func (client *Client) Status(handle string) (status *Status, err error) {
	req := getRequest()
	req.DataType = GET_STATUS
	req.Data = []byte(handle)
	client.write(req)
	var wg sync.WaitGroup
	wg.Add(1)
	client.mutex.Lock()
	client.lastcall = "s" + handle
	client.innerHandler["s"+handle] = ResponseHandler(func(resp *Response) {
		defer wg.Done()
		defer client.mutex.Unlock()
		var err error
		status, err = resp.Status()
		if err != nil {
			client.err(err)
		}
	})
	wg.Wait()
	return
}

// Send a something out, get the samething back.
func (client *Client) Echo(data []byte) (echo []byte, err error) {
	req := getRequest()
	req.DataType = ECHO_REQ
	req.Data = data
	client.write(req)
	var wg sync.WaitGroup
	wg.Add(1)
	client.mutex.Lock()
	client.lastcall = "e"
	client.innerHandler["e"] = ResponseHandler(func(resp *Response) {
		defer wg.Done()
		defer client.mutex.Unlock()
		echo = resp.Data
	})
	wg.Wait()
	return
}

// Close
func (client *Client) Close() (err error) {
	client.isConn = false
	return client.conn.Close()
}
