### ----------------------------------------------------
### -- Arithmetic::PaperAndPencil
### -- Licenses: Artistic-2.0
### -- Authors: Jean Forget
### -- File: lib/Arithmetic/PaperAndPencil.rakumod
### ----------------------------------------------------
### -- Chunk 1 of 5
# -*- encoding: utf-8; indent-tabs-mode: nil -*-

use Arithmetic::PaperAndPencil::Action;
use Arithmetic::PaperAndPencil::Char;
use Arithmetic::PaperAndPencil::Number;
use Arithmetic::PaperAndPencil::Label;

unit class Arithmetic::PaperAndPencil:ver<0.0.2>:auth<zef:jforget>;

has Arithmetic::PaperAndPencil::Action @.action is rw;

multi method BUILD () {
  @.action = ();
}

multi method BUILD(Str:D :$csv) {
  my $fh = $csv.IO.open(:r);
  @.action = $fh.lines.map( { Arithmetic::PaperAndPencil::Action.new-from-csv(csv => $_) } );
}

method csv(--> Str) {
 join '', @!action.map( { $_.csv ~ "\n" } );
}

method addition(*@numbers --> Arithmetic::PaperAndPencil::Number) {
  if @numbers.elems == 0 {
    die "The addition needs at least one number to add";
  }

  my Arithmetic::PaperAndPencil::Action $action;
  my Int $nb         = @numbers.elems;
  my Int $radix      = @numbers[0].radix;
  my Int $max-length = 0;
  my     @digits; # storing the numbers' digits
  my     @total;  # storing the total's digit positions

  $action .= new(level => 9, label => "TIT01", val1 => $radix.Str);
  self.action.push($action);

  for @numbers.kv -> $i, $n {
    # checking the number
    if $n.radix != $radix {
      die "All numbers must have the same radix";
    }
    # writing the number
    $action .= new(level => 5, label => 'WRI00', w1l => $i, w1c => 0, w1val => $n.value);
    self.action.push($action);
    # preparing the horizontal line
    if $max-length < $n.chars {
      $max-length = $n.chars;
    }
    # feeding the table of digits
    for $n.value.flip.comb.kv -> $j, $x {
      @digits[$j].push( %( lin => $i, col => -$j, val => $x) );
    }
  }
  $action .= new(level => 2, label => 'DRA02', w1l => $nb - 1, w1c => 1 - $max-length
                                             , w2l => $nb - 1, w2c => 0);
  self.action.push($action);
  for 0 ..^$max-length -> $j {
    @total[$j] = %( lin => $nb, col => -$j );
  }
  my $result = self!adding(@digits, @total, 0, $radix);
  return Arithmetic::PaperAndPencil::Number.new(value => $result, radix => $radix);
}

