/*
 * Copyright 2019 ACINQ SAS
 *
 * 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 fr.acinq.eclair.channel

import akka.actor.{Actor, ActorLogging, ActorRef, Stash}
import fr.acinq.eclair.Features
import fr.acinq.eclair.channel.Commitments.msg2String
import fr.acinq.eclair.wire.{Init, LightningMessage}
import scodec.bits.ByteVector

import scala.concurrent.duration._
import scala.util.Random

/**
 * A Fuzzy [[fr.acinq.eclair.Pipe]] which randomly disconnects/reconnects peers.
 */
class FuzzyPipe(fuzzy: Boolean) extends Actor with Stash with ActorLogging {

  import scala.concurrent.ExecutionContext.Implicits.global

  def receive = {
    case (a: ActorRef, b: ActorRef) =>
      unstashAll()
      context become connected(a, b, 10)

    case _ => stash()
  }

  def stayOrDisconnect(a: ActorRef, b: ActorRef, countdown: Int): Unit = {
    if (!fuzzy) context become connected(a, b, countdown - 1) // fuzzy mode disabled, we never disconnect
    else if (countdown > 1) context become connected(a, b, countdown - 1)
    else {
      log.debug("DISCONNECTED")
      a ! INPUT_DISCONNECTED
      b ! INPUT_DISCONNECTED
      context.system.scheduler.scheduleOnce(100 millis, self, 'reconnect)
      context become disconnected(a, b)
    }
  }

  def connected(a: ActorRef, b: ActorRef, countdown: Int): Receive = {
    case msg: LightningMessage if sender() == a =>
      log.debug(f"A ---${msg2String(msg)}%-6s--> B")
      b forward msg
      stayOrDisconnect(a, b, countdown)
    case msg: LightningMessage if sender() == b =>
      log.debug(f"A <--${msg2String(msg)}%-6s--- B")
      a forward msg
      stayOrDisconnect(a, b, countdown)
  }

  def disconnected(a: ActorRef, b: ActorRef): Receive = {
    case msg: LightningMessage if sender() == a =>
      // dropped
      log.info(f"A ---${msg2String(msg)}%-6s-X")
    case msg: LightningMessage if sender() == b =>
      // dropped
      log.debug(f"  X-${msg2String(msg)}%-6s--- B")
    case 'reconnect =>
      log.debug("RECONNECTED")
      val dummyInit = Init(Features.empty)
      a ! INPUT_RECONNECTED(self, dummyInit, dummyInit)
      b ! INPUT_RECONNECTED(self, dummyInit, dummyInit)
      context become connected(a, b, Random.nextInt(40))
  }
}