/*
 * Copyright 2015 University of Basel, Graphics and Vision Research Group
 *
 * 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 scalismo.io

import java.io.{ByteArrayOutputStream, File, InputStream}
import java.net.URLDecoder

import breeze.linalg.DenseVector
import scalismo.ScalismoTestSuite
import scalismo.geometry._
import scalismo.statisticalmodel.MultivariateNormalDistribution

import scala.io.Source
import scala.language.implicitConversions
import scala.collection.immutable.Seq

class LandmarkIOTests extends ScalismoTestSuite {

  implicit def doubleToFloat(d: Double): Float = d.toFloat

  implicit def inputStreamToSource(s: InputStream): Source = Source.fromInputStream(s)

  describe("Spray LandmarkIO") {

    val csvName = "/landmarks.csv"
    def csvStream() = getClass.getResourceAsStream(csvName)

    val jsonName = "/landmarks.json"
    def jsonStream() = getClass.getResourceAsStream(jsonName)

    /*
     * LEGACY LANDMARKS (csv format)
     */

    it("can read 3D landmarks in CSV format from a file") {
      val csvPath = getClass.getResource(csvName).getPath
      val landmarksTry = LandmarkIO.readLandmarksCsv[_3D](new File(URLDecoder.decode(csvPath, "UTF-8")))
      landmarksTry should be a 'Success

      val landmarks = landmarksTry.get
      landmarks.size should be(4)
      landmarks(3).point should be(Point(3.0, 1.0, 4.0))
    }

    it("can read 3D landmarks in CSV format from a stream") {
      val landmarksTry = LandmarkIO.readLandmarksCsvFromSource[_3D](csvStream())
      landmarksTry should be a 'Success

      val landmarks = landmarksTry.get
      landmarks.size should be(4)
      landmarks(3).point should be(Point(3.0, 1.0, 4.0))
    }

    it("can read 2D landmarks in CSV format from a file") {
      val csvPath = getClass.getResource(csvName).getPath
      val landmarksTry = LandmarkIO.readLandmarksCsv[_2D](new File(URLDecoder.decode(csvPath, "UTF-8")))
      landmarksTry should be a 'Success

      val landmarks = landmarksTry.get
      landmarks.size should be(4)
      landmarks(3).point should be(Point(3.0, 1.0))
    }

    it("can write a CSV file to disk and restore the landmarks") {
      val tmpFile = File.createTempFile("landmark", "txt")
      tmpFile.deleteOnExit()

      val landmarks =
        Seq(("first", Point(1.0, 2.0, 3.0)), ("second", Point(2.0, 1.0, 3.0))).map(t => Landmark(t._1, t._2))
      LandmarkIO.writeLandmarksCsv(landmarks, tmpFile) should be a 'Success

      val restoredLandmarksTry = LandmarkIO.readLandmarksCsv[_3D](tmpFile)
      restoredLandmarksTry should be a 'Success
      val restoredLandmarks = restoredLandmarksTry.get
      restoredLandmarks.size should be(landmarks.size)

      for ((landmark, restoredLandmark) <- landmarks.zip(restoredLandmarks)) {
        landmark should equal(restoredLandmark)
      }
    }

    /*
     * SIMPLE JSON LANDMARKS
     */

    def distWithDefaultVectors(d1: Double, d2: Double, d3: Double): MultivariateNormalDistribution = {
      val axes = List(DenseVector[Double](1, 0, 0), DenseVector[Double](0, 1, 0), DenseVector[Double](0, 0, 1))
      val devs = List(d1, d2, d3)
      val data = axes zip devs
      MultivariateNormalDistribution(DenseVector[Double](0, 0, 0), data)
    }

    val jsonLm1 = Landmark("one", Point(1, 2, 3))
    val jsonLm2 = Landmark("two", Point(2, 3, 4), Some("Landmark two"), Some(distWithDefaultVectors(1, 4, 9)))
    val jsonLms = List(jsonLm1, jsonLm2)

    it("can serialize and deserialize simple landmarks using JSON") {
      val out = new ByteArrayOutputStream()
      LandmarkIO.writeLandmarksJsonToStream(jsonLms, out)
      val written = new String(out.toByteArray)
      val read = LandmarkIO.readLandmarksJsonFromSource[_3D](Source.fromString(written)).get
      read should equal(jsonLms)
    }

    it("can read simple landmarks from a JSON Stream") {
      val read = LandmarkIO.readLandmarksJsonFromSource[_3D](jsonStream()).get
      read should equal(jsonLms)
    }

  }
}