/*
   Copyright 2010 Aaron J. Radke

   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 cc.drx

// import scala.concurrent.Future
// import scala.util.Try

//The following are such short additions they don't require their own file
class DrxFuture[A](val future:Future[A]) extends AnyVal{
  /**useful for skipping over a Future if it hasn't completed yet versus just waiting for it to finish*/
  2017-07-30("use successOption intead to match the scala headOption and tailOption like naming","v0.2.15")
  def successValue:Option[A] = future.value flatMap {_.toOption}
  /**useful for skipping over a Future if it hasn't completed yet versus just waiting for it to finish*/
  def successOption:Option[A] = future.value flatMap {_.toOption}
  // 2017-07-30("this is just too dangerous, it's too easy to use","v0.2.15")
  def block(maxWaitTime:Time):Try[A] = {
    Console.err.println(s"Warning: Blocking on Future for $maxWaitTime")
    blockWithoutWarning(maxWaitTime)
  }
  def blockWithoutWarning(maxWaitTime:Time):Try[A] = scala.concurrent.Await.ready(future, maxWaitTime).value.get

}

//TODO add extension methods of the Future object itself with with: implicit class FutureCompanionOps(val f: Future.type) extends AnyVal {
object DrxFuture{
  //cross compatibile version of unit (since 2.10 doesn't have this function)
  def unit = Future.successful{ () }
  //see DrxIterable.foreachBy for a time serial/linearized version of this sort of thing

  //--pattern to keep only the successful sites  https://stackoverflow.com/a/47687211/622016
  // def sequenceOfSuccesss[A](fs:Seq[Future[A]])(implicit ec:ExecutionContext):Future[Seq[A]] = {
  //   val seq = Future.sequence( fs.map{_.transform(Success(_))} )
  //   seq.map(_.collect{case Success(x)=>x})
  // }

}
class DrxOption[A](val opt:Option[A]) extends AnyVal{
  def ||(alternate: => Option[A]):Option[A] = opt orElse alternate
  def |(default: => A):A = opt getOrElse default //TODO add chained optioned with default in tests
  def require(errMsg: => Any):A = {
    scala.Predef.require(opt.nonEmpty, errMsg)
    opt.get
  }
}
class DrxTry[A](val t:Try[A]) extends AnyVal{
  def ||(alternate: => Try[A]):Try[A] = t orElse alternate
  def |(default: => A):A             = t getOrElse default
  def &(next: => Try[A]):Try[A]      = if(t.isSuccess) next else t
  /**return use an alternate default value with a custom warning*/
  def getOrElseWithWarning(default: => A, warning: => String):A = t getOrElse {
    Console.err.println(warning)
    default
  }
  /**return use an alternate default value but print a warning*/
  def |!(default: => A):A = t match {
      case scala.util.Success(v) => v
      case scala.util.Failure(e) =>
        System.err println e.getMessage
        default
  }
}


//TODO move these to it's own file
sealed trait IntegerType{
  val toInt:Int
}
sealed trait Tuple2Index1 extends IntegerType
sealed trait Tuple2Index2 extends IntegerType
// sealed trait Tuple3Index1 extends IntegerType
// sealed trait Tuple3Index2 extends IntegerType
// sealed trait Tuple3Index3 extends IntegerType
case object Zero extends IntegerType{val toInt = 0}
case object One extends Tuple2Index1{val toInt = 1}
case object Two extends Tuple2Index2{val toInt = 2}
case object Three extends IntegerType{val toInt = 3}


class DrxTuple2[A,B](val t:Tuple2[A,B]) extends AnyVal{
  def same:Boolean = t._1 == t._2
  // def map[C](f:Any => C):Tuple2[C,C] = Tuple2(f(t._1), f(t._2))  //TODO make a better super set of A,B instead of Any
  def get(i:Tuple2Index1):A = t._1
  def get(i:Tuple2Index2):B = t._2
  // def get(i:Two):B = t._2
}
class DrxTuple3[A,B,C](val t:Tuple3[A,B,C]) extends AnyVal{
  def same:Boolean = t._1 == t._2 && t._1 == t._3
  // def map[C](f:Any => C):Tuple2[C,C] = Tuple3(f(t._1), f(t._2), f(t._3))
}
/**used in conjuntion with DrxAny |> to support optional application
 * helpful to reduce the if(p) f(a) else a common pattern
 */
class DrxOptionApplyIf[A](val t:Option[A => A]) extends Function1[A,A]{
  def apply(x:A):A = if(t.isDefined) t.get.apply(x) else x
}
class DrxAny[A](val thisAny:A) extends AnyVal{
  2017-07-30("use `preApply` if you must (symbols are concise without concision)", "v0.2.15")
  /** a |> f => f(a) */
  def |>[B](f: A => B):B = f(thisAny)
  /** a preApply f => f(a) */
  def preApply[B](f: A => B):B = f(thisAny)
  // def postApply[B](f: A => B):B = f(a)

  //the following is useful for debug mode alternates but probobly has some other haskel notation
  // 2017-07-30("this is supported with DrxOptionApplyIf","v0.2.13") ???
  // def applyIf(fopt: Option[A => A]):A = if(fopt.isDefined) fopt.get(a) else a

  // 2017-07-30("type inference doesn't work so ideally here","v0.2.13") ???
  // def applyIf(p:A => Boolean)(f:A => A):A = if(p(a)) a else f(a)
  // def applyIf(p:Boolean)(f:A => A):A = if(p) a else f(a)

  // 2017-07-30("unless is inherently non-functional and impure and non short-circuited","v0.1.0")
  // def unless(cond:Boolean) = if(!cond) a  //this is here to prevent a future self trying this again

  /**side effect filter*/
  def sideEffect(f: A => Unit):A = {f(thisAny); thisAny}

  /**better chaining for types*/
  def applyIf(t:A => Boolean)(f: A => A):A = if(t(thisAny)) f(thisAny) else thisAny
  // def applyIf(t:A => Boolean, f: A => A):A = if(t(thisAny)) f(thisAny) else thisAny
  def applyIf(t:Boolean, f: A => A):A = if(t) f(thisAny) else thisAny

  /**optionally wrap an object*/
  def optionIf(f: A => Boolean):Option[A] = if(f(thisAny)) Some(thisAny) else None
  def someIf(f: A => Boolean):Option[A] = if(f(thisAny)) Some(thisAny) else None
  def noneIf(f: A => Boolean):Option[A] = if(f(thisAny)) None    else Some(thisAny)

  /**alias for option if*/
  2017-07-30("to many symbols are hard to undertand","v0.2.15")
  def ?>(f: A => Boolean):Option[A]       = if(f(thisAny)) Some(thisAny) else None
  // def transformSome(f: A => Option[A]):A  = f(a) match { case Some(x) => x; case None => a}

  // a left to right version of a containment tests
  // def elementOf(xs:Iterable[A]):Boolean = xs contains a
  // 2017-07-30("left to right reads nice but muddies every object", "2020-08-29")
  def elementOf(xs:Set[A]):Boolean = xs contains thisAny

}

class DrxSocket(val socket:java.net.Socket) extends AnyVal{
  // val socket = new java.net.Socket(OS.hostname, port)
  def in:Input   = Input(  socket.getInputStream()  )
  def out:Output = Output( socket.getOutputStream() )
}

/*
class DrxBufferedImage(val img:java.awt.image.BufferedImage) extends AnyVal{
  def size = Vec( img.getWidth.toDouble,  img.getHeight.toDouble )
}
*/