package frsrpc

import (
	"context"
	"fmt"
	"strings"
	"unicode/utf16"

	dcerpc "github.com/oiweiwei/go-msrpc/dcerpc"
	errors "github.com/oiweiwei/go-msrpc/dcerpc/errors"
	uuid "github.com/oiweiwei/go-msrpc/midl/uuid"
	ndr "github.com/oiweiwei/go-msrpc/ndr"
)

var (
	_ = context.Background
	_ = fmt.Errorf
	_ = utf16.Encode
	_ = strings.TrimPrefix
	_ = ndr.ZeroString
	_ = (*uuid.UUID)(nil)
	_ = (*dcerpc.SyntaxID)(nil)
	_ = (*errors.Error)(nil)
)

// frsrpc server interface.
type FrsrpcServer interface {

	// The FrsRpcSendCommPkt method transfers change order commands, files, and folders
	// on an FRS connection.
	//
	// Return Values: The method MUST return 0 on success or a nonzero error code on failure.
	// For protocol purposes, all nonzero values MUST be treated as equivalent failures
	// unless otherwise specified.
	//
	//	+--------------------------+------------------------------------+
	//	|          RETURN          |                                    |
	//	|        VALUE/CODE        |            DESCRIPTION             |
	//	|                          |                                    |
	//	+--------------------------+------------------------------------+
	//	+--------------------------+------------------------------------+
	//	| 0x00000000 ERROR_SUCCESS | The method completed successfully. |
	//	+--------------------------+------------------------------------+
	//
	// Exceptions Thrown: No exceptions are thrown beyond those thrown by the underlying
	// RPC protocol [MS-RPCE].
	//
	// FRS uses FrsRpcSendCommPkt to send COMM_PACKET objects (see section 2.2.3.5) between
	// replica members to replicate files and folders inside a Replica Set. See sections
	// 4.1 and 4.2.
	//
	// In the following section, these two sentences have the same meaning:
	//
	// * Machine A sends a COMM_PACKET to machine B.
	//
	// * Machine A calls the FrsRpcSendCommPkt method on machine B.
	//
	// There are 12 types of COMM_PACKETs (see section 2.2.3.6.2). If machine A sends a
	// COMM_PACKET packet to machine B with COMM_PACKET.COMM_COMMAND set to CMD_XXXX, a
	// simple way to describe this is as follows: Machine A sends CMD_XXXX to machine B.
	SendCommPacket(context.Context, *SendCommPacketRequest) (*SendCommPacketResponse, error)

	// The FrsRpcVerifyPromotionParent method does not perform any actions. It MUST return
	// ERROR_CALL_NOT_IMPLEMENTED.
	VerifyPromotionParent(context.Context, *VerifyPromotionParentRequest) (*VerifyPromotionParentResponse, error)

	// The FrsRpcStartPromotionParent method requests the server that is the Upstream Partner
	// to start a volatile connection for the purpose of seeding the system volume (SYSVOL)
	// folder on the container.
	//
	// Return Values: The method MUST return 0 on success; otherwise, it MUST return a nonzero
	// error code, as defined by the vendor.
	//
	// To create a \SYSVOL volume (required for the server to operate as a DC), the FrsRpcStartPromotionParent
	// method requests a volatile connection on the server for the purpose of seeding a
	// \SYSVOL folder. This is necessary because of the following: During DC promotion,
	// the newly promoted DC creates a set of Active Directory objects, which includes the
	// connection objects. When the newly promoted DC is trying to replicate the contents
	// from another DC in the domain (the process is called initial sync), the other DC
	// might not know the newly created connection, either because the DS replication service
	// has not replicated the connection out to the other DC or because the other DC has
	// not polled the Directory Service yet. The newly created DC creates a volatile connection
	// for only the initial sync and calls this RPC function to inform the other DC about
	// this volatile connection. In this way, the other DC does not reject the initial sync
	// request. <77> Once the initial sync is completed, NTFRS starts using the connection
	// generated by Knowledge Consistency Checker (KCC) for normal synchronization.
	StartPromotionParent(context.Context, *StartPromotionParentRequest) (*StartPromotionParentResponse, error)

	// The FrsNOP method verifies that the RPC connection for this interface is functioning
	// properly. This method is not used by FRS.
	//
	// This method has no parameters.
	//
	//	+-------------------+---------------------------+
	//	|      RETURN       |                           |
	//	|    VALUE/CODE     |        DESCRIPTION        |
	//	|                   |                           |
	//	+-------------------+---------------------------+
	//	+-------------------+---------------------------+
	//	| 0x00000000        | The method MUST return 0. |
	//	+-------------------+---------------------------+
	Noop(context.Context, *NoopRequest) (*NoopResponse, error)

	// Opnum4NotUsedOnWire operation.
	// Opnum4NotUsedOnWire

	// Opnum5NotUsedOnWire operation.
	// Opnum5NotUsedOnWire

	// Opnum6NotUsedOnWire operation.
	// Opnum6NotUsedOnWire

	// Opnum7NotUsedOnWire operation.
	// Opnum7NotUsedOnWire

	// Opnum8NotUsedOnWire operation.
	// Opnum8NotUsedOnWire

	// Opnum9NotUsedOnWire operation.
	// Opnum9NotUsedOnWire

	// Opnum10NotUsedOnWire operation.
	// Opnum10NotUsedOnWire
}

