package pnyx

import (
	"encoding"
	"fmt"

	"github.com/google/uuid"
)

type PacketType uint8
const (
  PACKET_SERVER_COMMAND PacketType = iota
  PACKET_CHANNEL_COMMAND 
  PACKET_CHANNEL_DATA
  PACKET_CHANNEL_PEER

  CHANNEL_HEADER_LEN int = 2
  CHANNEL_COMMAND_LEN = CHANNEL_HEADER_LEN + COMMAND_LENGTH + REQ_ID_LENGTH
  CHANNEL_PEER_LEN = CHANNEL_HEADER_LEN + PEER_ID_LENGTH

  SERVER_COMMAND_ADD_CHANNEL byte = iota
  SERVER_COMMAND_DEL_CHANNEL
)

type Payload interface {
  encoding.BinaryMarshaler
}

type Packet struct {
  Type PacketType
  Payload Payload
}

func(packet Packet) MarshalBinary() ([]byte, error) {
  payload, err := packet.Payload.MarshalBinary()
  if err != nil {
    return nil, err
  }

  return append([]byte{byte(packet.Type)}, payload...), nil
}

func ParsePacket(data []byte) (Payload, error) {
  if len(data) < 1 {
    return nil, fmt.Errorf("Packet too short to parse - %d/1", len(data))
  }

  switch PacketType(data[0]) {
  case PACKET_SERVER_COMMAND:
    return ParseServerCommandPacket(data[1:])
  case PACKET_CHANNEL_DATA:
    return ParseChannelDataPacket(data[1:])
  case PACKET_CHANNEL_COMMAND:
    return ParseChannelCommandPacket(data[1:])
  case PACKET_CHANNEL_PEER:
    return ParseChannelPeerPacket(data[1:])
  default:
    return nil, fmt.Errorf("Don't know how to parse packet type 0x%02x", data[0])
  }
}

type ServerCommandPacket struct {
  ReqID uuid.UUID
  Command byte
  Data []byte
}

func (packet ServerCommandPacket) MarshalBinary() ([]byte, error) {
  p := make([]byte, 17 + len(packet.Data))
  copy(p, packet.ReqID[:])
  p[16] = packet.Command
  copy(p[17:], packet.Data)

  return p, nil
}

func NewServerCommandPacket(command byte, data []byte) (*Packet, uuid.UUID) {
  req_id := uuid.New()
  return &Packet{
    Type: PACKET_SERVER_COMMAND,
    Payload: ServerCommandPacket{
      ReqID: req_id,
      Command: command,
      Data: data,
    },
  }, req_id
}

func ParseServerCommandPacket(data []byte) (ServerCommandPacket, error) {
  if len(data) < 17 {
    return ServerCommandPacket{}, fmt.Errorf("Not enough data to decode ServerCommandPacket: %d/%d", len(data), 17)
  }

  return ServerCommandPacket{
    ReqID: uuid.UUID(data[0:16]),
    Command: data[16],
    Data: data[17:],
  }, nil
}

type ChannelHeader struct {
  Channel ChannelID
  Mode ModeID
}

func(packet ChannelHeader) MarshalBinary() ([]byte, error) {
  return []byte{byte(packet.Channel), byte(packet.Mode)}, nil
}

func ParseChannelHeader(data []byte) (ChannelHeader, error) {
  if len(data) < 2 {
    return ChannelHeader{}, fmt.Errorf("Not enough bytes to parse ChannelPacket(%d/%d)", len(data), 6)
  }

  return ChannelHeader{
    Channel: ChannelID(data[0]),
    Mode: ModeID(data[1]),
  }, nil
}

type ChannelCommandPacket struct {
  ChannelHeader
  Command byte
  ReqID uuid.UUID
  Data []byte
}

func NewChannelCommandPacket(request_id uuid.UUID, channel ChannelID, mode ModeID, command byte, data []byte) *Packet {
  return &Packet{
    Type: PACKET_CHANNEL_COMMAND,
    Payload: ChannelCommandPacket{
      ChannelHeader: ChannelHeader{
        Channel: channel,
        Mode: mode,
      },
      Command: command,
      ReqID: request_id,
      Data: data,
    },
  } 
}

