package es.amuz.tacking.service.route

import java.net.URLDecoder
import java.text.Normalizer
import java.util.UUID

import es.amuz.tacking.frame.auth.{SecurityContext, SecurityInfo}
import es.amuz.tacking.frame.jade4j.JadeContext
import es.amuz.tacking.frame.pegdown.PegDownProcessorExt
import es.amuz.tacking.frame.scalikejdbc.JDBCContext
import es.amuz.tacking.model.structured_page.{PageStatus, StructuredPageAction, StructuredPages}
import es.amuz.tacking.protocols._
import es.amuz.tacking.util.FileAndPath
import org.joda.time.DateTime
import org.pegdown.Extensions
import scalikejdbc._
import shapeless.{:: => ->:}
import spray.http.StatusCodes._
import spray.http.Uri
import spray.httpx.encoding.Gzip
import spray.routing.{Directive1, Route}

import scala.annotation.tailrec
import scala.reflect.io.Path


/**
 * Created by company on 12/29/14.
 */


class WikiRouteService extends RouteService with JadeContext with SecurityContext with JDBCContext {

  def queryResult(names: List[String]) = {
    val matchedPages = queryPageId(names)
    val matched = names.take(matchedPages.size).zip(matchedPages).map(MatchedPage.tupled)
    val unmatched = names.drop(matchedPages.size)
    QueryPagesRes(matched, unmatched)
  }

  def queryPageId(names: List[String]): List[UUID] = queryPagesId(names, None).flatten.reverse

  @tailrec
  private def queryPagesId(names: List[String], parentId: Option[UUID], accum: List[Option[UUID]] = Nil): List[Option[UUID]] = {
    names match {
      case Nil =>
        accum
      case tname :: rnames =>
        getRequestPage(tname, parentId) match {
          case None =>
            accum
          case pageId =>
            queryPagesId(rnames, pageId, pageId :: accum)
        }
    }
  }

  def getRequestPage(urlSegment: String, parentId: Option[UUID]): Option[UUID] = {
    val column = StructuredPages.column
    StructuredPages.where(
      sqls
        .eq(column.parentId, parentId).and
        .eq(column.title, urlSegment)
    ).apply().headOption.map(_.id)
  }

  def getPlaceHolder = {
    val column = StructuredPages.column
    StructuredPages.where(
      sqls
        .isNull(column.parentId).and
        .eq(column.title, "PlaceHolder")
    ).apply().head.content
  }

  // todo : DeleteView에서 자식페이지를 보여준다
  //  annotation.tailrec
  //  def traverseTR[A](graph : Map[A,Set[A]], toVisit : Seq[A], visited : Set[A], accumulator : Seq[A]) : Seq[A] = {
  //    if(toVisit.isEmpty) {
  //      accumulator
  //    } else {
  //      val next = toVisit.head
  //      val succ = (graph(next) -- visited -- toVisit).toSeq
  //      // DFS :
  //      //traverseTR(graph, succ ++ toVisit.tail, visited + next, accumulator :+ next)
  //      // BFS :
  //      traverseTR(graph, toVisit.tail ++ succ, visited + next, accumulator :+ next)
  //    }
  //  }
  //
  //  def traverseFrom[A](graph : Map[A,Set[A]], initial : A) =
  //    traverseTR(graph, Seq(initial), Set.empty, Seq.empty)

  def createAction(implicit wContext: WikiPageInfo): Route =
    formFields('code.as[String]) { (code) =>
      complete {
        log.debug("create Action!")
        val name = wContext.newNameSpace.get
        val parentId = wContext.exists.reverse match {
          case Nil =>
            None
          case MatchedPage(parentName, parentId) :: acester =>
            Some(parentId)
        }
        val column = StructuredPages.column
        StructuredPages.createWithNamedValues(
          column.title -> name,
          column.content -> code,
          column.parentId -> parentId,
          column.status -> PageStatus.Show.id
        )
        FileAndPath.innerRedirect(wContext.uri.withQuery(), Found)
      }
    }

  def deleteAction(implicit wContext: WikiPageInfo): Route =
    withJadeContext { context =>
      complete {
        log.debug("delete Action!")
        val keyword = wContext.trailingPath.last
        context += ("title" -> s"${keyword} - Tigris")
        context += ("keyword" -> keyword)
        context += ("role" -> wContext.authInfo.role)
        render("wiki/view", context)
      }
    }

  def updateAction(implicit wContext: WikiPageInfo): Route =
    formFields('code.as[String]) { (code) =>
      complete {
        val MatchedPage(pageName, pageId) = wContext.exists.last
        val column = StructuredPages.column
        StructuredPages.updateById(pageId.toString)
          .withNamedValues(
            column.content -> code,
            column.modified -> Some(DateTime.now)
          )
        FileAndPath.innerRedirect(wContext.uri.withQuery(), Found)
      }
    }

  def createView(implicit wContext: WikiPageInfo): Route =
    withJadeContext { context =>
      complete {
        log.debug("create View!")
        val sample = getPlaceHolder
        val keyword = wContext.trailingPath.last
        context += ("title" -> s"Create / ${keyword} - Tigris")
        context += ("keyword" -> keyword)
        context += ("content" -> sample)
        context += ("role" -> wContext.authInfo.role)
        render("wiki/editor", context)
      }
    }

  def deleteView(implicit wContext: WikiPageInfo): Route =
    withJadeContext { context =>
      complete {
        log.debug("delete View!")
        val MatchedPage(pageName, pageId) = wContext.exists.last
        StructuredPages.findById(pageId.toString).get
        //        context += ("title" -> s"${keyword} - Tigris")
        //        context += ("keyword" -> keyword)
        render("wiki/view", context)
      }
    }