func RegisterFrsrpcServer(conn dcerpc.Conn, o FrsrpcServer, opts ...dcerpc.Option) {
	conn.RegisterServer(NewFrsrpcServerHandle(o), append(opts, dcerpc.WithAbstractSyntax(FrsrpcSyntaxV1_1))...)
}

func NewFrsrpcServerHandle(o FrsrpcServer) dcerpc.ServerHandle {
	return func(ctx context.Context, opNum int, r ndr.Reader) (dcerpc.Operation, error) {
		return FrsrpcServerHandle(ctx, o, opNum, r)
	}
}

func FrsrpcServerHandle(ctx context.Context, o FrsrpcServer, opNum int, r ndr.Reader) (dcerpc.Operation, error) {
	switch opNum {
	case 0: // FrsRpcSendCommPkt
		op := &xxx_SendCommPacketOperation{}
		if err := op.UnmarshalNDRRequest(ctx, r); err != nil {
			return nil, err
		}
		req := &SendCommPacketRequest{}
		req.xxx_FromOp(ctx, op)
		resp, err := o.SendCommPacket(ctx, req)
		return resp.xxx_ToOp(ctx, op), err
	case 1: // FrsRpcVerifyPromotionParent
		op := &xxx_VerifyPromotionParentOperation{}
		if err := op.UnmarshalNDRRequest(ctx, r); err != nil {
			return nil, err
		}
		req := &VerifyPromotionParentRequest{}
		req.xxx_FromOp(ctx, op)
		resp, err := o.VerifyPromotionParent(ctx, req)
		return resp.xxx_ToOp(ctx, op), err
	case 2: // FrsRpcStartPromotionParent
		op := &xxx_StartPromotionParentOperation{}
		if err := op.UnmarshalNDRRequest(ctx, r); err != nil {
			return nil, err
		}
		req := &StartPromotionParentRequest{}
		req.xxx_FromOp(ctx, op)
		resp, err := o.StartPromotionParent(ctx, req)
		return resp.xxx_ToOp(ctx, op), err
	case 3: // FrsNOP
		op := &xxx_NoopOperation{}
		if err := op.UnmarshalNDRRequest(ctx, r); err != nil {
			return nil, err
		}
		req := &NoopRequest{}
		req.xxx_FromOp(ctx, op)
		resp, err := o.Noop(ctx, req)
		return resp.xxx_ToOp(ctx, op), err
	case 4: // Opnum4NotUsedOnWire
		// Opnum4NotUsedOnWire
		return nil, nil
	case 5: // Opnum5NotUsedOnWire
		// Opnum5NotUsedOnWire
		return nil, nil
	case 6: // Opnum6NotUsedOnWire
		// Opnum6NotUsedOnWire
		return nil, nil
	case 7: // Opnum7NotUsedOnWire
		// Opnum7NotUsedOnWire
		return nil, nil
	case 8: // Opnum8NotUsedOnWire
		// Opnum8NotUsedOnWire
		return nil, nil
	case 9: // Opnum9NotUsedOnWire
		// Opnum9NotUsedOnWire
		return nil, nil
	case 10: // Opnum10NotUsedOnWire
		// Opnum10NotUsedOnWire
		return nil, nil
	}
	return nil, nil
}

// Unimplemented frsrpc
type UnimplementedFrsrpcServer struct {
}

func (UnimplementedFrsrpcServer) SendCommPacket(context.Context, *SendCommPacketRequest) (*SendCommPacketResponse, error) {
	return nil, dcerpc.ErrNotImplemented
}
func (UnimplementedFrsrpcServer) VerifyPromotionParent(context.Context, *VerifyPromotionParentRequest) (*VerifyPromotionParentResponse, error) {
	return nil, dcerpc.ErrNotImplemented
}
func (UnimplementedFrsrpcServer) StartPromotionParent(context.Context, *StartPromotionParentRequest) (*StartPromotionParentResponse, error) {
	return nil, dcerpc.ErrNotImplemented
}
func (UnimplementedFrsrpcServer) Noop(context.Context, *NoopRequest) (*NoopResponse, error) {
	return nil, dcerpc.ErrNotImplemented
}

var _ FrsrpcServer = (*UnimplementedFrsrpcServer)(nil)