method subtraction(Arithmetic::PaperAndPencil::Number :$high
                 , Arithmetic::PaperAndPencil::Number :$low
                 , Str :$type = 'std'
                 --> Arithmetic::PaperAndPencil::Number
                 ) {
  my Arithmetic::PaperAndPencil::Action $action;
  my Int $radix = $high.radix;
  my Int $leng  = $high.chars;
  if $low.radix != $radix {
    die "The two numbers have different bases: {$radix} != {$low.radix}";
  }
  if $type ne 'std' | 'compl' {
    die "Subtraction type '$type' unknown";
  }
  if $high ☈< $low {
    die "The high number {$high.value} must be greater than or equal to the low number {$low.value}";
  }
  if @.action {
    self.action[* - 1].level = 0;
  }
  if $type eq 'std' {
    $action .= new(level => 9, label => 'TIT02', val1 => $high.value, val2 => $low.value, val3 => $radix.Str);
    self.action.push($action);
    # set-up
    $action .= new(level => 5, label => 'WRI00', w1l => 0, w1c => $leng, w1val => $high.value);
    self.action.push($action);
    $action .= new(level => 5, label => 'WRI00', w1l => 1, w1c => $leng, w1val => $low.value);
    self.action.push($action);

    # computation
    my Str $result = '';
    $result = self!embedded-sub(basic-level => 0, l-hi => 0, c-hi => $leng, high => $high
                                                , l-lo => 1, c-lo => $leng, low  => $low
                                                , l-re => 2, c-re => $leng);
    self.action[* - 1].level = 0;
    return Arithmetic::PaperAndPencil::Number.new(radix => $radix, value => $result);
  }
  else {
    $action .= new(level => 9, label => 'TIT15', val1 => $high.value, val2 => $low.value, val3 => $radix.Str);
    self.action.push($action);
    my Arithmetic::PaperAndPencil::Number $complement = $low.complement($leng);
    # set-up
    $action .= new(level => 5, label => 'SUB03', val1 => $radix.Str, val2 => $low.value, val3 => $complement.value
                                               , w1l => 0, w1c => $leng, w1val => $high.value
                                               , w2l => 1, w2c => $leng, w2val => $complement.value);
    self.action.push($action);
    $action .= new(level => 2, label => 'DRA02', w1l => 1, w1c => 1
                                               , w2l => 1, w2c => $leng);
    self.action.push($action);

    my @digits; # storing the numbers' digits
    my @result; # storing the result's digit positions
    my Str $compl-val = '0' x ($leng - $complement.chars) ~ $complement.value;
    for (0 ..^ $leng) -> $i {
      @digits[$i; 0] = %( lin => 0, col => $leng - $i, val => $high.value.substr($leng - $i - 1, 1));
      @digits[$i; 1] = %( lin => 1, col => $leng - $i, val => $compl-val .substr($leng - $i - 1, 1));
      @result[$i]    = %( lin => 2, col => $leng - $i);
    }
    my Str $result = self!adding(@digits, @result, 0, $radix).substr(1);
    # getting rid of leading zeroes except if the result is zero
    $result ~~ s/^ '0' * //;
    if $result eq '' {
      $result = '0';
    }
    $action .= new(level => 0, label => 'SUB04', val1 => $result, r1l => 2, r1c => 0, r1val => '1', r1str => True);
    self.action.push($action);
    return Arithmetic::PaperAndPencil::Number.new(radix => $radix, value => $result);
  }
}

