package millfork.cli

import fastparse.core.Parsed.Failure

import scala.collection.mutable

/**
  * @author Karol Stasiak
  */
class CliParser[T] {

  private val options = mutable.ArrayBuffer[CliOption[T, _]]()
  private val mapFlags = mutable.Map[String, CliOption[T, _]]()
  private val mapOptions = mutable.Map[String, CliOption[T, _]]()
  private val mapExpansions = mutable.Map[String, List[String]]()
  private val _default = new UnknownParamOption[T]().action((p, _) => throw new IllegalArgumentException(s"Unknown option $p"))
  private var _status: Option[CliStatus.Value] = None
  options += _default

  private def add[O <: CliOption[T, _]](o: O): O = {
    options += o
    o.length match {
      case 1 =>
        o.names.foreach { n => mapFlags(n) = o }
      case 2 =>
        o.names.foreach { n => mapOptions(n) = o }
      case _ => ()
    }
    o
  }

  def parse(context: T, args: List[String]): (CliStatus.Value, T) = {
    val t = parseInner(context, args)
    _status.getOrElse(if (options.forall(_.validate())) CliStatus.Ok else CliStatus.Failed) -> t
  }

  def assumeStatus(s: CliStatus.Value): Unit = {
    _status = Some(s)
  }

  private def parseInner(context: T, args: List[String]): T = {
    args match {
      case k :: v :: xs if mapOptions.contains(k) =>
        mapOptions(k) match {
          case p: ParamOption[T] if !p._dummy => parseInner(p.encounter(v, context), xs)
          case _ => ???
        }
      case k :: xs if mapFlags.contains(k) =>
        mapFlags(k) match {
          case p: ExpansionOption[T] if !p._dummy =>
            parseInner(context, p.replacements ++ xs)
          case p: FlagOption[T] if !p._dummy =>
            parseInner(p.encounter(context), xs)
          case p: BooleanOption[T] if !p._dummy =>
            parseInner(p.encounter(k, context), xs)
          case p: NoMoreOptions[T] if !p._dummy =>
            p.encounter()
            xs.foldLeft(context)((t, x) => _default.encounter(x, t))
          case _ => ???
        }
      case x :: xs =>
        parseInner(_default.encounter(x, context), xs)
      case Nil => context
    }
  }


  def fluff(text: String*): Unit = add(new Fluff[T](text))

  def flag(names: String*): FlagOption[T] = add(new FlagOption[T](names))

  def boolean(trueName: String, falseName: String): BooleanOption[T] = add(new BooleanOption[T](trueName, falseName))

  def endOfFlags(names: String*): NoMoreOptions[T] = add(new NoMoreOptions[T](names))

  def default: UnknownParamOption[T] = _default

  def printHelp(firstTab: Int): List[String] = {
    options.filterNot(_._hidden).toList.flatMap(_.toStrings(firstTab))
  }

  def parameter(names: String*): ParamOption[T] = add(new ParamOption[T](names))

  def expansion(name: String)(replacements: String*): ExpansionOption[T] = add(new ExpansionOption[T](name)(replacements.toList))

}
