package sftp

import (
	
	
	
)

// The goal of the packetManager is to keep the outgoing packets in the same
// order as the incoming as is requires by section 7 of the RFC.

type packetManager struct {
	requests    chan orderedPacket
	responses   chan orderedPacket
	fini        chan struct{}
	incoming    orderedPackets
	outgoing    orderedPackets
	sender      packetSender // connection object
	working     *sync.WaitGroup
	packetCount uint32
	// it is not nil if the allocator is enabled
	alloc *allocator
}

type packetSender interface {
	sendPacket(encoding.BinaryMarshaler) error
}

func newPktMgr( packetSender) *packetManager {
	 := &packetManager{
		requests:  make(chan orderedPacket, SftpServerWorkerCount),
		responses: make(chan orderedPacket, SftpServerWorkerCount),
		fini:      make(chan struct{}),
		incoming:  make([]orderedPacket, 0, SftpServerWorkerCount),
		outgoing:  make([]orderedPacket, 0, SftpServerWorkerCount),
		sender:    ,
		working:   &sync.WaitGroup{},
	}
	go .controller()
	return 
}

// // packet ordering
func ( *packetManager) () uint32 {
	.packetCount++
	return .packetCount
}

// returns the next orderID without incrementing it.
// This is used before receiving a new packet, with the allocator enabled, to associate
// the slice allocated for the received packet with the orderID that will be used to mark
// the allocated slices for reuse once the request is served
func ( *packetManager) () uint32 {
	return .packetCount + 1
}

type orderedRequest struct {
	requestPacket
	orderid uint32
}

func ( *packetManager) ( requestPacket) orderedRequest {
	return orderedRequest{requestPacket: , orderid: .newOrderID()}
}
func ( orderedRequest) () uint32       { return .orderid }
func ( orderedRequest) ( uint32) { .orderid =  }

type orderedResponse struct {
	responsePacket
	orderid uint32
}

func ( *packetManager) ( responsePacket,  uint32,
) orderedResponse {
	return orderedResponse{responsePacket: , orderid: }
}
func ( orderedResponse) () uint32       { return .orderid }
func ( orderedResponse) ( uint32) { .orderid =  }

type orderedPacket interface {
	id() uint32
	orderID() uint32
}
type orderedPackets []orderedPacket

func ( orderedPackets) () {
	sort.Slice(, func(,  int) bool {
		return [].orderID() < [].orderID()
	})
}

// // packet registry
// register incoming packets to be handled
func ( *packetManager) ( orderedRequest) {
	.working.Add(1)
	.requests <- 
}

// register outgoing packets as being ready
func ( *packetManager) ( orderedResponse) {
	.responses <- 
	.working.Done()
}

// shut down packetManager controller
func ( *packetManager) () {
	// pause until current packets are processed
	.working.Wait()
	close(.fini)
}

// Passed a worker function, returns a channel for incoming packets.
// Keep process packet responses in the order they are received while
// maximizing throughput of file transfers.
func ( *packetManager) ( func(chan orderedRequest),
) chan orderedRequest {
	// multiple workers for faster read/writes
	 := make(chan orderedRequest, SftpServerWorkerCount)
	for  := 0;  < SftpServerWorkerCount; ++ {
		()
	}

	// single worker to enforce sequential processing of everything else
	 := make(chan orderedRequest)
	()

	 := make(chan orderedRequest, SftpServerWorkerCount)
	go func() {
		for  := range  {
			switch .requestPacket.(type) {
			case *sshFxpReadPacket, *sshFxpWritePacket:
				.incomingPacket()
				 <- 
				continue
			case *sshFxpClosePacket:
				// wait for reads/writes to finish when file is closed
				// incomingPacket() call must occur after this
				.working.Wait()
			}
			.incomingPacket()
			// all non-RW use sequential cmdChan
			 <- 
		}
		close()
		close()
		.close()
	}()

	return 
}

// process packets
func ( *packetManager) () {
	for {
		select {
		case  := <-.requests:
			debug("incoming id (oid): %v (%v)", .id(), .orderID())
			.incoming = append(.incoming, )
			.incoming.Sort()
		case  := <-.responses:
			debug("outgoing id (oid): %v (%v)", .id(), .orderID())
			.outgoing = append(.outgoing, )
			.outgoing.Sort()
		case <-.fini:
			return
		}
		.maybeSendPackets()
	}
}

// send as many packets as are ready
func ( *packetManager) () {
	for {
		if len(.outgoing) == 0 || len(.incoming) == 0 {
			debug("break! -- outgoing: %v; incoming: %v",
				len(.outgoing), len(.incoming))
			break
		}
		 := .outgoing[0]
		 := .incoming[0]
		// debug("incoming: %v", ids(s.incoming))
		// debug("outgoing: %v", ids(s.outgoing))
		if .orderID() == .orderID() {
			debug("Sending packet: %v", .id())
			.sender.sendPacket(.(encoding.BinaryMarshaler))
			if .alloc != nil {
				// mark for reuse the slices allocated for this request
				.alloc.ReleasePages(.orderID())
			}
			// pop off heads
			copy(.incoming, .incoming[1:])            // shift left
			.incoming[len(.incoming)-1] = nil         // clear last
			.incoming = .incoming[:len(.incoming)-1] // remove last
			copy(.outgoing, .outgoing[1:])            // shift left
			.outgoing[len(.outgoing)-1] = nil         // clear last
			.outgoing = .outgoing[:len(.outgoing)-1] // remove last
		} else {
			break
		}
	}
}

// func oids(o []orderedPacket) []uint32 {
// 	res := make([]uint32, 0, len(o))
// 	for _, v := range o {
// 		res = append(res, v.orderId())
// 	}
// 	return res
// }
// func ids(o []orderedPacket) []uint32 {
// 	res := make([]uint32, 0, len(o))
// 	for _, v := range o {
// 		res = append(res, v.id())
// 	}
// 	return res
// }