package main

import (
	"encoding/binary"
	"fmt"
	"os"
	"time"

	"git.metznet.ca/MetzNet/pnyx"
	"github.com/gen2brain/malgo"
	"github.com/hraban/opus"
)

func main() {
  decoders := map[pnyx.PeerID]chan[]byte{}
  encoder, err := opus.NewEncoder(48000, 1, opus.AppVoIP)
  if err != nil {
    panic(err)
  }

  ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, nil)
  if err != nil {
    panic(err)
  }

  defer ctx.Free()
  defer ctx.Uninit()

	infos, err := ctx.Devices(malgo.Playback)
	if err != nil {
    panic(err)
	}

  var playback_device *malgo.DeviceInfo = nil
	for _, info := range infos {
    if info.IsDefault != 0 {
      full, err := ctx.DeviceInfo(malgo.Playback, info.ID, malgo.Shared)
      if err != nil {
        panic(err)
      }
      playback_device = &full
    }
	}

  if playback_device == nil {
    panic("No default playback device")
  }

	infos, err = ctx.Devices(malgo.Capture)
	if err != nil {
    panic(err)
	}

  var capture_device *malgo.DeviceInfo = nil
	for _, info := range infos {
    if info.IsDefault != 0 {
      full, err := ctx.DeviceInfo(malgo.Capture, info.ID, malgo.Shared)
      if err != nil {
        panic(err)
      }
      capture_device = &full
    }
	}

  if capture_device == nil {
    panic("No default capture device")
  }

  inDeviceConfig := malgo.DefaultDeviceConfig(malgo.Capture)
	inDeviceConfig.Capture.Format = malgo.FormatS16
	inDeviceConfig.Capture.Channels = 1
  inDeviceConfig.Capture.DeviceID = capture_device.ID.Pointer()
	inDeviceConfig.SampleRate = 48000
  inDeviceConfig.PeriodSizeInFrames = 960
	inDeviceConfig.Alsa.NoMMap = 1
  inDeviceConfig.Capture.ShareMode = malgo.Shared

  outDeviceConfig := malgo.DefaultDeviceConfig(malgo.Playback)
	outDeviceConfig.Playback.Format = malgo.FormatS16
	outDeviceConfig.Playback.Channels = 1
  outDeviceConfig.Playback.DeviceID = playback_device.ID.Pointer()
	outDeviceConfig.SampleRate = 48000
  outDeviceConfig.PeriodSizeInFrames = 960
	outDeviceConfig.Alsa.NoMMap = 1
  outDeviceConfig.Playback.ShareMode = malgo.Shared

  mic := make(chan []byte, 0)
  speaker := make(chan []byte, 0)

  onSendFrames := func(output_samples []byte, input_samples []byte, framecount uint32) {
    select {
    case data := <- speaker:
      copy(output_samples, data)
    default:
    }
  }

	playbackCallbacks := malgo.DeviceCallbacks{
		Data: onSendFrames,
	}

  fmt.Printf("Creating playback device %+v with format %+v\n", playback_device, playback_device.Formats[0])
  outDevice, err := malgo.InitDevice(ctx.Context, outDeviceConfig, playbackCallbacks)
  if err != nil {
    panic(err)
  }

  fmt.Printf("Starting playback\n")
  err = outDevice.Start()
  if err != nil {
    panic(err)
  }

  defer outDevice.Uninit()
  defer outDevice.Stop()

	onRecvFrames := func(output_samples []byte, input_samples []byte, framecount uint32) {
    pcm := make([]int16, len(input_samples)/2)
    for i := 0; i < len(input_samples)/2; i++ {
      pcm[i] = int16(binary.LittleEndian.Uint16(input_samples[2*i:]))
    }

    data := make([]byte, len(input_samples))
    written, err := encoder.Encode(pcm, data)
    if err != nil {
      panic(err)
    }

    select {
    case mic <- data[:written]:
    default:
    }
	}

	captureCallbacks := malgo.DeviceCallbacks{
		Data: onRecvFrames,
	}

  fmt.Printf("Creating capture device %+v with format %+v\n", capture_device, capture_device.Formats[0])
	inDevice, err := malgo.InitDevice(ctx.Context, inDeviceConfig, captureCallbacks)
	if err != nil {
    panic(err)
	}

  fmt.Printf("Starting capture device\n")
	err = inDevice.Start()
	if err != nil {
    panic(err)
	}

  defer inDevice.Uninit()
  defer inDevice.Stop()

  fmt.Printf("Starting pnyx client\n")

  client, err := pnyx.NewClient(nil, os.Args[1])
  if err != nil {
    panic(err)
  }

  fmt.Printf("Started pnyx client\n")

  go func() {
    var buf [1024]byte
    for true {
      read, _, err := client.Connection.ReadFromUDP(buf[:])
      if err != nil {
        fmt.Printf("Read Error %s\n", err)
        break
      }

      data, err := pnyx.ParseSessionData(&client.Session, buf[pnyx.COMMAND_LENGTH + pnyx.SESSION_ID_LENGTH:read])
      if err != nil {
        fmt.Printf("ParseSessionData Error %s\n", err)
        continue
      }

      packet, err := pnyx.ParsePacket(data)
      if err != nil {
        fmt.Printf("ParsePacket Error %s - %x\n", err, data)
        continue
      }

      switch packet := packet.(type) {
      case pnyx.ChannelPeerPacket:
        if packet.Channel == pnyx.ChannelID(0) {
          decode_chan, exists := decoders[packet.Peer]
          if exists == false {
            decode_chan = make(chan[]byte, 1000)
            decoders[packet.Peer] = decode_chan

            go func(decode_chan chan[]byte){
              decoder, err := opus.NewDecoder(48000, 1)
              if err != nil {
                panic(err)
              }

              for true {
                select {
                case <-time.After(20*time.Millisecond):
                  pcm := make([]int16, 960)
                  err := decoder.DecodePLC(pcm)
                  if err != nil {
                    panic(err)
                  }

                  pcm_bytes := make([]byte, 960*2)
                  for i := 0; i < 960; i++ {
                    binary.LittleEndian.PutUint16(pcm_bytes[i*2:], uint16(pcm[i]))
                  }
                  speaker <- pcm_bytes
                case data := <-decode_chan:
                  pcm := make([]int16, 960)
                  written, err := decoder.Decode(data, pcm)
                  if err != nil {
                    panic(err)
                  }

                  pcm_bytes := make([]byte, written*2)
                  for i := 0; i < written; i++ {
                    binary.LittleEndian.PutUint16(pcm_bytes[i*2:], uint16(pcm[i]))
                  }
                  speaker <- pcm_bytes
                }
              }

            }(decoders[packet.Peer])
          }
          decode_chan <- packet.Data
        }
      default:
        fmt.Printf("Unhandled packet type: %s\n", packet)
      }
    }
  }()

  join_packet, _ := pnyx.NewChannelCommandPacket(pnyx.ChannelID(0), pnyx.MODE_CHANNEL, pnyx.CHANNEL_JOIN, nil)
  err = client.Send(join_packet)
  if err != nil {
    panic(err)
  }

  for true {
    data := <- mic
    err = client.Send(pnyx.NewChannelDataPacket(pnyx.ChannelID(0), pnyx.MODE_RAW, data))
    if err != nil {
      panic(err)
    }
  }
}
