package main

import (
	"bufio"
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
	"strings"

	esum "github.com/emojisum/emojisum/emoji"
	"github.com/kyokomi/emoji"
)

func main() {
	if err := run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

var (
	flParseOpenSSL   = flag.Bool("pb", false, "parse the output of BSD/OpenSSL style checksums on stdin (`openssl sha256 ./foo | emojisum -pb`)")
	flParseCoreUtils = flag.Bool("pg", false, "parse the output of GNU/CoreUtils style checksums on stdin (`sha256sum ./foo | emojisum -pg`)")
)

func run() error {
	flag.Parse()

	if *flParseOpenSSL {
		buf := bufio.NewReader(os.Stdin)
		for {
			line, err := buf.ReadString('\n')
			if err != nil && err == io.EOF {
				return nil
			} else if err != nil {
				return err
			}
			hash, name, sum, err := parseOpenSSL(line)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s: %q\n", err, line)
				continue
			}
			str := emojiFromBytes(sum)
			fmt.Printf("%s(%s)= %x\n", hash, name, sum)
			fmt.Printf("%s(%s)= %s\n", hash, name, str)
			fmt.Printf("%s(%s)= ", hash, name)
			emoji.Println(str)
		}
		// never gets here because of the return on EOF or err
	}
	if *flParseCoreUtils {
		buf := bufio.NewReader(os.Stdin)
		for {
			line, err := buf.ReadString('\n')
			if err != nil && err == io.EOF {
				return nil
			} else if err != nil {
				return err
			}
			name, sum, err := parseCoreUtils(line)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s: %q\n", err, line)
				continue
			}
			str := emojiFromBytes(sum)
			fmt.Printf("%x  %s\n", sum, name)
			fmt.Printf("%s  %s\n", str, name)
			emoji.Print(str)
			fmt.Printf("  %s\n", name)
		}
		// never gets here because of the return on EOF or err
	}

	// Otherwise do the checksum ourselves

	if flag.NArg() == 0 {
		sum, err := Sum(os.Stdin)
		if err != nil {
			return err
		}
		str := emojiFromBytes(sum)
		fmt.Printf("SHA1(-)= %x\n", sum)
		fmt.Printf("SHA1(-)= %s\n", str)
		fmt.Printf("SHA1(-)= ")
		emoji.Println(str)
		return nil
	}

	for _, arg := range flag.Args() {
		fh, err := os.Open(arg)
		if err != nil {
			return err
		}
		defer fh.Close()

		sum, err := Sum(fh)
		if err != nil {
			return err
		}
		str := emojiFromBytes(sum)
		fmt.Printf("SHA1(%s)= %x\n", arg, sum)
		fmt.Printf("SHA1(%s)= %s\n", arg, str)
		fmt.Printf("SHA1(%s)= ", arg)
		emoji.Println(str)
	}
	return nil
}

/*
openssl sum:
```
$> openssl sha256 tmp.efLuko
SHA256(tmp.efLuko)= f18bd8b680e834ab8097a66deb0255821195d9624e39da6b65903ff6a09a01bb
```
*/
func parseOpenSSL(line string) (hash, filename string, sum []byte, err error) {
	if !strings.Contains(line, "(") {
		return "", "", nil, ErrNotOpenSSLLine
	}
	chunks := strings.SplitN(strings.TrimRight(line, "\n"), ")= ", 2)
	if len(chunks) != 2 {
		return "", "", nil, ErrNotOpenSSLLine
	}
	chunksprime := strings.SplitN(chunks[0], "(", 2)
	if len(chunks) != 2 {
		return "", "", nil, ErrNotOpenSSLLine
	}
	sum, err = hex.DecodeString(chunks[1])
	if err != nil {
		return "", "", nil, err
	}
	return chunksprime[0], chunksprime[1], sum, nil
}

// ErrNotOpenSSLLine when the line to parse is not formated like an OpenSSL checksum line
var ErrNotOpenSSLLine = errors.New("not an openssl checksum line")

/*
coreutils output:
```
$ sha256sum ./tmp.efLuko
f18bd8b680e834ab8097a66deb0255821195d9624e39da6b65903ff6a09a01bb  ./tmp.efLuko
```
*/
func parseCoreUtils(line string) (filename string, sum []byte, err error) {
	chunks := strings.SplitN(strings.TrimRight(line, "\n"), "  ", 2)
	if len(chunks) != 2 {
		return "", nil, ErrNotCoreUtilsLine
	}
	sum, err = hex.DecodeString(chunks[0])
	if err != nil {
		return "", nil, err
	}
	return chunks[1], sum, nil
}

// ErrNotCoreUtilsLine when the line to parse is not formated like a coreutils checksum line
var ErrNotCoreUtilsLine = errors.New("not a coreutils checksum line")

// Sum is a basic wrapper around crypto/sha1
func Sum(r io.Reader) ([]byte, error) {
	h := sha1.New()
	if _, err := io.Copy(h, r); err != nil {
		return nil, err
	}
	sum := h.Sum(nil)
	return sum[:], nil
}

func emojiFromBytes(buf []byte) string {
	var ret string
	for _, b := range buf {
		for _, e := range esum.Map(b) {
			// use the first colon notation word and continue
			if esum.IsColonNotation(e) {
				ret = ret + e
				break
			}
		}
	}
	return ret
}
