package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/textproto"
	"strconv"

	"github.com/asdine/storm"

	"gopkg.in/telegram-bot-api.v4"
	"xeserv.us/r1459"
)

var (
	vhost = flag.String("vhost", "telegram.org", "vhost to use for things")
	port  = flag.Int("port", 3540, "port to listen on")
	store = flag.String("store", "var/", "where to store things")

	rooms map[string]int64
)

type Client struct {
	net.Conn
	limitReader *io.LimitedReader
	tpReader    *textproto.Reader
	sentNick    bool
	sentUser    bool
	nagged      bool

	username string
	Nick     string
	Password *string
	DB       *storm.DB
	TGBot    *tgbotapi.BotAPI
}

func (c *Client) KillClient(err error) {
	c.SendLine(&r1459.RawLine{
		Source: "xeserv.us",
		Verb:   "ERROR",
		Args: []string{
			"***",
			"Closing link: " + err.Error(),
		},
	})

	c.Close()

	c = nil
}

func (c *Client) SendLine(line *r1459.RawLine) error {
	log.Printf("%s <- %s", c.Nick, line)
	_, err := fmt.Fprintf(c, "%s\r\n", line)
	return err
}

func (c *Client) Join(shortname string, roomID int64) {
	room, err := c.MakeRoom(shortname, roomID)
	if err != nil {
		c.KillClient(err)

		log.Fatal(err)
	}

	for _, line := range room.Burst() {
		c.SendLine(line)
	}
}

func Mask(nick string) string {
	return fmt.Sprintf("%s!telegram@%s", nick, *vhost)
}

func main() {
	flag.Parse()

	server, err := net.Listen("tcp", ":"+strconv.Itoa(*port))
	if server == nil {
		panic("couldn't start listening: " + err.Error())
	}

	for {
		client, err := server.Accept()
		if err != nil {
			log.Printf("couldn't accept: %s", err.Error())
			continue
		}

		log.Printf("Accepted connection from %s", client.RemoteAddr())

		lr := &io.LimitedReader{
			R: client,
			N: 65536,
		}

		tpr := textproto.NewReader(bufio.NewReader(lr))

		c := &Client{
			Conn:        client,
			limitReader: lr,
			tpReader:    tpr,
		}

		go handleClient(c)
	}
}

func handleClient(c *Client) {
	for {
		line, err := c.tpReader.ReadLine()
		if err != nil {
			_ = c.Close()
			log.Printf("%s: %s", c.RemoteAddr(), err)

			return
		}

		log.Printf("%s -> %s", c.RemoteAddr(), line)

		ircLine := r1459.NewRawLine(line)

		switch ircLine.Verb {
		case "NICK":
			if len(ircLine.Args) != 1 {
				c.Close()
			}

			c.sentNick = true
			c.Nick = ircLine.Args[0]

			if c.DB == nil {
				db, err := storm.Open("var/" + c.Nick + ".db")
				if err != nil {
					c.KillClient(err)

					return
				}

				c.DB = db
			}
		case "PASS":
			if len(ircLine.Args) != 1 {
				c.KillClient(errors.New("Invalid format"))

				return
			}

			c.Password = &ircLine.Args[0]

			bot, err := tgbotapi.NewBotAPI(*c.Password)
			if err != nil {
				c.KillClient(err)

				return
			}

			me, err := bot.GetMe()
			if err != nil {
				c.KillClient(err)

				return
			}

			c.SendLine(&r1459.RawLine{
				Source: "xeserv.us",
				Verb:   "NOTICE",
				Args: []string{
					"***",
					fmt.Sprintf("You are logged in as %s (@%s)", me.FirstName, me.UserName),
				},
			})

			c.TGBot = bot

			u := tgbotapi.NewUpdate(0)
			u.Timeout = 60

			updates, err := bot.GetUpdatesChan(u)
			if err != nil {
				c.KillClient(err)
			}

			go func(c *Client) {
				for update := range updates {
					id := update.Message.Chat.ID
					r := &Room{}

					c.DB.One("RoomID", id, r)

					r.client = c

					if update.Message.Text != "" {
						r.RecieveMessage(update)
					}
				}
			}(c)

		case "USER":
			if len(ircLine.Args) != 4 {
				c.Close()
			}

			c.sentUser = true

		case "PING":
			ircLine.Verb = "PONG"
			c.SendLine(ircLine)

		case "PONG", "MODE", "JOIN", "WHO":
			continue

		case "ASSOCIATE":
			if len(ircLine.Args) != 2 {
				c.SendLine(&r1459.RawLine{
					Verb:   "NOTICE",
					Source: "xeserv.us",
					Args: []string{
						c.Nick, "JOIN <shortname> <id>",
					},
				})

				continue
			}

			n, _ := strconv.Atoi(ircLine.Args[1])
			c.Join(ircLine.Args[0], int64(n))

		case "PRIVMSG":
			room := &Room{}

			err := c.DB.One("Shortname", ircLine.Args[0][1:], room)
			if err != nil {
				panic(err)
			}

			room.client = c

			room.SendMessage(ircLine)

		default:
			c.SendLine(&r1459.RawLine{
				Source: "xeserv.us",
				Verb:   "421",
				Args: []string{
					c.Nick,
					"unknown command " + ircLine.Verb,
				},
			})
		}

		if c.TGBot != nil &&
			c.Password != nil &&
			c.sentNick &&
			c.sentUser &&
			!c.nagged {
			c.nagged = true

			c.SendLine(&r1459.RawLine{
				Source: "xeserv.us",
				Verb:   "001",
				Args: []string{
					c.Nick,
					"Welcome to the Internet Relay Chat Network: " + c.Nick,
				},
			})

			c.SendLine(&r1459.RawLine{
				Source: "xeserv.us",
				Verb:   "002",
				Args: []string{
					c.Nick,
					fmt.Sprintf("Your host is xeserv.us[%s], running version tbotd HEAD DEVEL", c.LocalAddr()),
				},
			})

			c.SendLine(&r1459.RawLine{
				Source: "xeserv.us",
				Verb:   "005",
				Args: []string{
					c.Nick,
					"CHANTYPES=!#", "CHANMODES=b,,,ns", "PREFIX=(ya)~&", "NETWORK=Telegram",
					"are supported by this server",
				},
			})

			rooms := []Room{}

			err := c.DB.All(&rooms)
			if err != nil {
				c.KillClient(err)

				return
			}

			for _, room := range rooms {
				c.Join(room.Shortname, room.RoomID)
			}
		}
	}
}
