import { describe, it, expect } from 'vitest';
import { encodeAX25, Frame, UnnumberedFrame, InformationFrame, SupervisoryFrame, buildIControl, buildSControl, buildUControl, encodeFrame, toHDLC } from '../src/frame';
import { Address } from '../src/address';
import { unescapeBuffer, computeFcs } from '../src/hdlc';

describe('Frame.buildSControl', () => {
  it('builds S control byte', () => {
    const c = buildSControl('RNR', 4, 0);
    // bits 0-1 == 01 and s-code 1 in bits 2-3
    expect((c & 0x03)).toBe(0x01);
  });
});

describe('Frame.encodeFrame', () => {
  it('builds I control byte and roundtrips encode/parse', () => {
    const dst = new Address('DST', 0);
    const src = new Address('SRC', 1);
    const ns = 3;
    const nr = 5;
    const pf = 1;
    const control = buildIControl(ns, nr, pf);
    const info = new TextEncoder().encode('payload');
    const f = new InformationFrame(src, dst, control, info, ns, nr, pf);
    const buf = encodeFrame(f);
    const parsed = Frame.fromBytes(buf);
    expect(parsed instanceof InformationFrame).toBe(true);
    const p = parsed as InformationFrame;
    expect(p.ns).toBe(ns);
    expect(p.nr).toBe(nr);
    expect(p.pf).toBe(pf);
    expect(new TextDecoder().decode(p.info)).toBe('payload');
  });

  it('encodes UI unnumbered frames including PID and info', () => {
    const dst = new Address('APRS', 0);
    const src = new Address('N0CALL', 0);
    const control = buildUControl('UI', 0);
    const info = new TextEncoder().encode('!Hello');
    const f = new UnnumberedFrame(src, dst, control, 'UI', 0, info);
    f.pid = 0xf0;
    const buf = encodeFrame(f);
    const parsed = Frame.fromBytes(buf);
    expect(parsed instanceof UnnumberedFrame).toBe(true);
    const u = parsed as UnnumberedFrame;
    expect(u.pid).toBe(0xf0);
    expect(new TextDecoder().decode(u.info)).toBe('!Hello');
  });
});

describe('Frame.fromBytes', () => {
  it('parses a simple UI frame', () => {
    const info = new TextEncoder().encode('hello');
    const payload = encodeAX25('DEST-0', 'SRC-1', info, { ui: true });
    const f = Frame.fromBytes(payload);
    expect(f.source).toBeInstanceOf(Address);
    expect(f.destination).toBeInstanceOf(Address);
    expect(f.source.toString()).toBe('SRC-1');
    expect(f.destination.toString()).toBe('DEST-0');
    expect(new TextDecoder().decode(f.info)).toBe('hello');
  });
  it('parses frames with multiple address fields', () => {
    const dest = new Address('DST', 0);
    const src = new Address('SRC', 1);
    const digi = new Address('DIGI', 2);
    const parts: number[] = [];
    parts.push(...dest.toBytes(false));
    parts.push(...src.toBytes(false));
    parts.push(...digi.toBytes(true));
    parts.push(0x03); // control
    parts.push(0xf0); // pid
    parts.push(...new TextEncoder().encode('spec'));
    const payload = Uint8Array.from(parts);
    const f = Frame.fromBytes(payload);
    expect(f.source.toString()).toBe('SRC-1');
    expect(f.destination.toString()).toBe('DST-0');
    expect(new TextDecoder().decode(f.info)).toBe('spec');
  });
});

describe('Frame.toHDLC', () => {
  it('produces HDLC frame with FCS via toHDLC', () => {
    const dst = new Address('APRS', 0);
    const src = new Address('N0CALL', 0);
    const control = buildUControl('UI', 0);
    const info = new TextEncoder().encode('!Hello');
    const f = new UnnumberedFrame(src, dst, control, 'UI', 0, info);
    f.pid = 0xf0;
    const h = toHDLC(f);
    expect(h[0]).toBe(0x7e);
    expect(h[h.length - 1]).toBe(0x7e);
    const inner = h.slice(1, h.length - 1);
    const unescaped = unescapeBuffer(inner);
    // last two bytes of unescaped payload must equal computed FCS (LE)
    const ax = encodeFrame(f);
    const fcs = computeFcs(ax);
    expect(unescaped[unescaped.length - 2]).toBe(fcs & 0xff);
    expect(unescaped[unescaped.length - 1]).toBe((fcs >> 8) & 0xff);
  });
});
