/**
 * Column-major map based storage sparse matrix
 *
 * @author liuyueming
 */
public class SparseMatrixColMajor implements SparseMatrix {
  protected int rowDim;
  protected int colDim;
  protected double defaultValue = 0.0;
  protected Map<Integer, Map<Integer, Double>> m = new HashMap<Integer, Map<Integer, Double>>();
  protected String name = this.getClass().getSimpleName() + Sequence.getInstance().nextSeq();

  public SparseMatrixColMajor() {}

  public SparseMatrixColMajor(String name) {
    this.name = name;
  }

  public SparseMatrixColMajor(int rowDim, int colDim) {
    this.rowDim = rowDim;
    this.colDim = colDim;
  }

  public SparseMatrixColMajor(String name, int rowDim, int colDim) {
    this.name = name;
    this.rowDim = rowDim;
    this.colDim = colDim;
  }

  public SparseMatrixColMajor(int rowDim, int colDim, double defaultValue) {
    this.rowDim = rowDim;
    this.colDim = colDim;
    this.defaultValue = defaultValue;
  }

  public SparseMatrixColMajor(String name, int rowDim, int colDim, double defaultValue) {
    this.name = name;
    this.rowDim = rowDim;
    this.colDim = colDim;
    this.defaultValue = defaultValue;
  }

  @Override
  public void setColDim(int nColDim) {
    this.colDim = nColDim;
  }

  @Override
  public void setRowDim(int nRowDim) {
    this.rowDim = nRowDim;
  }

  @Override
  public int getRowDim() {
    return rowDim;
  }

  @Override
  public int getColDim() {
    return colDim;
  }

  @Override
  public void set(int row, int col, double value) {
    Map<Integer, Double> aCol = m.get(col);
    if (aCol == null) {
      if (Math.abs(value) >= Matrix.zeroEps) {
        aCol = new HashMap<Integer, Double>();
        m.put(col, aCol);
        aCol.put(row, value);
      }
    } else {
      if (Math.abs(value) < Matrix.zeroEps) aCol.remove(row);
      else aCol.put(row, value);
    }
  }

  @Override
  public double get(int row, int col) {
    Map<Integer, Double> aCol = m.get(col);
    if (aCol == null) {
      return 0.0;
    } else {
      Double v = aCol.get(row);
      if (v == null) {
        return 0.0;
      } else {
        return v;
      }
    }
  }

  @Override
  public void add(int row, int col, double value) {
    set(row, col, get(row, col) + value);
  }

  @Override
  public Map<Integer, Map<Integer, Double>> getAll() {
    return m;
  }

  @Override
  public void setAll(int nRowBase, int nColBase, Map<Integer, Map<Integer, Double>> map) {
    for (Entry<Integer, Map<Integer, Double>> rowEentry : map.entrySet()) {
      int nCol = rowEentry.getKey();
      Map<Integer, Double> col = rowEentry.getValue();
      for (Entry<Integer, Double> entry : col.entrySet()) {
        int nRow = entry.getKey();
        set(nRowBase + nRow, nColBase + nCol, entry.getValue());
      }
    }
  }

  @Override
  public void clearAll() {
    this.rowDim = 0;
    this.colDim = 0;
    this.defaultValue = 0.0;
    // this.name = null;
    for (Entry<Integer, Map<Integer, Double>> row : m.entrySet()) {
      row.getValue().clear();
    }
    this.m.clear();
  }

  @Override
  public void clearData() {
    for (Entry<Integer, Map<Integer, Double>> row : m.entrySet()) {
      row.getValue().clear();
    }
    this.m.clear();
  }

  @Override
  public void mult(Vector x, Vector y) {
    throw new UnsupportedOperationException();
  }

  @Override
  public SparseMatrix trans() {
    Map<Integer, Map<Integer, Double>> m2 = m;
    m = new HashMap<Integer, Map<Integer, Double>>();
    int dim = this.colDim;
    this.colDim = this.rowDim;
    this.rowDim = dim;
    for (Entry<Integer, Map<Integer, Double>> col : m2.entrySet()) {
      int nCol = col.getKey();
      for (Entry<Integer, Double> row : col.getValue().entrySet()) {
        int nRow = row.getKey();
        set(nCol, nRow, row.getValue());
      }
    }
    return this;
  }

  /**
   * An overriding method can also return a subtype of the type returned by the overridden method.
   * This is called a covariant return type.
   */
  @Override
  public SparseMatrixColMajor copy() {
    SparseMatrixColMajor newM = new SparseMatrixColMajor(this.rowDim, this.colDim);
    newM.setAll(0, 0, this.m);
    return newM;
  }

  @Override
  public void print() {
    for (int i = 1; i <= rowDim; i++) {
      for (int j = 1; j <= colDim; j++) {
        System.out.print(String.format("%8.6f   ", get(i, j)));
      }
      System.out.println();
    }
    System.out.println();
  }

  public String toString() {
    return "SparseMatrix:" + name + "(" + this.rowDim + "," + this.colDim + "):N0C=" + m.size();
  }

  ////////////////////////////////////////////////////

  /** 返回列压缩存储方式的列索引数组,列号从0开始 */
  public int[][] getRowIndex() {
    int[][] rowIndex = new int[m.size()][];
    // for(int c=0; c<this.colDim; c++) {
    for (int c = this.colDim; --c >= 0; ) {
      Map<Integer, Double> col = m.get(c + 1);
      rowIndex[c] = new int[col.size()];
      int r = 0;
      for (Entry<Integer, Double> row : col.entrySet()) {
        int nRow = row.getKey();
        rowIndex[c][r] = nRow - 1;
        r++;
      }
    }
    return rowIndex;
  }

