/*
   Copyright 2010 Aaron J. Radke

   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 cc.drx


/**a generic discrete Grid container or 2d Matrix of m rows by n cols*/
trait Grid[A] extends Iterable[A]{
  //--required
  def m:Int //number of rows (indexed by i)
  def n:Int //number of cols (indexed by j)
  def apply(k:Int):A

  //--overridable for efficiency
  override def size:Int = m*n

  //--features
  def get(i:Int,j:Int):Option[A] = {val k = i*n+j; if(k >= m*n) None else Some(apply(k)) }
  def apply(i:Int,j:Int):A = apply(i*n+j)
  def zipWithIndices:Iterable[(A,Int,Int)] = for(i <- 0 to (m-1); j <- 0 to (n-1)) yield (apply(i*n+j),i,j) //row first

  def iterator:Iterator[A] = Iterator.range(0,size) map apply

  def row(i:Int):Iterable[A] = for(j <- 0 to (n-1)) yield apply(i*n+j) //TODO test
  def col(j:Int):Iterable[A] = for(i <- 0 to (m-1)) yield apply(i*n+j) //TODO test

  override def toString = s"Grid($m-by-$n, ${Format(take(3).toList)} ...)"
}
object Grid{
  def apply[A](vs:Array[A],nCols:Int):GridArray[A] = new GridArray[A](vs.size/nCols, nCols, vs)
  /**specialized array**/
  def apply(ds:Iterable[Double],nCols:Int):GridArray[Double] = apply[Double](ds.toArray, nCols)
  def fill(n:Int,m:Int)(f: => Double) = new GridArray[Double](n,m,Array.fill(n*m)(f))
  def fill(n:Int,m:Int)(v:Int) = new GridArray[Int](n,m,Array.fill(n*m)(v))

  // def readDouble(in:Input):GridArray[Double] = in.read{is => ???  }
}
/**a Grid or Matrix of m rows by n cols stored in an Array*/
class GridArray[A](val m:Int, val n:Int, private val vs:Array[A]) extends Grid[A]{
  def apply(k:Int):A = vs(k)
  def update(k:Int, v:A):Unit = vs(k) = v  //danger allows mutable access
}
object GridArrayDoubleFile{
  def apply(m:Int, n:Int, f:File):GridArrayDoubleFile = new GridArrayDoubleFile(m,n,ArrayDoubleFile(f,m*n))
}
class GridArrayDoubleFile(val m:Int, val n:Int, private val vs:ArrayDoubleFile) extends Grid[Double]{
  def apply(k:Int):Double = vs(k)
  override def row(i:Int):Iterable[Double] = drop(i*n).take(n)
}
//TODO make a file memory mapped file, there are many levels of potential abstraction.. LargeArray based and reading and writing methods

/*
class GridArrayDouble extends GridArray[Double]{
  // def write(out:Output):Unit = out.write{os => ???  }
}
*/
//TODO transpose
// class GridArrayTranspose[A](val m:Int, val n:Int, private val vs:Array[A]) extends GridArray[A]{
//   override def apply(i:Int,j:Int):A = apply(j*n+i)
// }