### ----------------------------------------------------
### -- Sequence::Generator
### -- Licenses: Artistic-2.0
### -- Authors: Elizabeth Mattijsen
### -- File: lib/Sequence/Generator.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 2
 )
    }

    # check if two strings can be sequenced
    sub sequenceable(str $left, str $right) {
        my constant %group = (|("a".."z"), |("A".."Z"), |("0".."9")) >>=>>> 1;

        my int $i = nqp::chars($left);
        if nqp::chars($right) == $i {
            nqp::while(
              --$i >= 0
                && %group{nqp::substr($left,$i,1)}
                     === %group{nqp::substr($right,$i,1)},
              nqp::null
            );
            $i >= 0
              ?? Failure.new("not same group")
              !! True
        }
        else {
            Failure.new("not same length")
        }
    }

    # Iterator for two string endpoints
    multi method iterator(
      Str:D $first, Str:D $last;; int :$no-first, int :$no-last
    --> Iterator:D) {

        nqp::istype((my $result := sequenceable($first, $last)),Failure)
          ?? $result
          !! $first eq $last
            ?? $no-first || $no-last                   # same string
              ?? ().iterator                            # some end excluded
              !! $first.iterator                        # just the one please
            !! nqp::chars($first) == 1                   # different string
              ?? StepCodepoint.new(                       # just one codepoint
                   $first, $last, $no-first, $no-last)
              !! StepMultipleCodepoints.new(              # multiple codepoints
                   $first, $last, $no-first, $no-last)
    }

    # Iterator for Callable with a numeric endpoint
    multi method iterator(
      Callable:D $code, Real:D $endpoint;; int :$no-first, int :$no-last
    --> Iterator:D) {
        my $iterator := self!"{ self!lambda-name($code) }"(
            (), $code, nqp::decont($endpoint), $no-last
        );
        $iterator.skip-one if $no-first;
        $iterator
    }

    # Iterator for anything with a numeric endpoint
    multi method iterator(
      Any:D $first, Real:D $endpoint;; int :$no-first, int :$no-last
    --> Iterator:D) {
        $endpoint == Inf
          ?? UnendingSucc.new($no-first ?? $first.succ !! $first)
          !! $endpoint == -Inf
            ?? $no-last
              ?? all-but-last(
                   UnendingPred.new($no-first ?? $first.pred !! $first)
                 )
              !! UnendingPred.new($no-first ?? $first.pred !! $first)
            !! Failure.new("Cannot handle $first.^name() with a numeric endpoint")
    }

    # helper methods for lambdas
    method !lambda-none($initials, $lambda, $endpoint, int $no-last) {
        nqp::istype($endpoint,Numeric:D) && $endpoint == Inf
          ?? LambdaNone.new($initials, $lambda, $no-last)
          !! LambdaNoneAccepts.new($initials, $lambda, $endpoint, $no-last)
    }
    method !lambda1($initials, $lambda, $endpoint, int $no-last) {
        nqp::istype($endpoint,Numeric:D) && $endpoint == Inf
          ?? Lambda1.new($initials, $lambda, $no-last)
          !! Lambda1Accepts.new($initials, $lambda, $endpoint, $no-last)
    }
    method !lambda2($initials, $lambda, $endpoint, int $no-last) {
        nqp::istype($endpoint,Numeric:D) && $endpoint == Inf
          ?? Lambda2.new($initials, $lambda, $no-last)
          !! Lambda2Accepts.new($initials, $lambda, $endpoint, $no-last)
    }
    method !lambda-n($initials, $lambda, $endpoint, int $no-last) {
        my int $args = $lambda.arity || $lambda.count;
        nqp::istype($endpoint,Numeric:D) && $endpoint == Inf
          ?? LambdaN.new($initials, $lambda, $no-last, $args)
          !! LambdaNAccepts.new($initials, $lambda, $endpoint, $no-last, $args)
    }
    method !lambda-all($initials, $lambda, $endpoint, int $no-last) {
        nqp::istype($endpoint,Numeric:D) && $endpoint == Inf
          ?? LambdaAll.new($initials, $lambda, $no-last)
          !! LambdaAllAccepts.new($initials, $lambda, $endpoint, $no-last)
    }

    # find the name of the lambda helper method to use
    method !lambda-name($lambda) {
        (my $args := $lambda.arity || $lambda.count)
          ?? $args == 1
            ?? 'lambda1'
            !! $args == 2
              ?? 'lambda2'
              !! $args == Inf
                ?? 'lambda-all'
                !! 'lambda-n'
          !! 'lambda-none'
    }

    # Iterator for given initial values with endpoint
    method !iterator-endpoint(
      $source, Mu $endpoint, int $no-first?, int $no-last?
    --> Iterator:D) {
        my $initials := nqp::create(IterationBuffer);
        my $iterator := nqp::null;

        # find the right iterator
        nqp::until(
          nqp::eqaddr((my $pulled := $source.pull-one),IterationEnd),
          nqp::if(
            nqp::isnull($iterator),
            nqp::if(
              nqp::istype($pulled,Code),
              ($iterator := self!"{ self!lambda-name($pulled) }"(
                $initials, $pulled, nqp::decont($endpoint), $no-last
              )),
              nqp::push($initials,$pulled)
            ),
            fail "Cannot have value after Callable: found $pulled"
          )
        );

        # no iterator yet, use initial values
        $iterator := self!elucidate($initials, $endpoint, $no-last)
          if nqp::isnull($iterator);

        $no-first && nqp::not_i(nqp::istype($iterator,Failure))
          ?? $iterator.skip-one
            ?? $iterator
            !! ().iterator
          !! $iterator
    }

    # Iterator for iterators with initial and endpoint values
    method !iterator-iterator(
      Iterator:D $left, Iterator:D $right, int $no-first, int $no-last
    --> Iterator:D) {
        nqp::eqaddr((my $endpoint := $right.pull-one),IterationEnd)
          ?? endpoint-mismatch($left, "empty list")
          !! $endpoint == Inf
            ?? Failure.new("can never produce later")
            !! TwoIterators.new(
                 self!iterator-endpoint($left, $endpoint, $no-first, $no-last),
                 $right
               )
    }

    # Iterator for given initial values with endpoint
    multi method iterator(
      @source, Mu $endpoint;; int :$no-first, int :$no-last
    --> Iterator:D) is default {
        nqp::istype($endpoint,Iterable)
          ?? self!iterator-iterator(
               @source.iterator, $endpoint.iterator, $no-first, $no-last
             )
          !! self!iterator-endpoint(
               @source.iterator, $endpoint, $no-first, $no-last
             )
    }

    method !elucidate2(
      IterationBuffer:D $seed is raw, $endpoint is raw, int $no-last
    --> Iterator:D) {
        my $one := nqp::atpos($seed,0);
        my $two := nqp::atpos($seed,1);

        nqp::eqaddr($one.WHAT,$two.WHAT)
          ?? nqp::istype($one,Numeric)

            # numeric sequence
            ?? (my $step := $two - $one)
              ?? $endpoint == Inf
                ?? UnendingStep.new($one, $step)
                !! nqp::istype($endpoint,Numeric)
                  ?? step-to($one, $step, $endpoint, $no-last)
                  !! endpoint-mismatch($one, $endpoint)
              !! not-deducible($one,$two)  # no direction


            # short-cut not deducible
            !! $one === $two
              ?? not-deducible($one,$two)

              # potential .succ sequence
              !! $one.succ === $two

                # .succ sequence
                ?? $endpoint == Inf
                  ?? UnendingSucc.new($one)
                  !! nqp::istype($endpoint,$one.WHAT)
                    ?? Lambda1Accepts.new($seed, *.succ, $endpoint, $no-last)
                    !! endpoint-mismatch($one, $endpoint)

                # potential .pred sequence
                !! $two.succ === $one

                  # .pred sequence
                  ?? $endpoint === -Inf
                    ?? UnendingPred.new($one)
                    !! nqp::istype($endpoint, $one.WHAT)
                      ?? Lambda1Accepts.new($seed, {
                           nqp::istype((my $value := .pred),Failure)
                             ?? (last)
                             !! $value
                         }, $endpoint, $no-last)
                      !! endpoint-mismatch($one, $endpoint)

          # alas, no go
                  !! not-deducible($one, $two)
          !! not-deducible($one, $two)
    }

    method !elucidateN(
      IterationBuffer:D $seed, $endpoint, int $no-last
    --> Iterator:D) {
        my int $elems = nqp::elems($seed);
        my $one   := nqp::atpos($seed,$elems - 3);
        my $two   := nqp::atpos($seed,$elems - 2);
        my $three := nqp::atpos($seed,$elems - 1);

        nqp::istype($one.WHAT,Real)
          && nqp::istype($two.WHAT,Real)
          && nqp::istype($three.WHAT,Real)

          # all numerics
          ?? (my $step := $two - $one) == $three - $two

            # arithmetic sequence
            ?? $endpoint == Inf
              ?? TwoIterators.new(
                   $seed.iterator,
                   UnendingStep.new($three + $step, $step)
                 )
              !! nqp::istype($endpoint,Real)
                ?? TwoIterators.new(
                     $seed.iterator,
                     step-to($three + $step, $step, $endpoint, $no-last)
                   )
                !! nqp::istype($endpoint,Code)
                  ?? Lambda1Accepts.new($seed, * + $step, $endpoint, $no-last)
                  !! endpoint-mismatch($seed, $endpoint)

            # numbers, but not an arithmetic sequence
            !! $one && $two && $three
                 && (my $mult := ($two / $one).narrow) > 0
                 && $three / $two == $mult

              # geometric sequence
              ?? $endpoint == Inf
                ?? TwoIterators.new(
                     $seed.iterator,
                     UnendingMult.new($three * $mult, $mult)
                   )
                !! nqp::istype($endpoint,Real)
                  ?? TwoIterators.new(
                       $seed.iterator,
                       mult-to($three * $mult, $mult, $endpoint, $no-last)
                     )
                  !! nqp::istype($endpoint,Code)
                    ?? Lambda1Accepts.new($seed, * * $mult, $endpoint, $no-last)
                    !! endpoint-mismatch($seed, $endpoint)
              !! not-deducible($one, $two, $three, "here")

          # all same type, potential .succ
          !! nqp::eqaddr($one.WHAT,$two.WHAT)
               && nqp::eqaddr($two.WHAT,$three.WHAT)
            ?? $one.succ === $two && $two.succ === $three

              # simple .succ
              ?? $endpoint == Inf
                ?? TwoIterators.new(
                     $seed.iterator,
                     UnendingSucc.new($three.succ)
                   )
                !! nqp::istype($three.WHAT,$endpoint.WHAT)
                     || nqp::istype($endpoint,Code)
                  ?? Lambda1Accepts.new($seed, *.succ, $endpoint, $no-last)
                  !! endpoint-mismatch($three, $endpoint)

              # not a simple .succ
              !! $two.succ === $one && $three.succ === $two

                # simple .pred
                ?? $endpoint === -Inf
                  ?? Lambda1.new($seed, *.pred, $no-last)
                  !! nqp::istype($three.WHAT,$endpoint.WHAT)
                       || nqp::istype($endpoint,Code)
                    ?? Lambda1Accepts.new($seed, *.succ, $endpoint, $no-last)
                    !! endpoint-mismatch($three, $endpoint)

            # alas, no go
                !! not-deducible($one, $two, $three)
            !! not-deducible($one, $two, $three)
    }

    # take seed / endpoint / and turn it into an iterator
    method !elucidate(IterationBuffer:D $seed, $endpoint, int $no-last) {
        my int $elems = nqp::elems($seed);
        $elems == 2
          ?? self!elucidate2($seed, $endpoint, $no-last)
          !! $elems > 2
            ?? self!elucidateN($seed, $endpoint, $no-last)
            !! $elems
              ?? self.iterator(nqp::shift($seed), $endpoint, 0, $no-last)
              !! not-deducible($seed.List)
    }

