package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/urfave/cli"
)

var (
	noop    = false
	build   = ""
	version = "dev-build"
	thread  = false
)

type StdinScanner struct {
	*bufio.Scanner
	tee bool
}

func NewStdinScanner(tee bool) *StdinScanner {
	return &StdinScanner{bufio.NewScanner(os.Stdin), tee}
}

func (s *StdinScanner) StreamBytes() chan []byte {
	ch := make(chan []byte)
	s.Split(bufio.ScanBytes)
	go func() {
		for s.Scan() {
			b := s.Bytes()
			ch <- b
			if s.tee {
				fmt.Printf("%s", b)
			}
		}
		failOnError(s.Err(), "error reading input")
		close(ch)
	}()
	return ch
}

func (s *StdinScanner) StreamLines() chan string {
	ch := make(chan string)
	s.Split(bufio.ScanLines)
	go func() {
		for s.Scan() {
			ch <- s.Text()
			if s.tee {
				fmt.Println(s.Text())
			}
		}
		failOnError(s.Err(), "error reading input")
		close(ch)
	}()
	return ch
}

func writeTemp(byteCh chan []byte) string {
	tmp, err := ioutil.TempFile(os.TempDir(), "slackcat-")
	failOnError(err, "unable to create tmpfile")

	w := bufio.NewWriter(tmp)
	for b := range byteCh {
		_, err := w.Write(b)
		failOnError(err, "error writing to tmpfile")
	}
	w.Flush()

	return tmp.Name()
}

func handleUsageError(c *cli.Context, err error, _ bool) error {
	fmt.Fprintf(c.App.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
	cli.ShowAppHelp(c)
	return cli.NewExitError("", 1)
}

func printFullVersion(c *cli.Context) {
	fmt.Fprintf(c.App.Writer, "%v version %v, build %v\n", c.App.Name, c.App.Version, build)
}

func main() {
	cli.VersionPrinter = printFullVersion

	app := cli.NewApp()
	app.Name = "slackcat"
	app.Usage = "redirect a file to slack"
	app.Version = version
	app.OnUsageError = handleUsageError
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "channel, c",
			Usage: "Slack channel or group to post to",
		},
		cli.StringFlag{
			Name:  "comment",
			Usage: "Initial comment for snippet",
		},
		cli.BoolFlag{
			Name:  "configure",
			Usage: "Configure Slackcat via oauth",
		},
		cli.StringFlag{
			Name:  "filename, n",
			Usage: "Filename for upload. Defaults to current timestamp",
		},
		cli.StringFlag{
			Name:  "filetype",
			Usage: "Specify filetype for syntax highlighting",
		},
		cli.BoolFlag{
			Name:  "list",
			Usage: "List team channel names",
		},
		cli.BoolFlag{
			Name:  "noop",
			Usage: "Skip posting file to Slack. Useful for testing",
		},
		cli.BoolFlag{
			Name:  "stream, s",
			Usage: "Stream messages to Slack continuously instead of uploading a single snippet",
		},
		cli.BoolFlag{
			Name:  "tee, t",
			Usage: "Print stdin to screen before posting",
		},
		cli.StringFlag{
			Name:  "token",
			Usage: "Optional Slack token to use, ignoring config file",
		},
		cli.StringFlag{
			Name:  "username, u",
			Usage: "Stream messages as given bot user. Defaults to auth user",
		},
		cli.StringFlag{
			Name:  "iconemoji, i",
			Usage: "Stream messages as given bot icon emoji. Defaults to auth user's icon",
		},
		cli.BoolFlag{
			Name:  "thread",
			Usage: "Send subsequent messages as threaded reply to orignial message",
		},
	}

	app.Action = func(c *cli.Context) {
		var config *Config

		if c.Bool("configure") {
			configureOA()
			os.Exit(0)
		}

		if c.String("token") != "" {
			config = &Config{
				Teams: map[string]string{
					"default": c.String("token"),
				},
				DefaultTeam: "default",
			}
		} else {
			configPath, exists := getConfigPath()
			if !exists {
				exitErr(fmt.Errorf("missing config file at %s\nuse --configure to create", configPath))
			}
			config = ReadConfig(configPath)
		}

		if c.Bool("list") {
			for teamName, token := range config.Teams {
				InitAPI(token)
				for _, n := range listChannels() {
					fmt.Printf("[%s] [channel]  %s\n", teamName, n)
				}
				for _, n := range listGroups() {
					fmt.Printf("[%s] [group]  %s\n", teamName, n)
				}
				for _, n := range listIms() {
					fmt.Printf("[%s] [im]  %s\n", teamName, n)
				}
			}
			os.Exit(0)
		}

		team, channel, err := config.parseChannelOpt(c.String("channel"))
		failOnError(err)

		noop = c.Bool("noop")
		thread = c.Bool("thread")
		username := c.String("username")
		iconEmoji := c.String("iconemoji")
		fileName := c.String("filename")
		fileType := c.String("filetype")
		fileComment := c.String("comment")

		token := config.Teams[team]
		if token == "" {
			exitErr(fmt.Errorf("no such team: %s", team))
		}

		InitAPI(token)
		slackcat := newSlackcat(username, iconEmoji, channel)

		if len(c.Args()) > 0 {
			if c.Bool("stream") {
				output("filepath provided, ignoring stream option")
			}
			filePath := c.Args()[0]
			if fileName == "" {
				fileName = filepath.Base(filePath)
			}
			slackcat.postFile(filePath, fileName, fileType, fileComment)
			os.Exit(0)
		}

		scanner := NewStdinScanner(c.Bool("tee"))

		if c.Bool("stream") {
			// If threaded then send comment first to start thread
			if thread {
				var s []string
				if fileComment != "" {
					s = append(s, fileComment)
				} else {
					s = append(s, "Slackcat Stream Output:")
				}
				slackcat.postMsg(s)
			}
			slackcat.stream(scanner.StreamLines())
		} else {
			filePath := writeTemp(scanner.StreamBytes())
			defer os.Remove(filePath)
			slackcat.postFile(filePath, fileName, fileType, fileComment)
			os.Exit(0)
		}
	}

	app.Run(os.Args)

}