  def updateView(implicit wContext: WikiPageInfo): Route =
    withJadeContext { context =>
      complete {
        log.debug("update View!")
        val MatchedPage(pageName, pageId) = wContext.exists.last
        val pageObj = StructuredPages.findById(pageId.toString).get
        val keyword = wContext.trailingPath.last
        context += ("title" -> s"Update / ${keyword} - Tigris")
        context += ("keyword" -> keyword)
        context += ("content" -> pageObj.content)
        context += ("role" -> wContext.authInfo.role)
        render("wiki/editor", context)
      }
    }

  def view(implicit wContext: WikiPageInfo): Route =
    withJadeContext { context =>
      complete {
        log.debug("View!")
        val MatchedPage(pageName, pageId) = wContext.exists.last
        val pageObj = StructuredPages.findById(pageId.toString).get

        val processor = new PegDownProcessorExt(Extensions.ALL)
        context += ("title" -> s"${pageName} - Tigris")
        context += ("keyword" -> pageName)
        context += ("content" -> processor.markdownToHtml(pageObj.content))
        context += ("role" -> wContext.authInfo.role)
        render("wiki/view", context)
      }
    }

  def urlsplitter(urls: List[String]): Directive1[Seq[String]] =
    provide(
      urls.map(URLDecoder.decode(_, "UTF-8"))
        .map(Normalizer.normalize(_, Normalizer.Form.NFKC))
    )

  def dropSegment(orig: Uri, drop: Int): Uri = {
    val parentPath = Path(orig.path.toString()).segments.dropRight(drop).mkString("/", "/", "")
    orig.withPath(Uri.Path(parentPath))
  }

  def routeFilter(
                   trailingPath: Seq[String],
                   uri: Uri,
                   action: StructuredPageAction.value,
                   authInfo: SecurityInfo
                   ): Directive1[(List[MatchedPage], Option[String])] =
    queryResult(trailingPath.toList) match {
      case QueryPagesRes(pageIdList, Nil) =>
        (authInfo.userSession, action) match {
          case (_, StructuredPageAction.Retrieve) =>
            provide(pageIdList -> None)
          case (Some(us), StructuredPageAction.Create) =>
            redirect(uri.withQuery("action" -> StructuredPageAction.Update), Found)
          case (Some(us), _) =>
            provide(pageIdList -> None)
          case (None, _) =>
            requireLogin()
        }
      case QueryPagesRes(parentIdList, "favicon.ico" :: Nil) =>
        redirect("/public/common/img/favicon.ico", SeeOther)
      case QueryPagesRes(parentIdList, newName :: Nil) =>
        (authInfo.userSession, action) match {
          case (None, StructuredPageAction.Create) =>
            requireLogin()
          case (Some(us), StructuredPageAction.Create) =>
            provide(parentIdList -> Some(newName))
          case _ =>
            redirect(
              uri.withQuery("action" -> StructuredPageAction.Create), Found
            )
        }
      case QueryPagesRes(parentIdList, newNames) =>
        redirect(
          dropSegment(uri, newNames.size - 1)
            .withQuery("action" -> StructuredPageAction.Create),
          Found)
    }

  def userSessionRedirector(authInfo: SecurityContext): Directive1[SecurityContext] = {
    provide(authInfo)
  }

  def wikiRouter(implicit context: WikiPageInfo): Route =
    context.action match {
      case StructuredPageAction.Create =>
        get(createView(context)) ~
          post(createAction(context))
      case StructuredPageAction.Delete =>
        get(deleteView(context)) ~
          post(deleteAction(context))
      case StructuredPageAction.Update =>
        get(updateView(context)) ~
          post(updateAction(context))
      case StructuredPageAction.Retrieve =>
        get(view(context))
      case _ =>
        redirect(context.uri.copy(query = Uri.Query.Empty), SeeOther)
    }


  def wikiContextProvider(trailingPath: Seq[String],
                          uri: Uri,
                          action: StructuredPageAction.value,
                          authInfo: SecurityInfo,
                          exists: List[MatchedPage],
                          newNameSpace: Option[String]
                           ): Directive1[WikiPageInfo] =
    provide(
      WikiPageInfo(trailingPath, uri, action, authInfo, exists, newNameSpace)
    )


  val wikiContextExtrator =
    path(Segments ~ Slash.?).flatMap(urlsplitter) &
      requestUri &
      parameters('action.as[StructuredPageAction.value] ? StructuredPageAction.Retrieve) &
      withUserSession

  def wikiContextProvider(
                           trailingPath: Seq[String],
                           uri: Uri,
                           action: StructuredPageAction.value,
                           authInfo: SecurityInfo): Directive1[WikiPageInfo] = {
    routeFilter(trailingPath, uri, action, authInfo).flatMap {
      case (exists, newNameSpace) =>
        wikiContextProvider(trailingPath, uri, action, authInfo, exists, newNameSpace)
    }
  }

  override def receive = runRoute {
    pathEndOrSingleSlash {
      get {
        encodeResponse(Gzip) {
          withJadeContext { context =>
            complete {
              context += ("title" -> "Stay tuned")
              render("wiki/main", context)
            }
          }
        }
      }
    } ~
      wikiContextExtrator {
        wikiContextProvider(_, _, _, _) {
          context =>
            wikiRouter(context)
        }
      }
  }
}
