package org.w3.directmapping

import org.w3.rdf.{Model => RDFModel, Implicits => RDFImplicits, _}

import org.w3.rdb.RDB._
import java.net.URLEncoder

trait DirectMappingModule extends RDFModel {

  lazy val DirectMapping = new DirectMapping {  }

  /**
   * The mapping functions implementing
   * <http://www.w3.org/2001/sw/rdb2rdf/directGraph/>
   */
  trait DirectMapping {

    /**
     * trick to reset the BNode generation, which is predictable using this function
     * the RDF module should provide this functionality at some point
     */
    private var NextBNode = 97
    def freshbnode () : BNode = {
      val ret = NextBNode
      NextBNode = NextBNode + 1
      BNode(ret.toChar.toString)
    }

    /**
     * TODO
     */
    def dbToTupleMap(db:Database):PartialFunction[Tuple, Node] =
      db.relations map { relation => tupleMapForRelation(relation) } reduceLeft { _ orElse _ }

    var MinEncode = true

    /**
     * TODO
     */
    def tupleMapForRelation(r:Relation):PartialFunction[Tuple, Node] = {
      def tupleToNode(t:Tuple):Node =
        r.pk match {
          // Table has a primary key
          case Some(pk) => NodeIRI(tupleToIRI(t, pk))
          // Table has no primkary key
          case None => NodeBNode(freshbnode())
        }
      r.body map { t => t -> tupleToNode(t) } toMap
    }
    
    /**
     * Main function expressing the RDF semantics of a SQL database
     */
    def databaseSemantics(db:Database):Graph = {
      // that makes this implementation not thread-safe
      NextBNode = 97
      val tuplemap = dbToTupleMap(db)
      Graph(db.relations flatMap { r => relationSemantics(tuplemap)(r) })
    }
  
    def relationSemantics(tuplemap:PartialFunction[Tuple, Node])(r:Relation):Graph =
      Graph(r.body flatMap { t => tupleSemantics(tuplemap)(t) })
  
    def tupleSemantics (tuplemap:PartialFunction[Tuple, Node])(t:Tuple):Set[Triple] = {
      val s:SubjectNode = SubjectNode(tuplemap(t))
      val poFromFKs = t.references map { fk => referenceSemantics(tuplemap)(t, fk) }
      val poFromLexicalValues = t.scalars flatMap { a => lexicalValueSemantics(t, a) }
      val poFromRelation = (PredicateIRI(IRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")),
			    ObjectNode(NodeIRI(IRI(UE(t.relation)))))
      (poFromFKs ++ poFromLexicalValues + poFromRelation) map { case (p, o) => Triple(s, p, o) }
    }

    /**
     * a foreign key contribute to generating triples
     */
    def referenceSemantics (tuplemap:PartialFunction[Tuple, Node])(t:Tuple, fk:ForeignKey):(Predicate, Object) = {
      val p = referencePredicateSemantics(t.relation, fk)
      val o = ObjectNode(tuplemap(t.dereference(fk)))
      (PredicateIRI(p), o)
    }

    /**
     * a lexical value contribute to generating triples (only if it is not null)
     */
    def lexicalValueSemantics(t:Tuple, a:AttrName):Option[(Predicate, Object)] = {
      val r:Relation = t.relation
      // a is implicitly promoted to an AttrList
      val p = lexicalValuePredicateSemantics(r, a)
      val cellValue = t(a)
      val datatype = r.header(a)
      (cellValue, datatype)  match {
	case (LexicalValue(l), Datatype.STRING) => {
	  val o = PlainLiteral(l, None)
	  Some(PredicateIRI(p), ObjectLiteral(o))
	}
	case (LexicalValue(l), d) => {
	  val o = TypedLiteral(l, datatypeSemantics(d))
	  Some(PredicateIRI(p), ObjectLiteral(o))
	}
	case (␀, _) => None
      }
      
    }

    /**
     * the generated IRI has to be "parsable" for a reverse mapping
     */
    def lexicalValuePredicateSemantics(r:Relation, a:AttrName):IRI =
      IRI(UE(r) + "#" + a)

    /**
     * the generated IRI has to be "parsable" for a reverse mapping
     */
    def referencePredicateSemantics(r:Relation, as:AttrList):IRI =
      IRI(UE(r) + "#" + as.attrs.mkString("_"))

    /**
     * the generated IRI has to be "parsable" for a reverse mapping
     * this function must generate a different IRI for each tuple
     * we know (not enforced by a type) that
     * - as is actually a pk
     * - the lexicalvalues are generated from pk
     * hence, the zip operation is safe as both list have the same size
     */
    def tupleToIRI(t:Tuple, pk:AttrList):IRI = {
      val r:Relation = t.relation
      val ls:List[LexicalValue] = t.lexvalues(pk)
      val pairs:List[String] = pk.attrs zip ls map { case (attrName, lexicalValue) => UE(attrName) + "." + UE(lexicalValue.s) }
      IRI(UE(r) + "/" + pairs.mkString("_") + "#_")
    }  

    //   IRI(UE(rn) + "#" + encoded)
    // }

    // TODO: aren't they already part of the RDF model?
    def datatypeSemantics (d:Datatype) : IRI =
      d match {
        case Datatype.INTEGER => IRI("http://www.w3.org/2001/XMLSchema#integer")
        case Datatype.FLOAT => IRI("http://www.w3.org/2001/XMLSchema#float")
        case Datatype.DATE => IRI("http://www.w3.org/2001/XMLSchema#date")
        case Datatype.TIME => IRI("http://www.w3.org/2001/XMLSchema#time")
        case Datatype.TIMESTAMP => IRI("http://www.w3.org/2001/XMLSchema#timestamp")
        case Datatype.CHAR => IRI("http://www.w3.org/2001/XMLSchema#char")
        case Datatype.VARCHAR => IRI("http://www.w3.org/2001/XMLSchema#varchar")
        case Datatype.STRING => IRI("http://www.w3.org/2001/XMLSchema#string")
      }

    def UE (s:String) : String = {
      if (MinEncode) {
	s.replaceAll("%", "%25")
	 .replaceAll("<", "%3C")
	 .replaceAll("\\+", "%2B")
	 .replaceAll(" ", "+")
	 .replaceAll("/", "%2F")
	 .replaceAll("=", "%3D")
	 .replaceAll("#", "%23")
	 .replaceAll(">", "%3E")
      } else {
	val r = URLEncoder.encode(s, "UTF-8")
	r
      }
    }
    def UE(r:Relation):String = UE(r.name.n)
    def UE(a:AttrName):String = UE(a.n)
  
  }

}
