package client

import (
	"bytes"
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

type Client struct {
	token string
	url   string

	Commits *CommitService
	Repos   *RepoService
	Users   *UserService
}

func New(token, url string) *Client {
	c := Client{
		token: token,
		url:   url,
	}

	c.Commits = &CommitService{&c}
	c.Repos = &RepoService{&c}
	c.Users = &UserService{&c}
	return &c
}

var (
	ErrNotFound       = errors.New("Not Found")
	ErrForbidden      = errors.New("Forbidden")
	ErrBadRequest     = errors.New("Bad Request")
	ErrNotAuthorized  = errors.New("Unauthorized")
	ErrInternalServer = errors.New("Internal Server Error")
)

// runs an http.Request and parses the JSON-encoded http.Response,
// storing the result in the value pointed to by v.
func (c *Client) run(method, path string, in, out interface{}) error {

	// create the URI
	uri, err := url.Parse(c.url + path)
	if err != nil {
		return err
	}

	if len(uri.Scheme) == 0 {
		uri.Scheme = "http"
	}

	if len(c.token) > 0 {
		params := uri.Query()
		params.Add("access_token", c.token)
		uri.RawQuery = params.Encode()
	}

	// create the request
	req := &http.Request{
		URL:           uri,
		Method:        method,
		ProtoMajor:    1,
		ProtoMinor:    1,
		Close:         true,
		ContentLength: 0,
	}

	// if data input is provided, serialize to JSON
	if in != nil {
		inJson, err := json.Marshal(in)
		if err != nil {
			return err
		}

		buf := bytes.NewBuffer(inJson)
		req.Body = ioutil.NopCloser(buf)

		req.ContentLength = int64(len(inJson))
		req.Header.Set("Content-Length", strconv.Itoa(len(inJson)))
		req.Header.Set("Content-Type", "application/json")
	}

	// make the request using the default http client
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	// Read the bytes from the body (make sure we defer close the body)
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	// Check for an http error status (ie not 200 StatusOK)
	switch resp.StatusCode {
	case 404:
		return ErrNotFound
	case 403:
		return ErrForbidden
	case 401:
		return ErrNotAuthorized
	case 400:
		return ErrBadRequest
	case 500:
		return ErrInternalServer
	}

	// Unmarshall the JSON response
	if out != nil {
		return json.Unmarshal(body, out)
	}

	return nil
}

// do makes an http.Request and returns the response
func (c *Client) do(method, path string) (*http.Response, error) {

	// create the URI
	uri, err := url.Parse(c.url + path)
	if err != nil {
		return nil, err
	}

	if len(uri.Scheme) == 0 {
		uri.Scheme = "http"
	}

	if len(c.token) > 0 {
		params := uri.Query()
		params.Add("access_token", c.token)
		uri.RawQuery = params.Encode()
	}

	// create the request
	req := &http.Request{
		URL:           uri,
		Method:        method,
		ProtoMajor:    1,
		ProtoMinor:    1,
		Close:         true,
		ContentLength: 0,
	}

	// make the request using the default http client
	return http.DefaultClient.Do(req)
}