package org.apache.clerezza.platform.graphnodeprovider

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.net.{HttpURLConnection, URL}
import org.slf4j.scala._
import org.apache.clerezza.rdf.core.access._
import org.apache.clerezza.rdf.core._
import org.apache.clerezza.platform.config.PlatformConfig
import org.apache.clerezza.platform.Constants
import org.apache.clerezza.platform.graphprovider.content.ContentGraphProvider
import org.apache.clerezza.platform.users.WebIdGraphsService
import org.apache.clerezza.rdf.utils.{UnionMGraph, UriMutatingTripleCollection, GraphNode}
import java.util.concurrent.locks.Lock
import org.apache.clerezza.platform.usermanager.UserManager
import java.security.{PrivilegedAction, AccessController}
import org.apache.clerezza.rdf.utils.graphnodeprovider.GraphNodeProvider

/**
 * A service that returns a GraphNode for a specified named resource, the returned GraphNode has
 * as BaseGraph the ContentGraph provided by the ContentGraphProvider and the for remote uris the
 * Graphs they dereference to and for local URIs with a path-section starting with /user/{username}/
 * the local-public-graph of that user.
 */
class PlatformGraphNodeProvider extends GraphNodeProvider with Logging {

  /**
   * Get a GraphNode for the specified resource, see class comments for details.
   */
  def get(uriRef: UriRef): GraphNode = {
    val uriString = uriRef.getUnicodeString
    val isLocal: Boolean = {
      import scala.collection.JavaConversions._
      //we assume all non http* uris to be local
      !uriString.toLowerCase.startsWith("http") || platformConfig.getBaseUris.exists(baseUri => uriString.startsWith(baseUri.getUnicodeString))
    }
    get(uriRef, isLocal)
  }

  /**
   * Get a GraphNode for the specified resource, The resource is assumed to be local, i.e. the method behaves like
   * get(UriRef) for a Uri with an authority section contained in the Set retuned by
   * <code>org.apache.clerezza.platform.config.PlatformConfig#getBaseUris()</code>
   */
  def getLocal(uriRef: UriRef): GraphNode = {
    get(uriRef, true)
  }
    
    
    /**
     *return true iff getLocal(uriRef).getNodeContext.size > 0
     */
    def existsLocal(uriRef: UriRef): Boolean = {
        val cgGraph = cgProvider.getContentGraph
        lazy val localInstanceUri = {
            val uri = new java.net.URI(uriRef.getUnicodeString)
      new UriRef(Constants.URN_LOCAL_INSTANCE + uri.getPath)
    }
        //TODO handle /user/
        existsInGraph(uriRef,cgGraph) || existsInGraph(localInstanceUri, cgGraph)
    }
  
    private[this] def existsInGraph(nodeUri: UriRef, tc: LockableMGraph): Boolean = {
        var readLock: Lock = tc.getLock.readLock
        readLock.lock
        try {
            return tc.filter(nodeUri, null, null).hasNext || tc.filter(null, null, nodeUri).hasNext
        }
        finally {
            readLock.unlock
        }
    }
    
  
  private def get(uriRef: UriRef, isLocal: Boolean): GraphNode = {
    val uriString = uriRef.getUnicodeString
    

    val uriPath = {
      val uri = new java.net.URI(uriString)
      uri.getPath
    }

    lazy val uriPrefix = {
      val uri = new java.net.URI(uriString)
      uri.getScheme+"://"+uri.getAuthority
    }

    val anyHostUri = new UriRef(Constants.URN_LOCAL_INSTANCE + uriPath)

    var mGraphs: List[TripleCollection] = Nil

    def addToUnion(mGraph: LockableMGraph) {
      //adding uncondinionately if (existsInGraph(uriRef, mGraph)) {
      mGraphs ::= mGraph
      //}
      if (isLocal) {
        if (existsInGraph(anyHostUri, mGraph)) {
          mGraphs ::= new UriMutatingTripleCollection(mGraph, Constants.URN_LOCAL_INSTANCE, uriPrefix)
        }
      }
    }

    val cgGraph = cgProvider.getContentGraph

    addToUnion(cgGraph)

    if (isLocal && (uriPath != null) && uriPath.startsWith("/user/")) {
      val nextSlash = uriPath.indexOf('/',6)    
      if (nextSlash != -1) {
        val userName = uriPath.substring(6, nextSlash)
        val webIdOption = AccessController.doPrivileged(new PrivilegedAction[Option[UriRef]]() {
            def run(): Option[UriRef] = {
              val userNode: GraphNode = userManager.getUserInSystemGraph(userName)
              if (userNode != null) {
                userNode.getNode match {
                  case u: UriRef => Some(u)
                  case _ => None
                }
              } else {
                None
              }
            }
          }
        )
        webIdOption match {
          case Some(u) => {
            val webIdInfo = webIdGraphsService.getWebIdInfo(u)
            addToUnion(webIdInfo.localPublicUserData)
          }
          case None => ;
        }
      }
    }

    if (!isLocal) {
      /**
       * As the resource might identify something other than a document we use this to find the redirect location
       */
      lazy val redirectLocationString = {
        val acceptHeader = "application/rdf+xml,*/*;q.1"
        val url = new URL(uriString)
        val connection = url.openConnection()
        connection match {
          case hc : HttpURLConnection => {
              hc.setRequestMethod("HEAD");
              hc.setInstanceFollowRedirects(false)
              hc.addRequestProperty("Accept",  acceptHeader)
              hc.getResponseCode match {
                case HttpURLConnection.HTTP_SEE_OTHER  => {
                    val location = hc.getHeaderField("Location")
                    if (location == null) {
                      throw new RuntimeException("No Location Headers in 303 response")
                    }
                    location
                  }
                case _ => uriString
              }
            }
          case _ => uriString
        }
      }

      //TODO add method to WebProxy to get the graph location location
      val graphUriString = {
        val hashPos = uriString.indexOf('#')
        if (hashPos != -1) {
          uriString.substring(0, hashPos)
        } else {
          redirectLocationString
        }
      }
      
      addToUnion(tcManager.getMGraph(new UriRef(graphUriString)))
    }

    val unionMGraph = new UnionMGraph(mGraphs:_*);
    new GraphNode(uriRef, unionMGraph)
  }

  private var tcManager: TcManager = null;

  protected def bindTcManager(tcManager: TcManager) = {
    this.tcManager = tcManager
  }

  protected def unbindTcManager(tcManager: TcManager) = {
    this.tcManager = null
  }

  private var platformConfig: PlatformConfig = null;

  protected def bindPlatformConfig(c: PlatformConfig) = {
    this.platformConfig = c
  }

  protected def unbindPlatformConfig(c: PlatformConfig) = {
    this.platformConfig = null
  }

  private var cgProvider: ContentGraphProvider = null
  protected def bindCgProvider(p: ContentGraphProvider) {
    this.cgProvider = p
  }
  protected def unbindCgProvider(p: ContentGraphProvider) {
    this.cgProvider = null
  }

  private var webIdGraphsService: WebIdGraphsService = null
  protected def bindWebIdGraphsService(webIdGraphsService: WebIdGraphsService): Unit = {
    this.webIdGraphsService = webIdGraphsService
  }

  protected def unbindWebIdGraphsService(webIdGraphsService: WebIdGraphsService): Unit = {
    this.webIdGraphsService = null
  }

  private var userManager: UserManager = null

  protected def bindUserManager(userManager: UserManager): Unit = {
    this.userManager = userManager
  }

  protected def unbindUserManager(userManager: UserManager): Unit = {
    this.userManager = null
  }

}
