// Copyright 2017 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package clog

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	SLACK             = "slack"
	_SLACK_ATTACHMENT = `{
	"attachments": [
		{
			"text": "%s",
			"color": "%s"
		}
	]
}`
)

var slackColors = []string{
	"",        // Trace
	"#3aa3e3", // Info
	"warning", // Warn
	"danger",  // Error
	"#ff0200", // Fatal
}

type SlackConfig struct {
	// Minimum level of messages to be processed.
	Level LEVEL
	// Buffer size defines how many messages can be queued before hangs.
	BufferSize int64
	// Slack webhook URL.
	URL string
}

type slack struct {
	Adapter

	url string
}

func newSlack() Logger {
	return &slack{
		Adapter: Adapter{
			quitChan: make(chan struct{}),
		},
	}
}

func (s *slack) Level() LEVEL { return s.level }

func (s *slack) Init(v interface{}) error {
	cfg, ok := v.(SlackConfig)
	if !ok {
		return ErrConfigObject{"SlackConfig", v}
	}

	if !isValidLevel(cfg.Level) {
		return ErrInvalidLevel{}
	}
	s.level = cfg.Level

	if len(cfg.URL) == 0 {
		return errors.New("URL cannot be empty")
	}
	s.url = cfg.URL

	s.msgChan = make(chan *Message, cfg.BufferSize)
	return nil
}

func (s *slack) ExchangeChans(errorChan chan<- error) chan *Message {
	s.errorChan = errorChan
	return s.msgChan
}

func (s *slack) write(msg *Message) {
	attachment := fmt.Sprintf(_SLACK_ATTACHMENT, msg.Body, slackColors[msg.Level])
	resp, err := http.Post(s.url, "application/json", bytes.NewReader([]byte(attachment)))
	if err != nil {
		s.errorChan <- fmt.Errorf("slack: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode/100 != 2 {
		data, _ := ioutil.ReadAll(resp.Body)
		s.errorChan <- fmt.Errorf("slack: %s", data)
	}
}

func (s *slack) Start() {
LOOP:
	for {
		select {
		case msg := <-s.msgChan:
			s.write(msg)
		case <-s.quitChan:
			break LOOP
		}
	}

	for {
		if len(s.msgChan) == 0 {
			break
		}

		s.write(<-s.msgChan)
	}
	s.quitChan <- struct{}{} // Notify the cleanup is done.
}

func (s *slack) Destroy() {
	s.quitChan <- struct{}{}
	<-s.quitChan

	close(s.msgChan)
	close(s.quitChan)
}

func init() {
	Register(SLACK, newSlack)
}
