// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package command

import (
	"errors"
	"fmt"
	"io"
	"sync"
	"time"

	"github.com/niruix/sshwifty/application/log"
	"github.com/niruix/sshwifty/application/network"
	"github.com/niruix/sshwifty/application/rw"
)

// Errors
var (
	ErrHandlerUnknownHeaderType = errors.New(
		"Unknown command header type")

	ErrHandlerControlMessageTooLong = errors.New(
		"Control message was too long")

	ErrHandlerInvalidControlMessage = errors.New(
		"Invalid control message")
)

// HandlerCancelSignal signals the cancel of the entire handling proccess
type HandlerCancelSignal chan struct{}

const (
	handlerReadBufLen = HeaderMaxData + 3 // (3 = 1 Header, 2 Etc)
)

type handlerBuf [handlerReadBufLen]byte

// handlerSender writes handler signal
type handlerSender struct {
	writer io.Writer
	lock   *sync.Mutex
}

// signal sends handler signal
func (h handlerSender) signal(hd Header, d []byte, buf []byte) error {
	bufLen := len(buf)
	dLen := len(d)

	if bufLen < dLen+1 {
		panic(fmt.Sprintln("Sending signal %s:%d requires %d bytes of buffer, "+
			"but only %d bytes is available", hd, d, dLen+1, bufLen))
	}

	buf[0] = byte(hd)

	wLen := copy(buf[1:], d) + 1

	_, wErr := h.Write(buf[:wLen])

	return wErr
}

// Write sends data
func (h handlerSender) Write(b []byte) (int, error) {
	h.lock.Lock()
	defer h.lock.Unlock()

	return h.writer.Write(b)
}

// streamHandlerSender includes all receiver as handlerSender, but it been
// designed to be use in streams
type streamHandlerSender struct {
	*handlerSender

	sendDelay time.Duration
}

// signal sends handler signal
func (h streamHandlerSender) signal(hd Header, d []byte, buf []byte) error {
	return h.handlerSender.signal(hd, d, buf)
}

// Write sends data
func (h streamHandlerSender) Write(b []byte) (int, error) {
	defer time.Sleep(h.sendDelay)

	return h.handlerSender.Write(b)
}

// Handler client stream control
type Handler struct {
	dialer       network.Dial
	commands     *Commands
	receiver     rw.FetchReader
	sender       handlerSender
	senderPaused bool
	receiveDelay time.Duration
	sendDelay    time.Duration
	log          log.Logger
	rBuf         handlerBuf
	streams      streams
}

func newHandler(
	dialer network.Dial,
	commands *Commands,
	receiver rw.FetchReader,
	sender io.Writer,
	senderLock *sync.Mutex,
	receiveDelay time.Duration,
	sendDelay time.Duration,
	l log.Logger,
) Handler {
	return Handler{
		dialer:       dialer,
		commands:     commands,
		receiver:     receiver,
		sender:       handlerSender{writer: sender, lock: senderLock},
		senderPaused: false,
		receiveDelay: receiveDelay,
		sendDelay:    sendDelay,
		log:          l,
		rBuf:         handlerBuf{},
		streams:      newStreams(),
	}
}

// handleControl handles Control request
//
// Params:
//   - d: length of the control message
//
// Returns:
//   - error
func (e *Handler) handleControl(d byte, l log.Logger) error {
	buf := e.rBuf[1:]

	if len(buf) < int(d) {
		return ErrHandlerControlMessageTooLong
	}

	rLen, rErr := io.ReadFull(&e.receiver, buf[:d])

	if rErr != nil {
		return rErr
	}

	if rLen <= 0 {
		return ErrHandlerInvalidControlMessage
	}

	switch buf[0] {
	case HeaderControlEcho:
		hd := HeaderControl
		hd.Set(d)

		e.rBuf[0] = byte(hd)
		e.rBuf[1] = HeaderControlEcho

		var wErr error

		if !e.senderPaused {
			_, wErr = e.sender.Write(e.rBuf[:rLen+1])
		} else {
			_, wErr = e.sender.writer.Write(e.rBuf[:rLen+1])
		}

		return wErr

	case HeaderControlPauseStream:
		if !e.senderPaused {
			e.sender.lock.Lock()
			e.senderPaused = true
		}

	case HeaderControlResumeStream:
		if e.senderPaused {
			e.sender.lock.Unlock()
			e.senderPaused = false
		}
	}

	return nil
}

// handleStream handles streams
//
// Params:
//   - d: Stream ID
//
// Returns:
//   - error
func (e *Handler) handleStream(h Header, d byte, l log.Logger) error {
	st, stErr := e.streams.get(d)

	if stErr != nil {
		return stErr
	}

	// WARNING: stream.Tick and it's underlaying commands MUST NOT write to
	//          client. This is because the client data writer maybe locked
	//          and only current routine (the same routine will be used to
	//          tick the stream) can unlock it.
	//          Calling write may dead lock the routine, with there is no way
	//          of recover.
	if st.running() {
		l.Debug("Ticking stream")

		return st.tick(h, &e.receiver, e.rBuf[:])
	}

	l.Debug("Start stream %d", h.Data())

	if e.senderPaused {
		e.sender.lock.Unlock()
		defer e.sender.lock.Lock()
	}

	return st.reinit(h, &e.receiver, streamHandlerSender{
		handlerSender: &e.sender,
		sendDelay:     e.sendDelay,
	}, l, e.commands, e.dialer, e.rBuf[:])
}

func (e *Handler) handleClose(h Header, d byte, l log.Logger) error {
	st, stErr := e.streams.get(d)

	if stErr != nil {
		return stErr
	}

	if e.senderPaused {
		e.sender.lock.Unlock()
		defer e.sender.lock.Lock()
	}

	cErr := st.close()

	if cErr != nil {
		return cErr
	}

	hhd := HeaderCompleted
	hhd.Set(h.Data())

	return e.sender.signal(hhd, nil, e.rBuf[:])
}

func (e *Handler) handleCompleted(d byte, l log.Logger) error {
	st, stErr := e.streams.get(d)

	if stErr != nil {
		return stErr
	}

	if e.senderPaused {
		e.sender.lock.Unlock()
		defer e.sender.lock.Lock()
	}

	return st.release()
}

// Handle starts handling
func (e *Handler) Handle() error {
	defer func() {
		if e.senderPaused {
			e.sender.lock.Unlock()
			e.senderPaused = false
		}

		e.streams.shutdown()
	}()

	requests := 0

	for {
		time.Sleep(e.receiveDelay)

		requests++

		d, dErr := rw.FetchOneByte(e.receiver.Fetch)

		if dErr != nil {
			return dErr
		}

		h := Header(d[0])
		l := e.log.Context("Request (%d)", requests).Context(h.String())

		l.Debug("Received")

		switch h.Type() {
		case HeaderControl:
			dErr = e.handleControl(h.Data(), l)

		case HeaderStream:
			dErr = e.handleStream(h, h.Data(), l)

		case HeaderClose:
			dErr = e.handleClose(h, h.Data(), l)

		case HeaderCompleted:
			dErr = e.handleCompleted(h.Data(), l)

		default:
			return ErrHandlerUnknownHeaderType
		}

		if dErr != nil {
			l.Debug("Request failed: %s", dErr)

			return dErr
		}

		l.Debug("Request successful")
	}
}
