/*
 * Copyright 2012 The Apache Software Foundation.
 *
 * 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 org.apache.clerezza.jaxrs.stanbol.webfragements

import org.apache.stanbol.commons.web.base.resource.BaseStanbolResource
import org.apache.wink.osgi.WinkRequestProcessor
import java.util.ArrayList
import java.util.Collections
import javax.servlet.FilterChain
import javax.servlet.Servlet
import javax.servlet.ServletContext
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServlet
import javax.ws.rs.ext.ContextResolver
import javax.ws.rs.ext.Provider
import org.apache.clerezza.osgi.services.ActivationHelper
import org.apache.clerezza.platform.globalmenu.GlobalMenuItem
import org.apache.clerezza.platform.globalmenu.GlobalMenuItemsProvider
import org.apache.felix.scr.annotations._
import org.apache.stanbol.commons.web.base.LinkResource
import org.apache.stanbol.commons.web.base.NavigationLink
import org.apache.stanbol.commons.web.base.ScriptResource
import org.apache.stanbol.commons.web.base.WebFragment
import org.osgi.framework.BundleContext
import org.osgi.framework.ServiceReference
import org.osgi.service.component.ComponentContext
import org.slf4j.scala.Logging



@Component
@Reference(name = "webFragment", 
           referenceInterface = classOf[WebFragment], 
           cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, 
           policy = ReferencePolicy.DYNAMIC)
//@Service(Array(classOf[Servlet]))
//@Property(name = "alias", value = Array("/stanbol-ugly-servlet-context-initializer"))
@Service(value = Array(classOf[javax.servlet.Filter], classOf[GlobalMenuItemsProvider]))
@Property(name ="pattern", value=Array(".*"))
class WebFragmentRunner extends javax.servlet.Filter with GlobalMenuItemsProvider with Logging {

  @Reference
  private var winkRequestProcessor: WinkRequestProcessor = _
  
  @Property(name = "org.apache.stanbol.commons.web.alias", value = Array("/"))
  final val ALIAS_PROPERTY = "org.apache.stanbol.commons.web.alias";

  @Property(name = STATIC_RESOURCES_URL_ROOT_PROPERTY, value = Array("/static"))
  final val STATIC_RESOURCES_URL_ROOT_PROPERTY = "org.apache.stanbol.commons.web.static.url";

  /**
   * The origins allowed for multi-host requests
   */
  @Property(name = CORS_ORIGIN, cardinality = 100, value = Array("*"))
  final val CORS_ORIGIN = "org.apache.stanbol.commons.web.cors.origin";

  @Property(name = CORS_ACCESS_CONTROL_EXPOSE_HEADERS, cardinality = 100, value = Array("Location"))
  final val CORS_ACCESS_CONTROL_EXPOSE_HEADERS = "org.apache.stanbol.commons.web.cors.access_control_expose_headers";

  
  private var webFragments: List[WebFragment] = Nil
  private var bundleContext: BundleContext = _
  private var activator: Option[ActivationHelper] = None
  
  private val linkResources = new ArrayList[LinkResource]();
  private val scriptResources = new ArrayList[ScriptResource]();
  private val navigationLinks = new ArrayList[NavigationLink]();
  
  private var staticUrlRoot: String = _
  private var applicationAlias: String = _
  
  private var corsOrigins : java.util.Set[String] = _
  private var exposedHeaders : java.util.Set[String] = _
  private var contextResolverImpl : ContextResolverImpl = _
  
  val menuItems = new java.util.HashSet[GlobalMenuItem]


  @Activate
  def activate(c: ComponentContext) {
    staticUrlRoot = c.getProperties().get(
      STATIC_RESOURCES_URL_ROOT_PROPERTY).asInstanceOf[String]
    applicationAlias = c.getProperties().get(
      ALIAS_PROPERTY).asInstanceOf[String];
    {
      val values = c.getProperties().get(CORS_ORIGIN);
      values match {
        case s: String => corsOrigins =  Collections.singleton(s)
        case i: java.lang.Iterable[_] => {
            corsOrigins = new java.util.HashSet[String]();
            val iter = i.iterator
            while (iter.hasNext) {
              corsOrigins.add(iter.next().toString)
            };
        }
      }
    }
    {
      val values = c.getProperties().get(CORS_ACCESS_CONTROL_EXPOSE_HEADERS);
      values match {
        case s: String => exposedHeaders = Collections.singleton(s)
        case i: java.lang.Iterable[_] => {
            exposedHeaders = new java.util.HashSet[String]();
            val iter = i.iterator
            while (iter.hasNext) {
              exposedHeaders.add(iter.next().toString)
            }
        }
      }
    }
    synchronized {
      println("activating with "+webFragments);
      bundleContext = c.getBundleContext
      registerFragments()
    }
  }
  
  @Deactivate
  def deactivate(c: ComponentContext) {
    synchronized {
      unregisterClasses()
      activator.foreach(_.stop(c.getBundleContext))
      activator = None
    }
  }

  private def registerFragments() {
    activator = Some(new ActivationHelper {
      
      for (f <- webFragments) {
        import scala.collection.JavaConverters._
        for (s <- f.getJaxrsResourceSingletons.asScala) {
          //could alos direcly invoke wink as below
          registerRootResource(s)
          logger.info("Registered: "+s)
        }
        for (c <- f.getJaxrsResourceClasses.asScala) {
            winkRequestProcessor.bindComponentClass(c)
            logger.info("Registered class: "+c)
        }
      }
      start (bundleContext)
    })
  }
  
  private def unregisterClasses() {
    for (f <- webFragments) {
        import scala.collection.JavaConverters._
        for (c <- f.getJaxrsResourceClasses.asScala) {
            winkRequestProcessor.unbindComponentClass(c)
            logger.info("Unregistered class: "+c)
        }
      }
  }

  protected def bindWebFragment(f: WebFragment) {
    linkResources.addAll(f.getLinkResources());
    scriptResources.addAll(f.getScriptResources());
    navigationLinks.addAll(f.getNavigationLinks());
    {
      import scala.collection.JavaConverters._
      for (nl <- f.getNavigationLinks.asScala) {
        menuItems.add(new GlobalMenuItem("/"+nl.getPath, nl.getLabel, nl.getLabel, nl.getOrder, "Stanbol"))
      }
    };
    synchronized {
      webFragments ::= f
      activator.foreach { a =>
        unregisterClasses();
        a.stop(bundleContext);
        registerFragments()
      }
    } 
  }

  protected def unbindWebFragment(f: WebFragment) {
    linkResources.removeAll(f.getLinkResources());
    scriptResources.removeAll(f.getScriptResources());
    navigationLinks.removeAll(f.getNavigationLinks());
    {
      import scala.collection.JavaConverters._
      for (nl <- f.getNavigationLinks.asScala) {
        menuItems.remove(new GlobalMenuItem("/"+nl.getPath, nl.getLabel, nl.getLabel, nl.getOrder, "Stanbol"))
      }
    };
    synchronized {
      webFragments = webFragments diff List(f)
      activator.foreach { a=>
        unregisterClasses()
        a.stop(bundleContext);
        registerFragments()
      }
    }
  }
  
  //all this servlet stuff is only needed to get the servlet context
  //override def init(config: javax.servlet.ServletConfig) {
  override def init(config: javax.servlet.FilterConfig) {
    val servletContext = config.getServletContext

    Collections.sort(linkResources);
    Collections.sort(scriptResources);
    Collections.sort(navigationLinks);
    servletContext.setAttribute(classOf[BundleContext].getName(), bundleContext);
    servletContext.setAttribute(BaseStanbolResource.ROOT_URL, applicationAlias);
    servletContext.setAttribute(BaseStanbolResource.STATIC_RESOURCES_ROOT_URL, staticUrlRoot);
    servletContext.setAttribute(BaseStanbolResource.LINK_RESOURCES, linkResources);
    servletContext.setAttribute(BaseStanbolResource.SCRIPT_RESOURCES, scriptResources);
    servletContext.setAttribute(BaseStanbolResource.NAVIGATION_LINKS, navigationLinks);
    servletContext.setAttribute(CORS_ORIGIN, corsOrigins);
    servletContext.setAttribute(CORS_ACCESS_CONTROL_EXPOSE_HEADERS, exposedHeaders);
    contextResolverImpl = new ContextResolverImpl(servletContext)
    winkRequestProcessor.bindComponent(contextResolverImpl)
  }
  
  override def doFilter(request: ServletRequest, response: ServletResponse,
      chain: FilterChain){
    chain.doFilter(request, response);
  }

  override def destroy() {
      winkRequestProcessor.unbindComponent(contextResolverImpl)
  }

  def getMenuItems() : java.util.Set[GlobalMenuItem]  = {

    return menuItems
  }

  @Provider
  class ContextResolverImpl(servletContext: ServletContext) extends ContextResolver[ServletContext] {

    def getContext(clazz: Class[_]): ServletContext = {
        def wrapped = servletContext;
        new ServletContext() {
          def getServletContextName() :String = { wrapped.getServletContextName()}
      def removeAttribute(name: String) :Unit = { wrapped.removeAttribute(name)}
      def setAttribute(name: String, value: Any) :Unit = { wrapped.setAttribute(name, value)}
      def getAttributeNames() :java.util.Enumeration[_] = { wrapped.getAttributeNames()}
      def getAttribute(name: String) :Object = { 
              val result = wrapped.getAttribute(name)
              if (result != null) {
                result
              } else {
                val serviceReference = bundleContext.getServiceReference(name) //: ServiceReference[Object]
                if (serviceReference != null) {
                  bundleContext.getService(serviceReference).asInstanceOf[Object]
                } else null
              }
            }
      def getInitParameterNames() :java.util.Enumeration[_] = { wrapped.getInitParameterNames()}
      def getInitParameter(name: String) :String = { wrapped.getInitParameter(name)}
      def getServerInfo() :String = { wrapped.getServerInfo()}
      def getRealPath(name: String) :String = { wrapped.getRealPath(name)}  
      def log(message: String, exception: Throwable) :Unit = { wrapped.log(message,exception)}
      def log(exception: Exception, message: String) :Unit = { wrapped.log(exception, message)}
      def log(message: String) :Unit = { wrapped.log(message)}
      @Deprecated
      def getServletNames() :java.util.Enumeration[_] = { wrapped.getServletNames()}
      @Deprecated
      def getServlets() :java.util.Enumeration[_] = { wrapped.getServlets()}
      @Deprecated
      def getServlet(name: String) :javax.servlet.Servlet = { wrapped.getServlet(name)}
      def getNamedDispatcher(name: String) :javax.servlet.RequestDispatcher = { wrapped.getNamedDispatcher(name)}
      def getRequestDispatcher(path: String) :javax.servlet.RequestDispatcher = { wrapped.getRequestDispatcher(path)}
      def getResourceAsStream(path: String) :java.io.InputStream = { wrapped.getResourceAsStream(path)}
      def getResource(path: String) :java.net.URL = { wrapped.getResource(path)}
      def getResourcePaths(path: String) :java.util.Set[_] = { wrapped.getResourcePaths(path)}
      def getMimeType(file: String) :String = { wrapped.getMimeType(file)}
      def getMinorVersion() :Int = { wrapped.getMajorVersion()}
      def getMajorVersion() :Int = { wrapped.getMajorVersion()}
      def getContext(uripath: String) :javax.servlet.ServletContext = { wrapped.getContext(uripath)}
      def getContextPath() :String = { wrapped.getContextPath()}
        }
    }
  }
}


