/**
 * 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.
 */

package org.apache.mahout.math;

import org.apache.mahout.math.flavor.MatrixFlavor;
import org.apache.mahout.math.flavor.TraversingStructureEnum;

import java.util.Arrays;

/** Matrix of doubles implemented using a 2-d array */
public class DenseMatrix extends AbstractMatrix {

  private double[][] values;

  /**
   * Construct a matrix from the given values
   * 
   * @param values
   *          a double[][]
   */
  public DenseMatrix(double[][] values) {
    this(values, false);
  }

  /**
   * Construct a matrix from the given values
   *
   * @param values
   *          a double[][]
   * @param shallowCopy directly use the supplied array?
   */
  public DenseMatrix(double[][] values, boolean shallowCopy) {
    super(values.length, values[0].length);
    if (shallowCopy) {
      this.values = values;
    } else {
      this.values = new double[values.length][];
      for (int i = 0; i < values.length; i++) {
        this.values[i] = values[i].clone();
      }
    }
  }

  /**
   * Constructs an empty matrix of the given size.
   * @param rows  The number of rows in the result.
   * @param columns The number of columns in the result.
   */
  public DenseMatrix(int rows, int columns) {
    super(rows, columns);
    this.values = new double[rows][columns];
  }

  @Override
  public Matrix clone() {
    DenseMatrix clone = (DenseMatrix) super.clone();
    clone.values = new double[values.length][];
    for (int i = 0; i < values.length; i++) {
      clone.values[i] = values[i].clone();
    }
    return clone;
  }
  
  @Override
  public double getQuick(int row, int column) {
    return values[row][column];
  }
  
  @Override
  public Matrix like() {
    return like(rowSize(), columnSize());
  }
  
  @Override
  public Matrix like(int rows, int columns) {
    return new DenseMatrix(rows, columns);
  }
  
  @Override
  public void setQuick(int row, int column, double value) {
    values[row][column] = value;
  }

  @Override
  public Matrix viewPart(int[] offset, int[] size) {
    int rowOffset = offset[ROW];
    int rowsRequested = size[ROW];
    int columnOffset = offset[COL];
    int columnsRequested = size[COL];

    return viewPart(rowOffset, rowsRequested, columnOffset, columnsRequested);
  }

  @Override
  public Matrix viewPart(int rowOffset, int rowsRequested, int columnOffset, int columnsRequested) {
    if (rowOffset < 0) {
      throw new IndexException(rowOffset, rowSize());
    }
    if (rowOffset + rowsRequested > rowSize()) {
      throw new IndexException(rowOffset + rowsRequested, rowSize());
    }
    if (columnOffset < 0) {
      throw new IndexException(columnOffset, columnSize());
    }
    if (columnOffset + columnsRequested > columnSize()) {
      throw new IndexException(columnOffset + columnsRequested, columnSize());
    }
    return new MatrixView(this, new int[]{rowOffset, columnOffset}, new int[]{rowsRequested, columnsRequested});
  }

  @Override
  public Matrix assign(double value) {
    for (int row = 0; row < rowSize(); row++) {
      Arrays.fill(values[row], value);
    }
    return this;
  }
  
  public Matrix assign(DenseMatrix matrix) {
    // make sure the data field has the correct length
    if (matrix.values[0].length != this.values[0].length || matrix.values.length != this.values.length) {
      this.values = new double[matrix.values.length][matrix.values[0].length];
    }
    // now copy the values
    for (int i = 0; i < this.values.length; i++) {
      System.arraycopy(matrix.values[i], 0, this.values[i], 0, this.values[0].length);
    }
    return this;
  }
  
  @Override
  public Matrix assignColumn(int column, Vector other) {
    if (rowSize() != other.size()) {
      throw new CardinalityException(rowSize(), other.size());
    }
    if (column < 0 || column >= columnSize()) {
      throw new IndexException(column, columnSize());
    }
    for (int row = 0; row < rowSize(); row++) {
      values[row][column] = other.getQuick(row);
    }
    return this;
  }
  
  @Override
  public Matrix assignRow(int row, Vector other) {
    if (columnSize() != other.size()) {
      throw new CardinalityException(columnSize(), other.size());
    }
    if (row < 0 || row >= rowSize()) {
      throw new IndexException(row, rowSize());
    }
    for (int col = 0; col < columnSize(); col++) {
      values[row][col] = other.getQuick(col);
    }
    return this;
  }
  
  @Override
  public Vector viewRow(int row) {
    if (row < 0 || row >= rowSize()) {
      throw new IndexException(row, rowSize());
    }
    return new DenseVector(values[row], true);
  }

  @Override
  public MatrixFlavor getFlavor() {
    return MatrixFlavor.DENSELIKE;
  }
}
