/*
 * Copyright (C) 2020  Prasoon Joshi
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package repository

import java.util.UUID

import org.joda.time.DateTime
import play.api.libs.json.Json
import sangria.macros.derive.GraphQLField
import graphql.SchemaInputObjects.DocumentInput
import graphql.SchemaInputObjects.IngestWithCrosswalk
import play.mvc.Http
import com.google.inject.Inject
import play.api.libs.ws.WSClient
import scala.concurrent.Future
import scala.util.chaining._
import scala.util.{Try, Failure, Success}

import models._
import models.{EntityType => ET}
import repository.NiosxGraph._

import com.lucidchart.open.xtract.ParseResult
import utils.FileUtils
import ingestion.CsvIngestion
import scala.io.Source
import play.api.libs.json.JsValue
import play.api.Configuration
import java.io.ObjectOutputStream
import java.io.ObjectInputStream
import play.api.mvc.WebSocket
import java.net.Socket
import org.apache.tinkerpop.gremlin.structure.Vertex
import org.apache.tinkerpop.gremlin.structure.Direction

object DataAccessComponent {
  //Mutations
  import graphql.SchemaInputObjects._

  case class CtxObject(mutation: Mutation)

  sealed trait Mutation {

    implicit val DocumentInputFormat = Json.format[DocumentInput]

    /*
    Creates a new finding aid and initializes the control and archDesc sections
    with default values.
     */
    @GraphQLField
    def addDocument(ead: DocumentInput) = {
      ead.id
    }

    def addAccession = ???
    def addStorageLocation = ??? //a tree structure
  }
}