method multiplication(Arithmetic::PaperAndPencil::Number :$multiplicand
                    , Arithmetic::PaperAndPencil::Number :$multiplier
                    , Str :$type      = 'std'
                    , Str :$direction = 'ltr'   # for the 'boat' type, elementary products are processed left-to-right or right-to-left ('rtl')
                    , Str :$mult-and-add = 'separate'   # for the 'boat' type, addition is a separate subphase (contrary: 'combined')
                    , Str :$product      = 'L-shaped'   # for the 'jalousie-?" types, the product is L-shaped along the rectangle (contrary: 'straight' on the bottom line)
                    --> Arithmetic::PaperAndPencil::Number
                    ) {
  my Arithmetic::PaperAndPencil::Action $action;
  if $multiplicand.radix != $multiplier.radix {
    die "Multiplicand and multiplier have different bases: {$multiplicand.radix} != {$multiplier.radix}";
  }
  my Str $title = '';
  my Int $radix = $multiplicand.radix;
  given $type {
    when 'std'        { $title = 'TIT03' ; }
    when 'shortcut'   { $title = 'TIT04' ; }
    when 'prepared'   { $title = 'TIT05' ; }
    when 'jalousie-A' { $title = 'TIT06' ; }
    when 'jalousie-B' { $title = 'TIT07' ; }
    when 'boat'       { $title = 'TIT08' ; }
    when 'russian'    { $title = 'TIT19' ; }
  }
  if $title eq '' {
    die "Multiplication type '$type' unknown";
  }
  if $type eq 'jalousie-A' | 'jalousie-B' {
    if $product ne 'L-shaped' | 'straight' {
      die "Product shape '$product' should be 'L-shaped' or 'straight'";
    }
  }
  if $type eq 'boat' {
    if $direction ne 'ltr' | 'rtl' {
      die "Direction '$direction' should be 'ltr' (left-to-right) or 'rtl' (right-to-left)";
    }
    if $mult-and-add ne 'separate' | 'combined' {
      die "Parameter mult-and-add '$mult-and-add' should be 'separate' or 'combined'";
    }
  }

  my Int $len1 = $multiplicand.chars;
  my Int $len2 = $multiplier.chars;
  if @.action {
    self.action[* - 1].level = 0;
  }
  $action .= new(level => 9
               , label => $title
               , val1  => $multiplicand.value
               , val2  => $multiplier.value
               , val3  => $multiplier.radix.Str
               );
  self.action.push($action);

  # caching the partial products for prepared and shortcut multiplications
  my %mult-cache = 1 => $multiplicand;
  if $type eq 'prepared' {
    my Str $limit = $multiplier.value.comb.max;
    self!preparation(factor => $multiplicand, limit => $limit, cache => %mult-cache);
  }

  if $type eq 'std' | 'shortcut' | 'prepared' {
    # set-up
    $action .= new(level => 5, label => 'WRI00', w1l => 0, w1c => $len1 + $len2, w1val => $multiplicand.value
                                               , w2l => 1, w2c => $len1 + $len2, w2val => $multiplier.value);
    self.action.push($action);
    $action .= new(level => 2, label => 'DRA02', w1l => 1, w1c => min($len1, $len2)
                                               , w2l => 1, w2c => $len1 + $len2);
    self.action.push($action);

    # multiplication of two single-digit numbers
    if $len1 == 1 && $len2 == 1 {
      my Arithmetic::PaperAndPencil::Number $pdt = $multiplier ☈× $multiplicand;
      $action .= new(level => 0, label => 'MUL02'
                   , r1l => 0, r1c => 2, r1val => $multiplier.value   , val1 => $multiplier.value
                   , r2l => 1, r2c => 2, r2val => $multiplicand.value , val2 => $multiplicand.value
                   , w1l => 2, w1c => 2, w1val => $pdt.value          , val3 => $pdt.value
                   );
      self.action.push($action);
      return $pdt;
    }
    # multiplication with a single-digit multiplier
    if $len2 == 1 && $type eq 'prepared' {
      my Arithmetic::PaperAndPencil::Number $pdt;
      $pdt = %mult-cache{$multiplier.value};
      $action .= new(level => 0, label => 'WRI05', val1 => $pdt.value
                   , w1l => 2, w1c => $len1 + 1, w1val => $pdt.value
                   );
      self.action.push($action);
      return $pdt;
    }
    if $len2 == 1 {
      my Arithmetic::PaperAndPencil::Number $pdt;
      $pdt = self!simple-mult(:basic-level(0), :l-md(0), :c-md($len1 + 1), :multiplicand($multiplicand)
                                             , :l-mr(1), :c-mr($len1 + 1), :multiplier(  $multiplier)
                                             , :l-pd(2), :c-pd($len1 + 1) );
      self.action[* - 1].level = 0;
      return $pdt;
    }
    # multiplication with a multi-digit multiplier
    my Arithmetic::PaperAndPencil::Number $pdt;
    $pdt = self!adv-mult(:basic-level(0), :l-md(0), :c-md($len1 + $len2), :multiplicand($multiplicand)
                                        , :l-mr(1), :c-mr($len1 + $len2), :multiplier(  $multiplier)
                                        , :l-pd(2), :c-pd($len1 + $len2)
                                        , :type($type), :cache(%mult-cache));
    self.action[* - 1].level = 0;
    return $pdt;
  }
  if $type eq 'jalousie-A' | 'jalousie-B' {
    $action .= new(level => 5, label => 'DRA02', w1l => 0, w1c => 1
                                               , w2l => 0, w2c => 2 × $len1);
    self.action.push($action);
    $action .= new(level => 5, label => 'DRA01', w1l => 1        , w1c => 0
                                               , w2l => 2 × $len2, w2c => 0);
    self.action.push($action);
    $action .= new(level => 5, label => 'DRA01', w1l => 1        , w1c => 2 × $len1
                                               , w2l => 2 × $len2, w2c => 2 × $len1);
    self.action.push($action);
    $action .= new(level => 5, label => 'DRA02', w1l => 2 × $len2, w1c => 1
                                               , w2l => 2 × $len2, w2c => 2 × $len1);
    self.action.push($action);
  }
  if $type eq 'jalousie-A' {
    for 1 .. $len1 -> $i {
      $action .= new(level => 5, label => 'WRI00', w1l => 0, w1c => 2 × $i - 1, w1val => $multiplicand.value.substr($i - 1, 1));
      self.action.push($action);
    }
    for 1 .. $len2 -> $i {
      $action .= new(level => 5, label => 'WRI00', w1l => 2 × $i, w1c => 2 × $len1 + 1, w1val => $multiplier.value.substr($i - 1, 1));
      self.action.push($action);
    }
    for 1 ..^ $len1 + $len2 -> $i {
      my $l1 = 1;
      my $c1 = 2 × $i;
      my $l2 = 2 × $len2;
      my $c2 = 2 × ($i - $len2) + 1;
      if $c1 ≥ 2 × $len1 {
        $l1 += $c1 - 2 × $len1;
        $c1  = 2 × $len1;
      }
      if $c2 ≤ 0 && $product eq 'L-shaped' {
        $l2 -= 1 - $c2;
        $c2  = 1;
      }
      $action .= new(level => 5, label => 'DRA04', w1l => $l1, w1c => $c1, w2l => $l2, w2c => $c2);
      self.action.push($action);
    }
    # end of set-up phase
    self.action[* - 1].level = 2;

    # multiplication phase
    my @partial;
    for 1 .. $len2 -> $l {
      my Arithmetic::PaperAndPencil::Number $x .= new(radix => $radix, value => $multiplier.value.substr($l - 1, 1));
      for 1 .. $len1 -> $c {
        my Arithmetic::PaperAndPencil::Number $y .= new(radix => $radix, value => $multiplicand.value.substr($c - 1, 1));
        my Arithmetic::PaperAndPencil::Number $pdt   = $x ☈× $y;
        my Arithmetic::PaperAndPencil::Number $unit  = $pdt.unit;
        my Arithmetic::PaperAndPencil::Number $carry = $pdt.carry;
        $action .= new(level => 5, label => 'MUL01', r1l => 2 × $l    , r1c => 2 × $len1 + 1, r1val => $x.value    , val1 => $x.value
                                                   , r2l => 0         , r2c => 2 × $c - 1   , r2val => $y.value    , val2 => $y.value
                                                   , w1l => 2 × $l - 1, w1c => 2 × $c - 1   , w1val => $carry.value, val3 => $pdt.value
                                                   , w2l => 2 × $l    , w2c => 2 × $c       , w2val => $unit.value
                                                   );
        self.action.push($action);
        @partial[$len1 + $len2 - $l - $c    ; 2 × $l    ] = %( lin => 2 × $l    , col => 2 × $c    , val => $unit.value);
        @partial[$len1 + $len2 - $l - $c + 1; 2 × $l - 1] = %( lin => 2 × $l - 1, col => 2 × $c - 1, val => $carry.value);
      }
      # end of line
      self.action[* - 1].level = 3;
    }
    # end of multiplication phase
    self.action[* - 1].level = 2;

    # Addition phase
    my @final;
    my Int $limit;
    given $product {
      when 'L-shaped' { $limit = $len1;         }
      when 'straight' { $limit = $len1 + $len2; }
    }
    for 0 ..^ $limit -> $i {
      @final[$i] = %( lin => 2 × $len2 + 1, col => 2 × ($len1 - $i) - 1);
    }
    for $limit ..^ $len1 + $len2 -> $i {
      @final[$i] = %( lin => 2 × ($len1 + $len2 - $i), col => 0 );
    }
    my Str $result = self!adding(@partial, @final, 0, $radix);
    self.action[* - 1].level = 0;
    return Arithmetic::PaperAndPencil::Number.new(radix => $radix, value => $result);
  }
  if $type eq 'jalousie-B' {
    for 1 .. $len1 -> $i {
      $action .= new(level => 5, label => 'WRI00', w1l => 0, w1c => 2 × $i, w1val => $multiplicand.value.substr($i - 1, 1));
      self.action.push($action);
    }
    for 1 .. $len2 -> $i {
      $action .= new(level => 5, label => 'WRI00', w1l => 2 × ($len2 - $i + 1), w1c => 0, w1val => $multiplier.value.substr($i - 1, 1));
      self.action.push($action);
    }
    for 1 - $len2 ..^ $len1 -> $i {
      my $l1 = 1;
      my $c1 = 1 + 2 × $i;
      my $l2 = 2 × $len2;
      my $c2 = 2 × ($i + $len2);
      if $c1 ≤ 0 {
        $l1 += 1 - $c1;
        $c1  = 1;
      }
      if $c2 ≥ 2 × $len1 && $product eq 'L-shaped' {
        $l2 -= $c2 - 2 × $len1;
        $c2  = 2 × $len1;
      }
      $action .= new(level => 5, label => 'DRA03', w1l => $l1, w1c => $c1, w2l => $l2, w2c => $c2);
      self.action.push($action);
    }
    # end of set-up phase
    self.action[* - 1].level = 2;

    # multiplication phase
    my @partial;
    for 1 .. $len2 -> $l {
      my Arithmetic::PaperAndPencil::Number $x .= new(radix => $radix, value => $multiplier.value.substr($len2 - $l, 1));
      for 1 .. $len1 -> $c {
        my Arithmetic::PaperAndPencil::Number $y .= new(radix => $radix, value => $multiplicand.value.substr($c - 1, 1));
        my Arithmetic::PaperAndPencil::Number $pdt   = $x ☈× $y;
        my Arithmetic::PaperAndPencil::Number $unit  = $pdt.unit;
        my Arithmetic::PaperAndPencil::Number $carry = $pdt.carry;
        $action .= new(level => 5, label => 'MUL01', r1l => 2 × $l    , r1c => 0         , r1val => $x.value    , val1 => $x.value
                                                   , r2l => 0         , r2c => 2 × $c    , r2val => $y.value    , val2 => $y.value
                                                   , w1l => 2 × $l    , w1c => 2 × $c - 1, w1val => $carry.value, val3 => $pdt.value
                                                   , w2l => 2 × $l - 1, w2c => 2 × $c    , w2val => $unit.value
                                                   );
        self.action.push($action);
        @partial[$len1 - $c + $l - 1; 2 × $l - 1] = %( lin => 2 × $l - 1, col => 2 × $c    , val => $unit.value);
        @partial[$len1 - $c + $l    ; 2 × $l    ] = %( lin => 2 × $l    , col => 2 × $c - 1, val => $carry.value);
      }
      # end of line
      self.action[* - 1].level = 3;
    }
    # end of multiplication phase
    self.action[* - 1].level = 2;

    # Addition phase
    my @final;
    my Int $limit;
    given $product {
      when 'L-shaped' { $limit = $len2; }
      when 'straight' { $limit = 0;     }
    }
    for 0 ..^ $limit -> $i {
      @final[$i] = %( lin => 2 × $i + 2, col => 2 × $len1 + 1);
    }
    for $limit ..^ $len1 + $len2 -> $i {
      @final[$i] = %( lin => 2 × $len2 + 1, col => 2 × ($len1 + $len2 - $i) );
    }
    my Str $result = self!adding(@partial, @final, 0, $radix);
    self.action[* - 1].level = 0;
    return Arithmetic::PaperAndPencil::Number.new(radix => $radix, value => $result);
  }
  if $type eq 'boat' {
    # set up phase
    my Int $tot-len = $len1 + $len2 - 1;
    $action .= new(level => 5, label => 'WRI00', w1l =>  0, w1c => $tot-len, w1val => $multiplicand.value);
    self.action.push($action);
    $action .= new(level => 5, label => 'DRA02', w1l => -1, w1c => 0, w2l => -1, w2c => $tot-len);
    self.action.push($action);
    $action .= new(level => 2, label => 'DRA02', w1l =>  0, w1c => 0, w2l =>  0, w2c => $tot-len);
    self.action.push($action);

    # arrays of line numbers per column
    my @lines-below = ( 1 ) xx ($len1 + $len2);
    my @lines-above = (-1 ) xx ($len1 + $len2);
    my @result      = ('0') xx ($len1 + $len2);

    # multiplication phase
    my @partial;
    for $len2 .. $tot-len -> $col {
      my Arithmetic::PaperAndPencil::Number $x .= new(radix => $radix, value => $multiplicand.value.substr($col - $len2, 1));
      # write the multiplier at the proper column
      self!push-below($multiplier, $col, @lines-below);

      # partial products
      my     $range = 1 .. $len2;
      my Int $last  = $len2;
      if $direction eq 'rtl' {
        $range = $range.reverse;
        $last  = 1;
      }
      for $range.list -> $c {
        my Arithmetic::PaperAndPencil::Number $y .= new(radix => $radix, value => $multiplier.value.substr($c - 1, 1));
        my Arithmetic::PaperAndPencil::Number $pdt   = $x ☈× $y;
        $action .= new(level => 6, label => 'MUL01', val1 => $y.value, r1l => @lines-below[$col - $len2 + $c] - 1, r1c => $col - $len2 + $c, r1val => $y.value, r1str => True
                                                   , val2 => $x.value, r2l => 0                                  , r2c => $col             , r2val => $x.value, r2str => ($c == $last)
                                                   , val3 => $pdt.value);
        self.action.push($action);
        if $mult-and-add eq 'separate' {
          self!push-above($pdt, $col - $len2 + $c, @lines-above, @partial, $tot-len);
        }
        else {
          self!add-above($pdt, $col - $len2 + $c, @lines-above, @result);
        }
        self.action[* - 1].level = 4;
      }
      self.action[* - 1].level = 3;
    }

    # addition phase
    my @final;
    my Str $result;
    if $mult-and-add eq 'separate' {
      for 0 ..^ @lines-above.elems -> $col {
        @final[ $col] = %( lin => @lines-above[$tot-len - $col], col => $tot-len - $col );
      }
      $result = self!adding(@partial, @final, 0, $radix, striking => True);
    }
    else {
       $result = @result.join;
    }
    self.action[* - 1].level = 0;
    return Arithmetic::PaperAndPencil::Number.new(radix => $radix, value => $result);
  }
  if $type eq 'russian' {
    # set-up
    my Arithmetic::PaperAndPencil::Number $md = $multiplicand;
    my Arithmetic::PaperAndPencil::Number $mr = $multiplier;
    my Int $c-md = 2 × $len2 + 1 + $len1;
    my Int $c-mr = $len2;
    $action .= new(level => 3, label => 'WRI00', w1l => 0, w1c => $c-md, w1val => $md.value
                                               , w2l => 0, w2c => $c-mr, w2val => $mr.value);
    self.action.push($action);

    # first phase, doubling the multiplicand and halving the multiplier
    my Int $l = 0;
    my @lines = ();
    @lines.push( { mr => $mr, md => $md });
    while $mr.value ne '1' {
      $mr = self!halving( l1 => $l, c1 => $c-mr, l2 => $l + 1, c2 => $c-mr, number => $mr);
      $md = self!doubling(l1 => $l, c1 => $c-md, l2 => $l + 1, c2 => $c-md, number => $md);
      @lines.push( { mr => $mr, md => $md });
      $l++;
    }
    self.action[* - 1].level = 2;

    # second phase, testing even numbers and striking
    my @partial;
    my @final;
    for @lines.kv -> $l, $line {
      $mr = $line<mr>;
      $md = $line<md>;
      if $mr.is-odd {
        for $md.value.flip.comb.kv -> $i, $x {
          push @partial[$i], %( lin => $l, col => $c-md - $i, val => $x );
        }
      }
      else {
        $action .= new(level => 4, label => 'MUL03', val1 => $mr.value, r1l => $l, r1c => $c-mr, r1val => $mr.value
                                                   , val2 => $md.value, r2l => $l, r2c => $c-md, r2val => $md.value, r2str => True);
        self.action.push($action);
      }
      if $mr.value eq '1' {
        for $md.value.flip.comb.kv -> $i, $x {
          @final[$i] = %( lin => @lines.elems, col => $c-md - $i );
        }
        $action .= new(level => 2, label => 'DRA02'
                     , w1l => $l, w1c => $c-md + 1 - $md.chars
                     , w2l => $l, w2c => $c-md);
        self.action.push($action);
      }
    }
    self.action[* - 1].level = 2;

    # third phase, adding
    my Str $result = self!adding(@partial, @final, 0, $radix);
    self.action[* - 1].level = 0;

    return  Arithmetic::PaperAndPencil::Number.new(:radix($radix), :value($result));
  }
}