#-- helper subs ----------------------------------------------------------------

    # helper sub to get correct "all but last" iterator
    proto sub all-but-last(|) {*}
    multi sub all-but-last(
      PredictiveIterator:D $iterator
    --> PredictiveIterator:D) {
        AllButLastPredictive.new($iterator)
    }
    multi sub all-but-last(Iterator:D $iterator --> Iterator:D) {
        AllButLast.new($iterator)
    }

    # helper sub to make a nqp::list_s for 2 codepoints
    sub cps2list_s(int $from, int $to) {
        my int $step = $from < $to ?? 1 !! -1;
        my int $this = $from - $step;

        my $chars := nqp::list_s;
        nqp::until(
          nqp::iseq_i(($this = nqp::add_i($this,$step)),$to),
          nqp::push_s($chars,nqp::chr($this))
        );
        nqp::push_s($chars,nqp::chr($this));

        $chars
    }

    # ensure buffer containes right number of values to be passed
    sub set-buffer-size(
      IterationBuffer:D $buffer, int $elems
    --> IterationBuffer:D) {
        nqp::while(
          nqp::isgt_i(nqp::elems($buffer),$elems),
          nqp::shift($buffer)
        );
        nqp::while(
          nqp::islt_i(nqp::elems($buffer),$elems),
          nqp::unshift($buffer,Any)
        );
        $buffer
    }

    # helper sub to return correct multiplication ender
    sub mult-endpoint($mult, $endpoint) {
        $mult > 1 ?? * >= $endpoint !! * <= $endpoint
    }

    # helper sub to return correct stepper iterator
    sub step-to($value, $step, $end, int $no-last --> Iterator:D) {
        $step > 0
          ?? StepUpto.new(  $value, $step, $no-last ?? $end - $step !! $end)
          !! StepDownto.new($value, $step, $no-last ?? $end - $step !! $end)
    }

    # helper sub to return correct multiplier iterator
    sub mult-to($value, $mult, $end, int $no-last --> Iterator:D) {
        my $iterator := $mult > 1
          ?? MultUpto.new(  $value, $mult, $end)
          !! MultDownto.new($value, $mult, $end);
        $no-last
          ?? AllButLast.new($iterator)
          !! $iterator
    }

    # make quitting easy
    sub not-deducible(*@values) is hidden-from-backtrace {
        X::Sequence::Deduction.new(from => @values>>.raku.join(",")).Failure
    }
    sub endpoint-mismatch(\from,\endpoint) is hidden-from-backtrace {
        X::Sequence::Endpoint.new(from => from, endpoint => endpoint).Failure
    }
}

