fcf.module({
  name: "fcf:NServer/NControllers/Route.js",
  dependencies: ["fcf:NServer/NControllers/Controller.js"],
  module: function(Controller){
    var Namespace = fcf.prepareObject(fcf, "packages/fcf/NServer/NControllers");

    class Route  extends Controller{
      getRequestInfo(){
        return {
          userRequest: true,
          redirect:    false,
        };
      }

      async getRoute(a_request, a_url) {
        let routeInfo = new fcf.RouteInfo({url: a_url});
        let node = await fcf.application.getRouter().getNode(a_url)
        fcf.append(routeInfo.args, node.args);
        routeInfo.subUri = node.subUri;

        if (!node)
          throw new fcf.Exception("ERROR_404", {"address": routeInfo.url});

        let originRoute = a_request.getRoute();

        do {
          if (!node.node || !node.node.endpoint || !node.node.endpoint.controller)
            break;
          let [Class] = await fcf.require([node.node.endpoint.controller]);
          if (!Class)
            break;
          let controller = new Class({endpoint: node.node.endpoint, route: this._options.route});
          let requestInfo = controller.getRequestInfo();
          if (!requestInfo.redirect)
            break;

          try {
            let redirectUrl = undefined;

            a_request.setRoute(routeInfo);
            let errorCode = undefined;
            await fcf.actions()
            .then(async (a_res, a_act)=>{
              a_request.setPreNext(()=>{
                redirectUrl = a_request.getRedirect();
                if (a_request.getStatus() >= 400) {
                  errorCode = a_request.getStatus();
                }
                a_request.clear();
                a_act.complete();
              })
              try {
                await controller.action(a_request);
              } catch(e){
                a_request.clear();
                a_act.error(e);
              }
            });

            if (errorCode)
              throw fcf.Exception("ERROR_404");
            a_request.setRoute(originRoute);
            let newNode = redirectUrl ? (await fcf.application.getRouter().getNode(redirectUrl)) : undefined;
            if (!newNode)
              break;
            node = newNode;
            routeInfo = new fcf.RouteInfo({url: redirectUrl});
          } catch(e){
            a_request.sendError(e);
            break;
          }
        } while(false);

        fcf.append(routeInfo.args, node.args);
        routeInfo.subUri = node.subUri;
        routeInfo.title  = node.node.title;
        routeInfo.description  = node.node.endpoint && node.node.endpoint.description ? node.node.endpoint.description : "";

        return routeInfo;
      }

      async action(a_request) {
        if (!a_request.getRoute().args.url){
          a_request.sendError(new fcf.Exception("ERROR_HTTP_ARG_NOT_SET", {"arg": "url"}));
          return;
        }
        let routeInfo = await this.getRoute(a_request, a_request.getRoute().args.url);
        a_request.send({route: routeInfo});
      }
    };

    Namespace.Route = Route;

    return Namespace.Route;
  }
});