func(packet ChannelCommandPacket) MarshalBinary() ([]byte, error) {
  header, err := packet.ChannelHeader.MarshalBinary()
  if err != nil {
    return nil, err
  }

  data := make([]byte, len(header) + len(packet.Data) + REQ_ID_LENGTH + COMMAND_LENGTH)
  copy(data, header)
  data[CHANNEL_HEADER_LEN] = packet.Command
  copy(data[CHANNEL_HEADER_LEN + COMMAND_LENGTH:], packet.ReqID[:])
  copy(data[CHANNEL_HEADER_LEN + COMMAND_LENGTH + REQ_ID_LENGTH:], packet.Data)
  return data, nil
}

func ParseChannelCommandPacket(data []byte) (ChannelCommandPacket, error) {
  if len(data) < CHANNEL_COMMAND_LEN {
    return ChannelCommandPacket{}, fmt.Errorf("Not enough data to decode channel command packet %d/%d", len(data), CHANNEL_COMMAND_LEN)
  }

  header, err := ParseChannelHeader(data[:CHANNEL_HEADER_LEN])
  if err != nil {
    return ChannelCommandPacket{}, err
  }

  command := data[CHANNEL_HEADER_LEN]
  request_id := uuid.UUID(data[CHANNEL_HEADER_LEN+COMMAND_LENGTH:])
  return ChannelCommandPacket{
      ChannelHeader: header,
      Command: command,
      ReqID: request_id,
      Data: data[CHANNEL_COMMAND_LEN:],
  }, nil
}

type ChannelPeerPacket struct {
  ChannelHeader
  Peer PeerID
  Data []byte
}

func NewChannelPeerPacket(peer PeerID, channel ChannelID, mode ModeID, data []byte) *Packet {
  return &Packet{
    Type: PACKET_CHANNEL_PEER,
    Payload: ChannelPeerPacket{
      ChannelHeader: ChannelHeader{
        Channel: channel,
        Mode: mode,
      },
      Peer: peer,
      Data: data,
    },
  }
}

func(packet ChannelPeerPacket) MarshalBinary() ([]byte, error) {
  header, err := packet.ChannelHeader.MarshalBinary()
  if err != nil {
    return nil, err
  }

  data := append(header, packet.Peer[:]...)
  return append(data, packet.Data...), nil
}

func ParseChannelPeerPacket(data []byte) (ChannelPeerPacket, error) {
  if len(data) < CHANNEL_PEER_LEN {
    return ChannelPeerPacket{}, fmt.Errorf("Not enough bytes to parse ServerChannelPacket: %d/%d", len(data), PEER_ID_LENGTH)
  }

  header, err := ParseChannelHeader(data)
  if err != nil {
    return ChannelPeerPacket{}, err
  }

  return ChannelPeerPacket{
    ChannelHeader: header,
    Peer: PeerID(data[CHANNEL_HEADER_LEN:]),
    Data: data[CHANNEL_PEER_LEN:],
  }, nil
}


type ChannelDataPacket struct {
  ChannelHeader
  Data []byte
}

func NewChannelDataPacket(channel ChannelID, mode ModeID, data []byte) *Packet {
  return &Packet{
    Type: PACKET_CHANNEL_DATA,
    Payload: ChannelDataPacket{
      ChannelHeader: ChannelHeader{
        Channel: channel,
        Mode: mode,
      },
      Data: data,
    },
  }
}

func(packet ChannelDataPacket) MarshalBinary() ([]byte, error) {
  header, err := packet.ChannelHeader.MarshalBinary()
  if err != nil {
    return nil, err
  }

  return append(header, packet.Data...), nil
}

func ParseChannelDataPacket(data []byte) (ChannelDataPacket, error) {
  if len(data) < CHANNEL_HEADER_LEN {
    return ChannelDataPacket{}, fmt.Errorf("Not enough data to parse ChannelDataPacket")
  }

  header, err := ParseChannelHeader(data)
  if err != nil {
    return ChannelDataPacket{}, nil
  }

  return ChannelDataPacket{
    ChannelHeader: header,
    Data: data[CHANNEL_HEADER_LEN:],
  }, nil
}