  @Override
  public String getName() {
    return name;
  }

  @Override
  public SparseMatrix setName(String name) {
    this.name = name;
    return this;
  }

  public int getNonZeroNumber() {
    int rlt = 0;
    for (Entry<Integer, Map<Integer, Double>> e1 : m.entrySet()) {
      rlt += e1.getValue().size();
    }
    return rlt;
  }

  /**
   * Constructs and returns a new <tt>SparseVecotor</tt> view representing the row of the given
   * column. The returned view is backed by this matrix, so changes in the returned view are
   * reflected in this matrix, and vice-versa.
   *
   * @param row
   */
  public SparseVectorHashMap viewCol(int col) {
    SparseVectorHashMap rlt = new SparseVectorHashMap(this.rowDim, this.m.get(col), false);
    return rlt;
  }

  /**
   * Return A[:,col]*vec, where A==this, vec.length=A.getRowDim()
   *
   * @param vec
   * @return
   */
  public double multColumn(double[] vec, int col) {
    Map<Integer, Double> c = m.get(col);
    double rlt = 0.0;
    if (c != null) {
      for (Entry<Integer, Double> e : c.entrySet()) rlt += e.getValue() * vec[e.getKey() - 1];
    }
    return rlt;
  }

  /**
   * Return A[:,col]*vec, where A==this, vec.length=A.getRowDim()
   *
   * @param vec
   * @param vec Nonzero index list of vec, start form 1
   * @return
   */
  public double multColumn(double[] vec, List<Integer> nonzeroIndex, int col) {
    double rlt = 0.0;
    if (nonzeroIndex.size() == 0) return rlt;
    Map<Integer, Double> c = m.get(col);
    if (c != null) {
      for (Integer idx : nonzeroIndex) {
        Double v = c.get(idx);
        if (v != null) rlt += vec[idx - 1] * v;
      }
    }
    return rlt;
  }

  /**
   * Write this matrix to a file with Matlab mat file format. The variable name in matlab workspace
   * is specified by <tt>setName()</tt>. Default variable name is
   * <tt>"SparseMatrix"+UniqueSequenceNumber</tt>.
   *
   * <p>If more than one matrix need to be written in a single mat file use
   * <tt>MatlabMatFileWriter</tt> instead.
   *
   * @param fileName
   */
  public void writeMatFile(String fileName) {
    MatlabMatFileWriter w = new MatlabMatFileWriter();
    w.addSparseMatrix(this);
    w.writeFile(fileName);
  }

  @Override
  public Iterator<MatrixEntry> iterator() {
    return new SMIterator(this.m.entrySet().iterator());
  }

  /** Iterator over this sparse matrix. */
  class SMIterator implements Iterator<MatrixEntry> {
    /** Matrix cursor */
    Iterator<Entry<Integer, Map<Integer, Double>>> colIter;

    Iterator<Entry<Integer, Double>> rowIter;

    SMIterator(Iterator<Entry<Integer, Map<Integer, Double>>> iter) {
      colIter = iter;
      while (colIter.hasNext()) {
        Entry<Integer, Map<Integer, Double>> nextCol = colIter.next();
        rowIter = nextCol.getValue().entrySet().iterator();
        if (rowIter.hasNext()) {
          entry.col = nextCol;
          return;
        }
      }
    }

    /** Matrix entry */
    final SMEntry entry = new SMEntry();

    public boolean hasNext() {
      if (rowIter != null && rowIter.hasNext()) return true;
      else {
        while (colIter.hasNext()) {
          Entry<Integer, Map<Integer, Double>> nextCol = colIter.next();
          rowIter = nextCol.getValue().entrySet().iterator();
          if (rowIter.hasNext()) {
            entry.col = nextCol;
            return true;
          }
        }
      }
      return false;
    }

    public MatrixEntry next() {
      Entry<Integer, Double> ele = null;
      if (rowIter.hasNext()) {
        ele = rowIter.next();
        entry.eleInCol = ele;
      } else if (colIter.hasNext()) {
        Entry<Integer, Map<Integer, Double>> nextCol = colIter.next();
        entry.col = nextCol;

        rowIter = nextCol.getValue().entrySet().iterator();
        ele = rowIter.next();
        entry.eleInCol = ele;
      }
      return entry;
    }

    public void remove() {
      rowIter.remove();
    }
  }

  /** Matrix entry backed by the matrix. */
  class SMEntry implements MatrixEntry {

    private Entry<Integer, Map<Integer, Double>> col;
    private Entry<Integer, Double> eleInCol;

    @Override
    public int getRow() {
      return eleInCol.getKey();
    }

    @Override
    public int getCol() {
      return col.getKey();
    }

    @Override
    public double getValue() {
      return eleInCol.getValue();
    }

    @Override
    public void setValue(double value) {
      eleInCol.setValue(value);
    }
  }

  @Override
  public void writeSimpleFile(String fileName) {
    // TODO Auto-generated method stub

  }

  @Override
  public double apply(int row, int col) {
    return this.get(row, col);
  }

  @Override
  public void update(int row, int col, double value) {
    this.set(row, col, value);
  }
}