#-- operator front-end ---------------------------------------------------------

my proto sub infix:<...>(|) is export is equiv(&infix:<...>) {*}
my multi sub infix:<...>(Mu $a, Whatever) { $a ... Inf }
my multi sub infix:<...>(Mu $a, Mu $b) {
    nqp::istype(
      (my $iterator := Sequence::Generator.iterator($a, $b)),
      Failure
    ) ?? $iterator
      !! Seq.new: $iterator
}

my proto sub infix:<...^>(|) is export is equiv(&infix:<...>) {*}
my multi sub infix:<...^>(Mu $a, Whatever) { $a ...^ Inf }
my multi sub infix:<...^>(Mu $a, Mu $b) {
    nqp::istype(
      (my $iterator := Sequence::Generator.iterator($a, $b, :no-last)),
      Failure
    ) ?? $iterator
      !! Seq.new: $iterator
}

my proto sub infix:<^...>(|) is export is equiv(&infix:<...>) {*}
my multi sub infix:<^...>(Mu $a, Whatever) { $a ^... Inf }
my multi sub infix:<^...>(Mu $a, Mu $b) {
    nqp::istype(
      (my $iterator := Sequence::Generator.iterator($a, $b, :no-first)),
      Failure
    ) ?? $iterator
      !! Seq.new: $iterator
}

my proto sub infix:<^...^>(|) is export is equiv(&infix:<...>) {*}
my multi sub infix:<^...^>(Mu $a, Whatever) { $a ^...^ Inf }
my multi sub infix:<^...^>(Mu $a, Mu $b) {
    nqp::istype(
      (my $iterator := Sequence::Generator.iterator($a, $b,:no-first,:no-last)),
      Failure
    ) ?? $iterator
      !! Seq.new: $iterator
}

my constant &infix:<…>   is export := &infix:<<...>>;
my constant &infix:<…^>  is export := &infix:<<...^>>;
my constant &infix:<^…>  is export := &infix:<<^...>>;
my constant &infix:<^…^> is export := &infix:<<^...^>>;

# # vim: expandtab shiftwidth=4


