/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { HexBuffer } from '../HexBuffer'
import { W3Buffer } from '../W3Buffer'
import { type WarResult, type JsonResult } from '../CommonInterfaces'
import { type Translator } from './Translator'
import { type Sound } from '../data/Sound'

export class SoundsTranslator implements Translator<Sound[]> {
  private static instance: SoundsTranslator

  private constructor () {}

  public static getInstance (): SoundsTranslator {
    if (this.instance == null) {
      this.instance = new this()
    }
    return this.instance
  }

  public static jsonToWar (sounds: Sound[]): WarResult {
    return this.getInstance().jsonToWar(sounds)
  }

  public static warToJson (buffer: Buffer): JsonResult<Sound[]> {
    return this.getInstance().warToJson(buffer)
  }

  public jsonToWar (soundsJson: Sound[]): WarResult {
    const outBufferToWar = new HexBuffer()

    /*
         * Header
         */
    outBufferToWar.addInt(3) // file version
    outBufferToWar.addInt(soundsJson?.length || 0) // number of sounds

    /*
         * Body
         */
    soundsJson?.forEach((sound) => {
      outBufferToWar.addString(sound.name) // e.g. gg_snd_HumanGlueScreenLoop1
      outBufferToWar.addString(sound.path) // e.g. Sound\Ambient\HumanGlueScreenLoop1.wav

      // EAX effects enum (e.g. missiles, speech, etc)
      /*
                default = DefaultEAXON
                combat = CombatSoundsEAX
                drums = KotoDrumsEAX
                spells = SpellsEAX
                missiles = MissilesEAX
                hero speech = HeroAcksEAX
                doodads = DoodadsEAX
            */
      outBufferToWar.addString(sound.eax != null ? sound.eax : 'DefaultEAXON') // defaults to "DefaultEAXON"

      // Flags, if present (optional)
      let flags = 0
      if (sound.flags != null) {
        if (sound.flags.looping) flags |= 0x1
        if (sound.flags['3dSound']) flags |= 0x2
        if (sound.flags.stopOutOfRange) flags |= 0x4
        if (sound.flags.music) flags |= 0x8
      }
      outBufferToWar.addInt(flags)

      // Fade in and out rate (optional)
      outBufferToWar.addInt(sound.fadeRate != null ? sound.fadeRate.in != null ? sound.fadeRate.in : 10 : 10) // default to 10
      outBufferToWar.addInt(sound.fadeRate != null ? sound.fadeRate.out != null ? sound.fadeRate.out : 10 : 10) // default to 10

      // Volume (optional)
      outBufferToWar.addInt(sound.volume != null ? sound.volume : -1) // default to -1 (for normal volume)

      // Pitch (optional)
      outBufferToWar.addFloat(sound.pitch != null ? sound.pitch : 1.0) // default to 1.0 for normal pitch

      // Mystery numbers... their use is unknown by the w3x documentation, but they must be present
      outBufferToWar.addFloat(0)
      outBufferToWar.addInt(8) // or -1?

      // Which channel to use? Use the lookup table for more details (optional)
      /*
                0=General
                1=Unit Selection
                2=Unit Acknowledgement
                3=Unit Movement
                4=Unit Ready
                5=Combat
                6=Error
                7=Music
                8=User Interface
                9=Looping Movement
                10=Looping Ambient
                11=Animations
                12=Constructions
                13=Birth
                14=Fire
            */
      outBufferToWar.addInt(sound.channel != null ? sound.channel : 0) // default to 0

      // Distance fields
      outBufferToWar.addFloat(sound.distance.min)
      outBufferToWar.addFloat(sound.distance.max)
      outBufferToWar.addFloat(sound.distance.cutoff)

      // More mystery numbers...
      outBufferToWar.addFloat(0)
      outBufferToWar.addFloat(0)
      outBufferToWar.addFloat(127) // or -1?
      outBufferToWar.addFloat(0)
      outBufferToWar.addFloat(0)
      outBufferToWar.addFloat(0)

      outBufferToWar.addString(sound.variableName)
      outBufferToWar.addString('')
      outBufferToWar.addString(sound.path)

      // More unknowns
      outBufferToWar.addFloat(0)
      outBufferToWar.addByte(0)
      outBufferToWar.addFloat(0)
      outBufferToWar.addFloat(0)
      outBufferToWar.addFloat(0)
      outBufferToWar.addByte(0)
      outBufferToWar.addFloat(0)
    })

    return {
      errors: [],
      buffer: outBufferToWar.getBuffer()
    }
  }

  public warToJson (buffer: Buffer): JsonResult<Sound[]> {
    const result: Sound[] = []
    const outBufferToJSON = new W3Buffer(buffer)

    const fileVersion = outBufferToJSON.readInt() // File version
    const numSounds = outBufferToJSON.readInt() // # of sounds

    for (let i = 0; i < numSounds; i++) {
      const sound: Sound = {
        name: '',
        variableName: '',
        path: '',
        eax: '',
        volume: 0,
        pitch: 0,
        channel: 0,
        flags: {
          looping: true, // 0x00000001=looping
          '3dSound': true, // 0x00000002=3D sound
          stopOutOfRange: true, // 0x00000004=stop when out of range
          music: true // 0x00000008=music},
        },
        fadeRate: {
          in: 0,
          out: 0
        },
        distance: {
          min: 0,
          max: 0,
          cutoff: 0
        }
      }

      sound.name = outBufferToJSON.readString()
      sound.path = outBufferToJSON.readString()
      sound.eax = outBufferToJSON.readString()

      const flags = outBufferToJSON.readInt()
      sound.flags = {
        looping: !!(flags & 0b1), // 0x00000001=looping
        '3dSound': !!(flags & 0b10), // 0x00000002=3D sound
        stopOutOfRange: !!(flags & 0b100), // 0x00000004=stop when out of range
        music: !!(flags & 0b1000) // 0x00000008=music
      }

      sound.fadeRate = {
        in: outBufferToJSON.readInt(),
        out: outBufferToJSON.readInt()
      }

      sound.volume = outBufferToJSON.readInt()
      sound.pitch = outBufferToJSON.readFloat()

      // Unknown values
      outBufferToJSON.readFloat()
      outBufferToJSON.readInt()

      sound.channel = outBufferToJSON.readInt()

      sound.distance = {
        min: outBufferToJSON.readFloat(),
        max: outBufferToJSON.readFloat(),
        cutoff: outBufferToJSON.readFloat()
      }

      // Unknown values
      outBufferToJSON.readFloat()
      outBufferToJSON.readFloat()
      outBufferToJSON.readFloat()
      outBufferToJSON.readFloat()
      outBufferToJSON.readFloat()
      outBufferToJSON.readFloat()

      sound.variableName = outBufferToJSON.readString()

      // Unknown values
      outBufferToJSON.readString()
      outBufferToJSON.readString()
      outBufferToJSON.readChars(4)
      outBufferToJSON.readChars(1)
      outBufferToJSON.readChars(4)
      outBufferToJSON.readChars(4)
      outBufferToJSON.readChars(4)
      outBufferToJSON.readChars(1)
      outBufferToJSON.readChars(4)

      result.push(sound)
    }

    return {
      errors: [],
      json: result
    }
  }
}
