/*
 * Copyright 2018 MOIA GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.moia.streamee.demo

import akka.actor.typed.{ ActorRef, Behavior }
import akka.actor.typed.scaladsl.Behaviors
import akka.stream.Materializer
import io.moia.streamee.{ IntoableProcessor, Process, ProcessSinkRef, Step }
import org.slf4j.LoggerFactory
import scala.annotation.tailrec
import scala.util.Random

object WordShuffler {

  final case class ShuffleWord(word: String)
  final case class WordShuffled(word: String)

  def apply(): Process[ShuffleWord, WordShuffled] =
    Process[ShuffleWord, WordShuffled]
      .via(shuffleWordToString)
      .via(shuffle)
      .via(stringToWordShuffled)

  def shuffleWordToString[Ctx]: Step[ShuffleWord, String, Ctx] =
    Step[ShuffleWord, Ctx].map(_.word)

  def shuffle[Ctx]: Step[String, String, Ctx] =
    Step[String, Ctx].map(shuffleWord)

  def stringToWordShuffled[Ctx]: Step[String, WordShuffled, Ctx] =
    Step[String, Ctx].map(WordShuffled)

  private def shuffleWord(word: String) = {
    @tailrec def loop(word: String, acc: String = ""): String =
      if (word.isEmpty)
        acc
      else {
        val (left, right) = word.splitAt(Random.nextInt(word.length))
        val c             = right.head
        val nextWord      = left + right.tail
        loop(nextWord, c +: acc)
      }

    if (word.length <= 3)
      word
    else
      word.head +: loop(word.tail.init) :+ word.last
  }
}

object WordShufflerRunner {
  import WordShuffler._

  sealed trait Command
  final case class GetProcessSinkRef(replyTo: ActorRef[ProcessSinkRef[ShuffleWord, WordShuffled]])
      extends Command
  final case object Shutdown     extends Command
  private final case object Stop extends Command

  private val logger = LoggerFactory.getLogger(getClass)

  def apply()(implicit mat: Materializer): Behavior[Command] =
    Behaviors.setup { context =>
      import context.executionContext

      val self                  = context.self
      val wordShufflerProcessor = IntoableProcessor(WordShuffler(), "word-shuffler")

      wordShufflerProcessor.whenDone.onComplete { reason =>
        if (logger.isWarnEnabled) logger.warn(s"Process completed: $reason")
        self ! Stop
      }

      Behaviors.receiveMessagePartial {
        case GetProcessSinkRef(replyTo) =>
          replyTo ! wordShufflerProcessor.sinkRef()
          Behaviors.same

        case Shutdown =>
          wordShufflerProcessor.shutdown()
          Behaviors.receiveMessagePartial { case Stop => Behaviors.stopped }
      }
    }
}