method division(Arithmetic::PaperAndPencil::Number :$dividend
              , Arithmetic::PaperAndPencil::Number :$divisor
              , Str :$type   = 'std'
              , Str :$result = 'quotient'
              , Str :$mult-and-sub is copy = 'combined'
              ) {
  my Arithmetic::PaperAndPencil::Action $action;
  my Int $radix = $dividend.radix;
  if $radix != $divisor.radix {
    die "Dividend and divisor have different bases: {$radix} != {$divisor.radix}";
  }
  if $divisor.value eq '0' {
    die "Division by zero is impossible";
  }

  if @.action {
    self.action[* - 1].level = 0;
  }
  my Int $len1  = $dividend.chars;
  my Int $len2  = $divisor .chars;
  my Int $bot   = 2;
  my Str $title = '';
  given $type {
    when 'std'      { $title = 'TIT09' ; }
    when 'cheating' { $title = 'TIT10' ; }
    when 'prepared' { $title = 'TIT11' ; $mult-and-sub = 'separate' }
    when 'boat'     { $title = 'TIT12' ; }
  }
  if $title eq '' {
    die "Division type '$type' unknown";
  }
  if $result ne 'quotient' | 'remainder' | 'both' {
    die "Result type '$result' unknown";
  }
  if $mult-and-sub ne 'combined' | 'separate' {
    die "Mult and sub type '$mult-and-sub' unknown";
  }
  $action .= new(level => 9
               , label => $title
               , val1  => $dividend.value
               , val2  => $divisor.value
               , val3  => $radix.Str
               );
  self.action.push($action);

  # Divisions with obvious results
  my $zero = Arithmetic::PaperAndPencil::Number.new(:radix($radix), :value<0>);
  my $one  = Arithmetic::PaperAndPencil::Number.new(:radix($radix), :value<1>);
  if $divisor.value eq '1' {
    $action .= new(level => 0, label => 'DIV05', val1 => $dividend.value, w1l => 1, w1c => 0, w1val => $dividend.value);
    self.action.push($action);
    given $result {
      when 'quotient'  { return $dividend; }
      when 'remainder' { return $zero; }
      when 'both'      { return ($dividend, $zero); }
    }
  }
  if $dividend ☈< $divisor {
    $action .= new(level => 0, label => 'DIV06', val1 => $dividend.value, val2 => $divisor.value
                 , w1l => 1, w1c => 0, w1val => '0');
    self.action.push($action);
    given $result {
      when 'quotient'  { return $zero; }
      when 'remainder' { return $dividend; }
      when 'both'      { return ($zero, $dividend); }
    }
  }

  # caching the partial products for prepared, cheating and boat divisions
  my %div-cache = 0 => $zero, 1 => $divisor;
  if $type eq 'prepared' {
    self!pre