class MockDataRepository(conf: Configuration,
  ws: WSClient, 
  gs: GremlinServer,
  ec: NiosxExecutionContext) extends DataRepository {

  implicit val executionContext = ec

  private val sampleXml = 
<ead xmlns="http://ead3.archivists.org/schema/" audience="external" id="f8b405d6-fc4f-4ae8-adeb-7ddb07b758bd"> <!-- EAD3 required element -->
      <control countryencoding="iso3166-1" dateencoding="iso8601" langencoding="iso639-2b" repositoryencoding="iso15511" scriptencoding="iso15924"> <!-- EAD3 required element -->
        <recordid instanceurl="http://library.marist.edu/archives/LTP/LTP.xml">1.1</recordid> <!-- EAD3 required element -->
        <filedesc> <!-- EAD3 required element -->
          <titlestmt> <!-- EAD3 required element -->
            <titleproper>Lowell Thomas Papers</titleproper> <!-- Required element within titlestmt -->
          </titlestmt>
          <publicationstmt>
            <publisher>Marist College</publisher>
            <date>April 2010</date>
            <address>
              <addressline>3399 North Road</addressline>
              <addressline>Poughkeepsie, NY 12601</addressline>
            </address>
            <p><ref href="https://creativecommons.org/publicdomain/zero/1.0/">CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</ref></p>
          </publicationstmt>
        </filedesc>
        <maintenancestatus value="revised"/> <!-- EAD3 required element -->
        <maintenanceagency countrycode="US"> <!-- EAD3 required element -->
          <agencycode>NPkMC</agencycode>
          <agencyname>Marist College Archives Special Collections</agencyname>
        </maintenanceagency>
        <languagedeclaration>
          <language langcode="eng">English</language>
          <script scriptcode="latn">Latin</script>
        </languagedeclaration>
        <maintenancehistory> <!-- EAD3 required element -->
          <maintenanceevent> <!-- EAD3 required element -->
            <eventtype value="created"/> <!-- EAD3 required element -->
            <eventdatetime standarddatetime="2010-04">April 2010</eventdatetime> <!-- EAD3 required element -->
            <agenttype value="human"/> <!-- EAD3 required element -->
            <agent>Monish Singh</agent> <!-- EAD3 required element -->
          </maintenanceevent>
          <maintenanceevent>
            <eventtype value="updated"/>
            <eventdatetime standarddatetime="2014-09-11">September 11, 2014</eventdatetime>
            <agenttype value="human"/>
            <agent>Monish Singh</agent>
          </maintenanceevent>
        </maintenancehistory>
      </control>
      <archdesc level="collection"> <!-- EAD3 required element -->
        <did> <!-- EAD3 required element -->
          <repository> <!-- Optional EAD3 element, added to comply with DACS requirement for 'Name and Location of Repository Element' -->
            <corpname source="NAF" identifier="http://id.loc.gov/authorities/names/n91102518">
              <part>Marist College</part>
              <!-- <part>Archives &amp; Special Collections</part> -->
            </corpname>
            <address>
              <addressline>3399 North Road</addressline>
              <addressline>Poughkeepsie, NY 12601</addressline>
            </address>
          </repository>
          <origination source="lcnaf" identifier="http://id.loc.gov/authorities/names/n79139713"> <!-- Optional EAD3 element, added to comply with DACS requirement for 'Name of Creator(s) Element'. The new <relations> element can satisfy this and expand on it, but as <relations> is experimental, use of <origination> is recommended for now -->
            <persname>
              <part localtype="surname">Thomas</part>
              <part localtype="givenname">Lowell</part>
              <part localtype="dates">1892-1981</part>
            </persname>
          </origination>
          <unittitle>Lowell Thomas Papers</unittitle> <!-- Optional EAD3 element, added to comply with DACS requirement for 'Title Element' -->
          <unitid>MA-1</unitid> <!-- Optional EAD3 element, added to comply with 'Reference Code Element' of DACS requirement -->
          <unitdatestructured unitdatetype="inclusive"> <!-- Optional EAD3 element, added to comply with DACS requirement for 'Date Element' -->
           <daterange>
              <fromdate standarddate="1789">1789</fromdate>
              <todate standarddate="1996">1996</todate>
            </daterange>
          </unitdatestructured>
          <unitdatestructured unitdatetype="bulk">
            <daterange>
              <fromdate standarddate="1900">1900</fromdate>
              <todate standarddate="1981">1981</todate>
            </daterange>
          </unitdatestructured>
          <physdescset> <!-- Optional EAD3 element, added to comply with DACS requirement for 'Extent Element' -->
            <physdescstructured physdescstructuredtype="carrier" coverage="whole" >
              <quantity>6</quantity>
              <unittype>boxes</unittype>
            </physdescstructured>
            <physdescstructured physdescstructuredtype="spaceoccupied" coverage="whole">
              <quantity>6</quantity>
              <unittype>cubic feet</unittype>
            </physdescstructured>
          </physdescset>
          <langmaterial> <!-- Optional EAD3 element, added to comply with DACS requirement for 'Language and Scripts of the Material Element' -->
            <language langcode="eng">English</language>
          </langmaterial>
        </did>
        <accessrestrict> <!-- Optional EAD3 element, added to comply with DACS requirement for 'Conditions Governing Access Element' -->
          <p>Items in this collection do not circulate and may be used in-house only.</p>
        </accessrestrict>
        <!--<userestrict>--><!-- Optional EAD3 element, added to comply with DACS requirement for 'Conditions Governing Access Element' -->
          <!--<p>Individuals requesting reproductions expressly assume the responsibility for compliance with all pertinent provisions of the Copyright Act, 17 U.S.C. ss101 et seq. Patrons further agree to indemnify and hold harmless the Marist College Archives &amp; Special Collections and its staff in connection with any disputes arising from the Copyright Act, over the reproduction of material at the request of patrons. For more information please visit the following website: http://www.loc.gov/copyright/title17/.</p>
        </userestrict>-->
        <scopecontent> <!-- Optional EAD3 element, added to comply with DACS requirement for 'Scope and Content Element' -->
          <p>The Lowell Thomas Papers encompasses a wide range of materials documenting the life, family, and professional career of Lowell Thomas. These materials include correspondence, administrative and financial documents, manuscripts, printed materials, photographic materials, motion picture films, audio recordings, and memorabilia.</p>
        </scopecontent>
        <bioghist> <!-- Optional EAD3 element, added to comply with DACS single-level optimum requirement for 'Administrative/Biographical History Element' -->
          <p>Lowell Jackson Thomas was born in Woodington, Ohio on 6 April 1892 to two school teachers, Harry G. Thomas and Harriet Wagner Thomas. The family did not remain in Darke County, Ohio for very long because Lowell's father wanted to become a doctor. Harry G. Thomas's pursuit of a medical degree led the family to Kirkman, Iowa where five-year-old Lowell was enrolled in school for the first time. Harry's studies soon led him to the University of Nebraska. Upon graduation, Harry G. Thomas followed his brother's suggestion to begin his medical practice in the twin mining boom towns of Cripple Creek and Victor, Colorado. It was here in the rugged American West that Lowell spent the better part of his childhood and young adult years.</p>
        </bioghist>
        <controlaccess> <!-- Optional EAD3 element, added to comply with DACS single-level optimum requirement for 'Access points' -->
          <persname relator="creator" source="lcnaf" identifier="http://id.loc.gov/authorities/names/n79139713">
            <part localtype="surname">Thomas</part>
            <part localtype="givenname">Lowell</part>
            <part localtype="dates">1892-1981</part>
          </persname>
          <subject source="lcsh" identifier="http://id.loc.gov/authorities/subjects/sh85070753">
            <part>Journalism</part>
            <part>United States</part>
          </subject>
          <subject source="lcsh" identifier="http://id.loc.gov/authorities/subjects/sh85148273">
            <part>World War, 1939-1945</part>
          </subject>
          <subject source="lcsh" identifier="http://id.loc.gov/authorities/subjects/sh2008113810">
            <part>World War, 1914-1918</part>
            <part>Campaigns</part>
            <part>Middle East</part>
          </subject>
        </controlaccess>
        <dsc>
          <c01 level="series">
            <did>
              <unitid>MA-1-01</unitid>
              <unittitle>Textual materials</unittitle>
              <unitdatestructured unitdatetype="inclusive">
                <dateset>
                  <daterange>
                    <fromdate standarddate="1789">1789</fromdate>
                    <todate standarddate="1984">1984</todate>
                  </daterange>
                  <datesingle standarddate="1996">1996</datesingle>
                </dateset>
              </unitdatestructured>
            </did>
            <c02 level="file">
              <did>
                <unittitle>Correspondence, writings, and other material</unittitle>
                <unitid>MA-1-01-01</unitid>
                <unitdatestructured unitdatetype="inclusive">
                  <daterange>
                    <fromdate standarddate="1789">1789</fromdate>
                    <todate standarddate="1984">1984</todate>
                  </daterange>
                </unitdatestructured>
                <container localtype="box">1-5</container>
              </did>
              <scopecontent>
                <p>These files contain correspondence between Thomas family members; published and manuscript writings by Lowell Thomas, including autobiographical content, articles, speeches, and travelogues; and topically-arranged collections of journal articles, newspaper clippings, and ephemera.</p>
              </scopecontent>
            </c02>
            <c02 level="file">
              <did>
                <unittitle>American Expeditionary Forces (A.E.F.)</unittitle>
                <unitid>MA-1-01-02</unitid>
                <unitdatestructured unitdatetype="inclusive">
                  <daterange>
                    <fromdate standarddate="1918">1918</fromdate>
                    <todate standarddate="1919">1919</todate>
                  </daterange>
                </unitdatestructured>
                <container localtype="box">495</container>
                <dao daotype="derived" href="http://library.marist.edu/archives/LTP/digitizedContents/Box%20495/1.25.1.1.495.2.pdf"/>
              </did>
            </c02>
            <c02 level="file">
              <did>
                <unittitle>General Edmund Allenby</unittitle>
                <unitid>MA-1-01-03</unitid>
                <unitdatestructured unitdatetype="inclusive">
                  <datesingle standarddate="1918">1918</datesingle>
                </unitdatestructured>
                <container localtype="box">495</container>
                <dao daotype="borndigital" href="http://library.marist.edu/archives/LTP/digitizedContents/Box%20495/1.25.1.1.495.1.pdf"/>
              </did>
            </c02>
          </c01>
          <c01 level="series">
            <did>
              <unitid>MA-1-02</unitid>
              <unittitle>Graphic materials</unittitle>
              <unitdatestructured unitdatetype="inclusive">
                <daterange>
                  <fromdate standarddate="1891">1891</fromdate>
                  <todate standarddate="1980">1980</todate>
                </daterange>
              </unitdatestructured>
              <container localtype="box">6</container>
            </did>
            <scopecontent>
              <p>Comprises photographs, lantern slides, and glass plate negatives accumulated by Frank R. Roberson and Lowell Thomas arranged by topic and geographic coverage.  Also includes commercial photographs from Beseler Lantern Slide Co., Inc., Curtis and Miller, T.H. McAllister Co., J. Horace McFarland Co., and other firms.</p>
            </scopecontent>
            <controlaccess>
              <persname relator="subject" source="lcnaf" identifier="http://id.loc.gov/authorities/names/n80079447.html">
                <part>Dalai Lama XIV</part>
                <part>1935-</part>
              </persname>
              <persname relator="subject" source="lcnaf" identifier="http://id.loc.gov/authorities/names/n79097491.html">
                <part>Lawrence, T.E. (Thomas Edward)</part>
                <part>1888-1935</part>
              </persname>
            </controlaccess>
          </c01>
        </dsc>
      </archdesc>
    </ead> 

    
  import com.lucidchart.open.xtract.XmlReader
  
//  val sampleDoc = scalaxb.fromXML[generated.Ead](sampleXml)
val sampleDoc = XmlReader.of[models.Ead].read(sampleXml).toOption match {
    case Some(sample) => sample
    case None => throw new Exception("Could not read xml")
  }

  def find[E <: NiosxEntity](id: String): Option[E] = ???
  def textSearch[E <: NiosxEntity](blob: String): Seq[E] = ???
  
  def isValidGraphId(graphId: String): Boolean =
    gs.isValidGraphId(graphId)

  def allFindingAid: List[models.MilliEntity] = 
    List(Metadata[Ead, MilliEntity].toEntity(sampleDoc)) 
  def findingAid(id: String): Option[models.MilliEntity] = ??? 
//    allFindingAid find (_.graphId match {
//      case Some(i) if (i==id) => true
//      case None => false
//    })
  
  def findEntity(graphId: String): Option[MilliEntity] = {
    gs.findById[MilliEntity](graphId: String)
  }


  /**
   * niosx mycelium ideas
   */
  import akka.actor._

  object NiosxSocketActor {
    def props(out: ActorRef) = Props(new NiosxSocketActor(out))
  }
  class NiosxSocketActor(out: ActorRef) extends Actor {
    def receive = {
      case msg =>
        out ! msg
    }
  }


  def fromRemote(blob: String): Seq[MilliEntity] = {
    
    //TODO: Decide the underlying data exchange protocol and add a 
    //list of remote clients to pull data from. Configure at installation.
    val remoteList: List[String] = List("https://api.milli.link/remote")

    val socket: Socket = ???
    val ois = new ObjectInputStream(socket.getInputStream)
    //TODO: Serialize based on the API/Object version.
    //Object version could be recorded as a RDF record, etc.
    val data: Seq[MilliEntity] = ois.readObject().asInstanceOf[Seq[MilliEntity]]
    ois.close
    data

  }

  import graphql.SchemaInputObjects._

//  def universalSearch[E](
//    next: Long,
//    cursor: Option[String],
//    filters: EntityFilterInput): Page = {
//
//      val rs =
//        gs.Find[MilliEntity]
//          .findByBlob(blob)
//          .paginate(next, afterCursor.toLong)
//      val hasNextPage = rs.hasNextPage
//
//      ???
//  }

  def paginatedSearch(
    next: Long, 
    afterCursor: Option[String],
    filters: EntityFilterInput): Page = {
    
      
      def getCursorValue(cursor: Option[String]): Long = Try {
        cursor.getOrElse("0").toLong
      } match {
        case Failure(_) => 0L
        case Success(c: Long) => c
      }

      val endCursor = (getCursorValue(afterCursor)+next).toString
      val hasNextPage = gs.hasMore(filters.blob, endCursor.toLong) 
      val annoCount = 0
      val entityFilter: EntityFilter = 
        fromFilterInput(filters)
      val pageInfo = PageInfo(0, 
        endCursor, 
        hasNextPage,
        entityFilter)

      val localObjects: Seq[MilliEntity] =
        //rs.get
        gs
          .filterEntityGraph(
            blob = filters.blob,
            next = next.toInt,
            cursor = getCursorValue(afterCursor))
  
      val plusAnnos: Seq[MilliEntity] =
        gs.filterAnnos[MilliEntity, Subject](
          entityToAnnoRel = MeToAnnoRelationship,
          blob = filters.blob,
          next = next.toInt,
          cursor = getCursorValue(afterCursor))

//      val remoteObjects: Seq[MilliEntity] = 
//        fromRemote(blob = filters.blob)
      
      val edges: Seq[PageEdge] =
        (localObjects)// ++: plusAnnos)// :++ remoteObjects)
          .map(PageEdge(_, endCursor, next, true, annoCount))

//      gs
//          .paginatedFind(
//            blob   = filters.blob,
//            next   = next.toInt,
//            cursor = getCursorValue(afterCursor))
//          .map(new PageEdge(_, endCursor, next, true, annoCount))
//
      new Page(edges, pageInfo)
  }

  def fromFilterInput(in: EntityFilterInput): EntityFilter =
    new EntityFilter(
      blob = in.blob,
      lang = 
        in.lang.map(_.flatMap(gs.findById[Language](_))),
      dateRange = in.dateRange.map(DateRange.fromDateRangeInput(_)),
      subjects = 
        in.subjects.map(_.flatMap(gs.findById[Subject](_))),
      people = in.people.map(_.flatMap(gs.findById[Person](_))),
      places = in.places.map(_.flatMap(gs.findById[Place](_))),
      partners = in.partners.map(_.flatMap(gs.findById[Partner](_)))
    )

  //TODO: Reactive stream at the next iteration
  def nonPaginatedSearch(blob: String): Seq[MilliEntity] =
    //gs.findByText(blob).get
    gs
      .Find[MilliEntity]
      .findByBlob(blob)
      .get

  def ingestWithCrosswalk(
    ingestWithCrosswalk: IngestWithCrosswalk): Future[Option[models.MilliEntity]] =
        ws
          .url(ingestWithCrosswalk.url)
          .get()
          .map({
            wsResponse => { 
              //scalaxb.fromXML[generated.Ead](wsResponse.xml)
              val ead = 
                XmlReader.of[models.Ead].read(wsResponse.xml).toOption
              import models._
              ead.flatMap({ ead => 
                val entity = Metadata[Ead, MilliEntity].toEntity(ead)
                
                gs.Save[MilliEntity]
                  .save(entity, Darkness) match {
                    case f: gs.SaveFailure => None
                    case s: gs.SaveSuccess =>
                      gs.VertToCC[MilliEntity].convert(s.v)
                  }
              })
            }
          })

  def addSubject(s: SubjectIn): Future[Option[Subject]] = Future {
    val sub = Subject(
      graphId = FileUtils.getNewGraphId,
      prefLabel = s.prefLabel,
      inScheme = s.inScheme,
      id = s.id)

    gs.Save[Subject].save(sub, Darkness) match {
      case f: gs.SaveFailure => None
      case g: gs.SaveSuccess => gs.VertToCC[Subject].convert(g.v)
    }
  }

  def saveRdfDocument(scheme: String, line: String): Option[Int] =  {
    
    val rdf: String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"

    val descriptions = 
      (xml.XML.loadString(line) \ "Description")
        .filter(n => (n \ "inScheme" \@ raw"{$rdf}resource") == scheme )
  
    val subject: Seq[Subject] = (for {
      d <- descriptions
      prefLabel <- (d \ "prefLabel").map(_.text)
      inScheme <- (d \ "inScheme").map(l => l \@ raw"{$rdf}resource")
      id <- d.map(i => i \@ raw"{$rdf}about")
    } yield Subject("graphId",
      typ = ET.Subject,
      prefLabel, inScheme, id))

    subject
      .headOption
      //TODO: Save to a vocabulary, not Darkness
      .flatMap(gs.Save[Subject].save(_, Darkness) match {
        case f: gs.SaveFailure => None
        case s: gs.SaveSuccess => Some(1) //gs.VertToCC[Subject].convert(s.v)
      }) 
  }

  def rdfIngest(scheme: String, filePath: String): Int = {
    Source
      .fromFile(filePath)
      .getLines()
      .map(l => saveRdfDocument(scheme, l))
      .length
  }
    
  import graphql.SchemaInputObjects._

  def csvIngest(in: CsvIngestInput) = {

    CsvIngestion
      .ingestCsvWithFieldMap(in.filePath, in.crosswalk, in.partner)
      .map(e => gs.Save[MilliEntity].save(e, Darkness))
      .length
  }

  def addEntity(e: MilliEntityInput): Future[Option[MilliEntity]] = {
    val niosxEntity = MilliEntity(
      graphId = FileUtils.getNewGraphId,
      agencyCode = e.agencyCode,
      recordId = e.recordId,
      unitId = e.unitId,
      title = e.title,
      creator = e.creator,
      dateOfCreation = e.dateOfCreation,
      extent = e.extent,
      level = e.level,
      partner = Partner(FileUtils.getNewGraphId, ET.Partner, e.partner),
      description = e.description,
      location = e.location,
      accessRestrict = e.accessRestrict,
      useRestrict = e.useRestrict,
      language = e.language,
      unitType = e.unitType,
      format = e.format)
    Future { 
      gs.Save[MilliEntity].save(niosxEntity, Darkness) match {
        case f: gs.SaveFailure => None.tap(_ => println(f.msg))
        case s: gs.SaveSuccess => 
          gs.VertToCC[MilliEntity].convert(s.v)
      }
    }
  }

  def addAnnotation(annoInput: AnnotationInput): 
  Future[Option[models.Annotation]] = {
    val annotation = Annotation(motivation = annoInput.motivation,
      id = "https://api.milli.link/annotation",
      body = Some(Seq(bodyFromBodyInput(annoInput.body))),
      target = Seq(targetFromInput(annoInput.target)))
    Future { 
      gs.Save[Annotation].save(
        annotation,
        ParentRel(
          pGuid = annoInput.targetId,
          rel = MeToAnnoRelationship)
        ) match {
          case f: gs.SaveFailure => None.tap(_ => println(f.msg))
          case s: gs.SaveSuccess => gs.VertToCC[Annotation].convert(s.v)
        }
      //TODO:
      //If we change Relationship to take a list of edges,
      //we can accommodate 
      //concept = NiosxConcept(annoInput.concept))
    }
  }

  def bodyFromBodyInput(in: BodyInput): BodyType = {
    val concept = NiosxConcept(in.concept.getOrElse(GenericConcept.name))
    //TODO: Create body based on `typ`
    in.typ match {
      case "TextualBody" =>
        BodyType(ET.TextualBody,
          //TODO: Add `NiosxConcept` to BodyInput to support generic cases
          in.motivation,
          in.value,
          in.format,
          in.language,
          in.creator,
          concept
          )
    }
  }

  def targetFromInput(in: TargetInput): SpecificResource = 
    SpecificResource(graphId = FileUtils.getNewGraphId,
      typ = ET.SpecificResource, 
      source = in.source,
      targetId = in.targetId)

  def addComment(comment: CommentInput): Future[Option[models.Annotation]] = {
    val targetId = comment.targetId
    val body = BodyType(ET.TextualBody,
      "commenting",
      comment.value,
      "","", "Mock comment agent",
      Comment)
    val annotation = Annotation(motivation = "commenting",
      id = "https://api.milli.link/annotation/",
      body = Some(Seq(body)),
      target = Seq(SpecificResource(graphId = FileUtils.getNewGraphId,
        ET.SpecificResource, "", targetId)))
    Future { gs.Save[Annotation].save(
      entity = annotation,
      edge = ParentRel(targetId, MeToAnnoRelationship)) match {
        case f: gs.SaveFailure => None.tap(_ => println(f.msg))
        case s: gs.SaveSuccess => 
          gs.VertToCC[Annotation].convert(s.v)
      }
      //TODO: Accommodate comment as a concept
      //concept = Comment)
    }
  }

  def addSubjectAnno(in: SubjectAnnoInput): Future[Option[Annotation]] = {
    val concept = SubjectHeading
    val targetId = in.targetId
    val sub: Option[String] = in.prefLabel
    val newGraphId = FileUtils.getNewGraphId
    //TODO: Get fields to fetch or create a subject. Wrap it in a 
    //LocalResource as body and add the annotation.
    val subVert: Vertex =
      in.subjectId
        .flatMap(gs.findVertByGraphId(_))
        .getOrElse(
          sub.fold(throw new Exception("Id invalid and no prefLabel"))
          ({ l: String => 
            gs.Save[Subject].save(
              Subject(graphId = newGraphId,
                prefLabel = l,
                inScheme = "https://api.milli.link/terms/",
                id = newGraphId),
              //TODO: Add to the user's default vocabulary
              Darkness
            ) match {
              case f: gs.SaveFailure => throw new Exception(f.msg)
              case s: gs.SaveSuccess => s.v //gs.VertToCC[Subject].convert(s.v).get
            }
          })
        )
    val subject: Subject = gs.VertToCC[Subject].convert(subVert).get
      
    val graphId = FileUtils.getNewGraphId
    val body = LocalResource(
      graphId = graphId,
      iri = graphId,
      source = subject.graphId,
      concept = concept,
      entity = Some(subject))
    //TODO: Create another target type that's web resource
    val target = SpecificResource(
      graphId = FileUtils.getNewGraphId,
      ET.SpecificResource,
      """The relationship between a Specific Resource and the resource
      that it is a more specific representation of.""",
      targetId)
    val anno = Annotation(motivation = "commenting",
      id = "https://api.milli.link/annotation/",
      body = Some(Seq(body)),
      target = Seq(target)
      )
    
    Future {
      gs.Save[Annotation]
        .save(anno, ParentRel(targetId, MeToAnnoRelationship)) match {
          case f: gs.SaveFailure => None.tap(_ => println(f.msg))
          case success: gs.SaveSuccess => {
            val body: Vertex = 
              success.v
                .vertices(Direction.OUT, AnnoToBodyRel.edgeLabel)
                .next()
            gs.addEdge(from = body, to = subVert, edge = concept.name)
            gs.VertToCC[Annotation].convert(success.v)
          }
        }
    }
  }

  def parseSaveResponse[E](res: gs.SaveResponse,
    converter: gs.VertToCC[E]) = res match {
    case f: gs.SaveFailure => None.tap(_ => println(f.msg))
    case s: gs.SaveSuccess =>
      converter.convert(s.v)
  }

}
