unit Measure;

interface

uses Cables;

var
  Mode: (LocalAnswer, RemoteAsk, RemoteAnswer);
  OneItemPerLine, Verbose: boolean;
  InitRandom: longint;
  EqualVoteMode: (AlwaysTrue, AlwaysFalse, RandomChoice, Previous, NotPrevious);

procedure AskValues(var mm: Wire; var nn: Switch) ;
function Probe(a: Wire): boolean ;
procedure Change(b: Switch) ;
procedure Done(cc: Connectivity) ;

implementation

uses Generate, Judging;

const
  Test = true ;

var
  Measuring: boolean; { measurer has started }

  Teasing, Helping: boolean; { at most one is true }
  C: Cable;
  Defined: boolean; { C defined }
  S: SwitchSet; { switch b is conducting == b in S }
  CS: SwitchSet; { CS = [1..N] - S }
  Z: array [Wire] of SwitchSet; { measurer knows that C[a] in Z[a] }
  prevProbe: array [Wire] of boolean;

  total: longint; { # commands }
  redundant, inefficient: longint; { # redun., ineff. probes }
  count: array [ Command ] of longint; { count[c] = # c-commands }
  probes: array [Wire] of integer;
  changes: array [Switch] of integer;

procedure WriteCounts(var out: text; long: boolean);
  var cmd: Command; a: Wire; b: Switch;
  begin
  writeln(out, 'Number of commands:') ;
  writeln(out, probeChr:6, changeChr:6, doneChr:6, '  Redun Ineff Total') ;
  for cmd := Low(Command) to High(Command) do write(out, count[cmd]:6) ;
  writeln(out, redundant:7, inefficient:6, total:6) ;
  if long then begin
    writeln(out, 'Probe counts per wire:') ;
    for a := 1 to C.M do begin
      if a mod 10 = 1 then write(out, '   ') ;
      write(out, probes[a]:4) ;
      if a mod 10 = 0 then writeln(out)
      else if a mod 5 = 0 then write(out, '   ')
      end { for a } ;
    if C.M mod 10 <> 0 then writeln(out) ;
    writeln(out, 'Change counts per switch:') ;
    for b := 1 to C.N do begin
      if b mod 10 = 1 then write(out, '   ') ;
      write(out, changes[b]:4) ;
      if b mod 10 = 0 then writeln(out)
      else if b mod 5 = 0 then write(out, '   ')
      end { for b } ;
    if C.N mod 10 <> 0 then writeln(out)
    end { if long }
  end { WriteCounts } ;

procedure Error ;
  begin
  if Measuring then writeln('Must first call Done')
  else writeln('Must first call AskValues') ;
  halt
  end { Error } ;

procedure AskValues(var mm: Wire; var nn: Switch) ;
  var
    ch, dummy: char; options: set of char; sm, sn: string;
    a: Wire; b: Switch; cmd: Command;
  begin
  if Measuring then Error ;
  if Verbose then write(LogFile, 'Ask values: ') ;
  Flush(LogFile) ;

  case Mode of
  LocalAnswer, RemoteAnswer: begin
    repeat
      write('Cable: G(enerate, R(eadFile, T(easer, H(elper') ;
      options := ['G', 'R', 'T', 'H'] ;
      if Defined then begin
        write(', S(ame') ;
        Include(options, 'S')
        end { if } ;
      write(', Q(uit: ') ; Include(options, 'Q') ;
      readln(ch) ; ch := UpCase(ch)
      until ch in options ;
    case ch of
      'G': GenerateCable(C) ;
      'R': ReadCable(C) ;
      'T', 'H': begin
        if MequN then begin
          write('Give M: ') ;
          readln(C.M) ; C.N := C.M
          end { then }
        else begin
          write('Give M, N: ') ;
          readln(C.M, dummy, C.N)
          end { else }
        end ;
      'S': { nothing } ;
      'Q': with C do begin M := 0 ; N := 0 end ;
      end { case } ;
    Teasing := (ch = 'T') or (C.M < 0) ;
    Helping := (ch = 'H') ;
    with C do M := abs(M) ;
    Defined := not (Teasing or Helping) ;
    with C do begin
      mm := M ; nn := N ;
      S := [ ] ;
      CS := [1..N] ;
      for a := 1 to M do begin
        Z[a] := CS ;
        prevProbe[a] := false
        end { for a }
      end { with C } ;
    writeln('Conversing...')
    end { LocalAnswer, RemoteAnswer } ;

  RemoteAsk: begin
    read(mm) ;
    if MequN then begin readln ; nn := mm end
    else readln(nn) ;
    C.M := mm ; C.N := nn
    end { RemoteAsk } ;
  end { case } ;

  if Verbose then
    writeln(LogFile, 'M = ', mm:1, ' and N = ', nn:1) ; Flush(LogFile) ;
  if Teasing then
    writeln(LogFile, 'Teasing for ', mm, ' wires and ', nn, ' switches')
  else if Helping then
    writeln(LogFile, 'Helping for ', mm, ' wires and ', nn, ' switches')
  else if Defined then ShowCable(LogFile, C, true) ;
  for cmd := Low(Command) to High(Command) do count[cmd] := 0 ;
  total := 0 ; redundant := 0 ; inefficient := 0 ;
  for a := 1 to mm do probes[a] := 0 ;
  for b := 1 to nn do changes[b] := 0 ;
  if Mode = RemoteAnswer then begin
    Str(mm:1, sm) ; Str(nn:1, sn) ;
    WriteRemote(sm) ;
    if not MequN then begin
      if OneItemPerLine then WriteRemoteCR else WriteRemote(' ') ;
      WriteRemote(sn)
      end { if };
    WriteRemoteCR
    end { if } ;
  if InitRandom < 0 then Randomize
  else RandSeed := InitRandom ;
  if Verbose then writeln(LogFile, '  RandSeed = ', RandSeed:1) ;
  Measuring := true ;
  writeln(LogFile) ;
  writeln(LogFile, 'Begin of Conversation') ;
  if not Verbose then
    writeln(LogFile, '  (Probes and Changes not logged)')
  end { AskValues } ;

function Probe(a: Wire): boolean ;
  var prb: boolean; r: char; i: Wire;
      V, Vc: SwitchSet; t, tc: word; lt, ltc: integer;
  begin
  if not Measuring then Error ;
  if Verbose then begin
    write(LogFile, 'Probe wire ') ;
    Flush(LogFile)
    end { if } ;
  if Mode = RemoteAnswer then begin
    a := ReadRemoteInt;
    ReadRemoteCR
    end { if } ;
  if Verbose then begin
    write(LogFile, a:3, ': ') ;
    Flush(LogFile)
    end { if } ;

  case Mode of
  LocalAnswer, RemoteAnswer: begin
    V := Z[a] * S ; Vc := Z[a] * CS ;
    t := Card(V, C.N) ; tc := Card(Vc, C.N) ;
    lt := Log2(t) ; ltc := Log2(tc) ;
    if Teasing then begin
      if Verbose then begin
        writeln(LogFile) ;
        writeln(LogFile, '  (2-log counts: ', lt:1, ', ', ltc:1, ')')
        end { if } ;
      if lt <> ltc then prb := (lt >= ltc)
      else begin
        {$R- collect "votes" }
        t := 0 ; tc := 0 ;
        for i := 1 to C.M do if i <> a then begin
          inc(t, Card(V * Z[i], C.N)) ; inc(tc, Card(Vc * Z[i], C.N))
          end { for / if } ;
        if Verbose then writeln(LogFile, '  (votes: ', t:1, ', ', tc:1, ') ') ;
        if t = tc then
          case EqualVoteMode of
            AlwaysTrue: prb := true ;
            AlwaysFalse: prb := false ;
            RandomChoice: prb := odd(Random(2)) ;
            Previous: prb := prevProbe[a] ;
            NotPrevious: prb := not prevProbe[a] ;
            end { case }
        else prb := (t < tc)
        end {$R+ else }
      end { then }
    else if Helping then begin
      if t = 0 then prb := false
      else if tc = 0 then prb := not prevProbe[a]
      else prb := (t <= tc)
      end { Helping }
    else
      prb := C.f[a] in S ;
    if prb then Z[a] := V else Z[a] := Vc ;
    prevProbe[a] := prb ;
    if Verbose then
      writeln(LogFile, prb:1) ;
    if (lt = -1) or (ltc = -1) then begin
      inc(redundant) ;
      if Verbose then writeln(LogFile, '  Redundant probe')
      end
    else if abs(lt - ltc) > 1 then begin
      inc(inefficient) ;
      if Verbose then writeln(LogFile, '  Inefficient probe')
      end ;
    end { LocalAnswer, RemoteAnswer } ;

  RemoteAsk: begin
    write(probeChr) ;
    if OneItemPerLine then writeln else write(' ') ;
    writeln(a:1) ;
    readln(r) ;
    prb := (r = yesChr) ;
    if Verbose then
      writeln(LogFile, prb:1)
    end { RemoteAsk } ;
  end { case } ;

  Flush(LogFile) ;
  inc(count[probeCmd]) ; inc(total) ; inc(probes[a]) ;
  if Mode = RemoteAnswer then begin
    if prb then WriteRemote(yesChr)
    else WriteRemote(noChr) ;
    WriteRemoteCR
    end { if } ;
  Probe := prb
  end { Probe } ;

procedure Change(b: Switch) ;
  var r: char; chg: boolean;
  begin
  if not Measuring then Error ;
  if Verbose then begin
    write(LogFile, ' ':24, 'Change switch ') ;
    Flush(LogFile)
    end { if } ;
  if Mode = RemoteAnswer then begin
    b := ReadRemoteInt ;
    ReadRemoteCR
    end { if } ;
  if Verbose then begin
    write(LogFile, b:3, ': ') ;
    Flush(LogFile)
    end { if } ;

  case Mode of
  LocalAnswer, RemoteAnswer: begin
    if b in S then begin
      Exclude(S, b) ;
      Include(CS, b)
      end { then }
    else begin
      Include(S, b) ;
      Exclude(CS, b)
      end { else } ;
    chg := (b in S)
    end { LocalAnswer, RemoteAnswer };

  RemoteAsk: begin
    write(changeChr) ;
    if OneItemPerLine then writeln else write(' ') ;
    writeln(b:1) ;
    readln(r) ; chg := (r = yesChr) { ignore }
    end { RemoteAsk } ;
  end { case } ;

  inc(count[changeCmd]) ; inc(total) ; inc(changes[b]) ;
  if Verbose then
    writeln(LogFile, chg:1) ;
  if Mode = RemoteAnswer then begin
    if chg then WriteRemote(yesChr)
    else WriteRemote(noChr) ;
    WriteRemoteCR
    end { if } ;
  end { Change } ;

procedure Done(cc: Connectivity) ;
  var a: Wire; r: boolean; ch: char;
  begin
  if not Measuring then Error ;
  write(LogFile, 'Done:') ; Flush(LogFile) ;
  if Mode = RemoteAnswer then begin
    for a := 1 to C.M do cc[a] := ReadRemoteInt ;
    ReadRemoteCR
    end { then } ;
  for a := 1 to C.M do write(LogFile, ' ', cc[a]:1) ;
  writeln(LogFile) ;
  writeln(LogFile, 'End of Conversation') ;
  writeln(LogFile) ;
  inc(count[doneCmd]) ; inc(total) ;
  WriteCounts(LogFile, true) ;

  case Mode of
  LocalAnswer, RemoteAnswer: begin
    writeln ;
    WriteCounts(output, false) ;
    writeln(LogFile) ;
    writeln(LogFile, 'Evaluation:') ;
    r := true ;
    with C do for a := 1 to M do begin
      if Teasing then begin
        if Z[a] = [ cc[a] ] then { correct }
          f[a] := cc[a]
        else begin { incorrect }
          f[a] := RandomSwitch(Z[a] - [ cc[a] ], N) ;
          if cc[a] in Z[a] then begin { bad luck }
            write('BAD LUCK at wire ', a:1, ': ') ;
            write(cc[a]:1, ' could be any in ')
            end { bad luck }
          else begin { error }
            write('ERROR at wire ', a:1, ': ') ;
            write(cc[a]:1, ' should at least be in ')
            end { error } ;
          WriteSwitches(output, Z[a], N, true) ;
          r := false
          end { incorrect }
        end { Teasing }
      else if Helping then begin
        if cc[a] in Z[a] then begin { correct }
          f[a] := cc[a] ;
          if Z[a] <> [ cc[a] ] then begin { lucky }
            write('LUCKY at wire ', a:1, ': ') ;
            write(cc[a]:1, ' could be any in ') ;
            WriteSwitches(output, Z[a], N, true) ;
            end { lucky }
          end { correct }
        else begin { error }
          write('ERROR at wire ', a:1, ': ') ;
          write(cc[a]:1, ' should be in ') ;
          WriteSwitches(output, Z[a], N, true) ;
          r := false
          end { error }
        end { Helping }
      else begin { normal }
        if cc[a] = f[a] then begin { correct }
          if Z[a] <> [ cc[a] ] then begin { lucky }
            write('LUCKY at wire ', a:1, ': ') ;
            write(cc[a]:1, ' could be any in ') ;
            WriteSwitches(output, Z[a], N, true)
            end { lucky }
          end { correct }
        else begin { incorrect }
          write('ERROR at wire ', a:1, ': ') ;
          writeln(cc[a]:1, ' should be ', f[a]:1) ;
          r := false
          end { incorrect }
        end { normal }
      end { with C / for a } ;
    Defined := true ;
    if r then begin
      writeln('CORRECT') ;
      writeln(LogFile, '  CORRECT')
      end
    else begin
      writeln('WRONG') ;
      writeln(LogFile, '  WRONG')
      end ;
    writeln(LogFile) ;
    if Defined then begin
      if Teasing or Helping then ShowCable(LogFile, C, true) ;
      ShowCable(output, C, false) ;
      WriteCable(C)
      end { if Defined }
    end { LocalAnswer, RemoteAnswer } ;

  RemoteAsk: begin
    write(doneChr) ;
    for a := 1 to C.M do begin
      if OneItemPerLine then writeln else write(' ') ;
      write(cc[a]:1)
      end { for a } ;
    writeln
    end { RemoteAsk } ;
  end { case } ;

  Flush(LogFile) ;
  Measuring := false
  end { Done } ;

begin
Defined := false ;
Measuring := false ;
Mode := LocalAnswer ; { maybe changed by the using program }
OneItemPerLine := false ; { may be changed by the using program }
Verbose := false ; { may be changed by the using program }
InitRandom := -1 ; { may be changed by the using program }
EqualVoteMode := AlwaysTrue ; { may be changed by the using program }
end.