/*
 * ArrayListTable class.
 *
 * Copyright (C) 2007 SATOH Takayuki All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package ts.util.table;

import java.io.Serializable;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Arrays;
import java.util.Collections;
import java.util.NoSuchElementException;
import ts.util.table.Record.SimpleEntry;

/**
 * 配列のリストとして実装されたテーブル・クラス。
 * <br>
 * レコードを表すマップは値の集合を配列に格納し、カラム・キーの集合を
 * {@link ts.util.table.Header Header}オブジェクトによってレコード間で共用する。
 * <br>
 * この{@link ts.util.table.Header Header}オブジェクトは、カラム・キーと配列の
 * インデックスを対応付けるマップを内部に持ち、この配列のインデックスを介して
 * カラム・キーと配列に格納された値を対応付ける。
 * <br>
 * レコード・コレクションはリストで実装されているので、レコードの順番があり、
 * カラムの値が同じレコードを複数格納することができる。
 *
 * @param <C> カラム・キーのタイプ。
 * @param <V> カラムの値のタイプ。
 *
 * @author  佐藤隆之.
 * @version $Revision: 1.1.1.1 $, $Date: 2010-10-16 00:03:50 $
 */
public class ArrayListTable<C,V> extends AbstractTable<C,V>
{
  /** シリアル・バージョン番号。 */
  static final long serialVersionUID = 1940969047348687437L;

  /**
   * デフォルトコンストラクタ。
   */
  public ArrayListTable()
  {}

  /**
   * 初期レコード容量と初期カラム容量を引数にとるコンストラクタ。
   *
   * @param  initRecCapacity 初期レコード容量。
   * @param  initColCapacity 初期カラム容量。
   * @throws AssertionError 引数が負の値の場合（デバッグ・モードのみ）。
   */
  public ArrayListTable(int initRecCapacity, int initColCapacity)
  {
    super(initRecCapacity, initColCapacity);
  }

  /**
   * ヘッダを引数にとるコンストラクタ。
   *
   * @param  header ヘッダ・オブジェクト。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  protected ArrayListTable(Header<C> header)
  {
    super(header);
  }

  /**
   * {@inheritDoc}
   *
   * @throws AssertionError 引数が負の値の場合（デバッグ・モードのみ）。
   */
  @Override
  protected Header<C> createHeader(int initColCapacity)
  {
    return new ArrayListTableHeader(initColCapacity);
  }

  /**
   * {@inheritDoc}
   *
   * @throws AssertionError 引数が負の値の場合（デバッグ・モードのみ）。
   */
  @Override
  protected Collection<Record<C,V>> createRecordCollection(int initRecCapacity)
  {
    assert (initRecCapacity >= 0) : "@param:initRecCapacity is negative.";
    return new LinkedList<Record<C,V>>();
  }

  /**
   * レコード・オブジェクトを作成する。
   *
   * @param  initColCapacity 初期カラム容量。
   * @return レコード・オブジェクト。
   * @throws AssertionError 引数が負の値の場合（デバッグ・モードのみ）。
   */
  @Override
  protected Record<C,V> createRecord(int initColCapacity)
  {
    assert (initColCapacity >= 0) : "@param:initColCapacity is negative.";

    return new ArrayListTableRecord(initColCapacity);
  }

  /* -- inner class -- */

  /**
   * {@link ts.util.table.ArrayListTable ArrayListTable}クラスのヘッダ
   * を表すクラス。
   * <br>
   * カラム・キーと配列インデックスの対応付けを行うマップを保持し、
   * 値を配列に格納する {@link
   * ts.util.table.ArrayListTable.ArrayListTableRecord ArrayListTableRecord}
   * オブジェクトの共有のキー集合として使用される。
   */
  protected class ArrayListTableHeader implements Header<C>, Serializable
  {
    /** シリアル・バージョン番号。 */
    static final long serialVersionUID = -114778725908464738L;

    /** カラムと配列インデックスの組を格納するマップ。 */
    private HashMap<C,Integer> columnMap_ ;

    /** 現在使用されているインデックスの最大値。 */
    private int maxArrayIndex_ = -1;

    /**
     * 初期カラム容量を引数にとるコンストラクタ。
     *
     * @param  initColCapacity 初期カラム容量。
     * @throws AssertionError 引数が負の値の場合（デバッグ・モードのみ）。
     */
    public ArrayListTableHeader(int initColCapacity)
    {
      assert (initColCapacity >= 0) : "@param:initColCapacity is negative.";
      columnMap_ = new HashMap<C,Integer>(initColCapacity);
    }

    /**
     * このヘッダに格納されているカラム・キーの数を取得する。
     *
     * @return カラム・キーの数。
     */
    public int columnCount()
    {
      return columnMap_.size();
    }
  
    /**
     * このヘッダに格納されているカラム・キーを順に取得するためのイテレータを
     * 取得する。
     *
     * @return カラム・キーのイテレータ。
     */
    public Enumeration<C> columns()
    {
      return Collections.enumeration(columnMap_.keySet());
    }

    /**
     * 指定されたオブジェクトと同じカラム・キーがこのヘッダに格納されているか
     * どうかを確認する。
     *
     * @param  column カラム・キー。
     * @return 指定されたカラム・キーがこのオブジェクトに格納されている場合は
     *         <tt>true</tt>を返す。
     */
    public boolean hasColumn(Object column)
    {
      return columnMap_.containsKey(column);
    }
  
    /**
     * ヘッダにカラム・キーを追加する。
     *
     * @param  column カラム・キー。
     */
    public void addColumn(C column)
    {
      addArrayIndexForColumn(column);
    }

    /**
     * カラム・キーに対応付けられたインデックスを取得する。
     * <br>
     * このインデックスは、配列で表されるレコードでの、各カラムの値が格納される
     * 位置を指定するために使用される。
     *
     * @param  column カラム・キー。
     * @return カラム・キーに対応付けられたインデックス。
     *         指定されたカラム・キーがこのオブジェクトに格納されていない場合は
     *         -1を返す。
     */
    protected int getArrayIndexForColumn(Object column)
    {
      Integer v = columnMap_.get(column);
      return (v == null) ? -1 : v.intValue();
    }

    /**
     * ヘッダにカラム・キーを追加する。
     * <br>
     * 引数のカラム・キーとインデックスの組を、このオブジェクトが保持するマップ
     * に登録する。
     * インデックスには、現在使用されているインデックスの最大値+1が設定される。
     * 戻り値は、設定したインデックスを返す。
     * <br>
     * 引数のカラム・キーがヘッダに既に格納されていた場合は、追加せずに設定
     * されているインデックスを返す。
     *
     * @param  column カラム・キー。
     * @return カラム・キーのインデックス。
     */
    protected int addArrayIndexForColumn(C column)
    {
      Integer i = columnMap_.get(column);
      if (i != null) {
        return i.intValue();
      }

      maxArrayIndex_ ++;
      columnMap_.put(column, maxArrayIndex_);
      return maxArrayIndex_ ;
    }
  }

  /**
   * {@link ts.util.table.ArrayListTable ArrayListTable}クラスのレコードを表す
   * マップ・クラス。
   * <br>
   * カラム・キーと配列インデックスの対応付けを行うマップを保持する
   * {@link ts.util.table.ArrayListTable.ArrayListTableHeader 
   * ArrayListTableHeader} オブジェクトをこのマップのキー集合とし、
   * マップの値は配列に格納する。
   */
  protected class ArrayListTableRecord extends Record<C,V>
  {
    /** シリアル・バージョン番号。 */
    static final long serialVersionUID = 4663048964389775476L;

    /** 各カラムの値を格納する配列。 */
    private V[] values_ ;

    /**
     * 初期カラム容量を引数にとるコンストラクタ。
     *
     * @param  initColCapacity 初期カラム容量。
     * @throws AssertionError 引数が負の値の場合（デバッグ・モードのみ）。
     */
    public ArrayListTableRecord(int initColCapacity)
    {
      assert (initColCapacity >= 0) : "@param:size is negative.";

      values_ = newValue(initColCapacity);
    }

    /**
     * レコードの各カラムの値を格納するための配列を作成する。
     *
     * @param  initColCapacity 初期カラム容量。
     * @return レコードの各カラムの値を格納するための配列。
     */
    @SuppressWarnings("unchecked")
    private V[] newValue(int initColCapacity)
    {
      return (V[]) new Object[(initColCapacity < 0) ? 0 : initColCapacity];
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Header<C> header()
    {
      return ArrayListTable.this.header();
    }

    /**
     * {@link ts.util.table.ArrayListTable.ArrayListTableHeader
     * ArrayListTableHeader}オブジェクトを取得する。
     *
     * @return {@link ts.util.table.ArrayListTable.ArrayListTableHeader
     *         ArrayListTableHeader}オブジェクト。
     */
    @SuppressWarnings("unchecked")
    private ArrayListTableHeader altHeader()
    {
      return (ArrayListTableHeader) header();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsValue(Object value)
    {
      if (value == null) {
        if (size() > values_.length) {
          return true;
        }

        for (int i=0; i<size(); i++) {
          if (values_[i] == null) {
            return true;
          }
        }
      }
      else {
        int n = Math.min(size(), values_.length);
        for (int i=0; i<n; i++) {
          if (value.equals(values_[i])) {
            return true;
          }
        }
      }

      return false;
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    protected V getValue(Object column)
    {
      try {
        return values_[ altHeader().getArrayIndexForColumn(column) ];
      }
      catch (Exception e) {
        return null;
      }
    }
  
    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    protected V putValue(C column, V value)
    {
      int ind = altHeader().addArrayIndexForColumn(column);
      if (ind >= values_.length) {
        Object arr = new Object[ind + 10];
        System.arraycopy(values_, 0, arr, 0, values_.length);
        values_ = (V[]) arr;
      }
  
      V old = values_[ind];
      values_[ind] = value;
      return old;
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    protected V removeValue(Object column)
    {
      try {
        int ind = altHeader().getArrayIndexForColumn(column);
        V old = values_[ind];
        values_[ind] = null;
        return old;
      }
      catch (Exception e) {
        return null;
      }
    }
  
    /**
     * {@inheritDoc}
     */
    public void clear()
    {
      Arrays.fill(values_, null);
    }
    
    /**
     * {@inheritDoc}
     */
    public Collection<V> values()
    {
      return new AbstractCollection<V>() {
        public int size() {
          return header().columnCount();
        }
        public Iterator<V> iterator() {
          return new Iterator<V>() {
            private int index_ = -1;
  
            public boolean hasNext() {
              return (index_ + 1 < size());
            }
            public V next() {
              index_ ++;
              if (index_ >= size()) {
                throw new NoSuchElementException();
              }
              else if (index_ < values_.length) {
                return values_[index_];
              }
              else {
                return null;
              }
            }
            public void remove() {
              if (index_ < 0) {
                throw new IllegalStateException();
              }
              else if (index_ >= size()) {
                throw new NoSuchElementException();
              }
              else if (index_ < values_.length) {
                values_[index_] = null;
              }
            }
          };
        }
      };
    }
  
    /**
     * {@inheritDoc}
     */
    public Set<Map.Entry<C,V>> entrySet()
    {
      return new AbstractSet<Map.Entry<C,V>>() {
        public int size() {
          return header().columnCount();
        }
        public Iterator<Map.Entry<C,V>> iterator() {
          return new Iterator<Map.Entry<C,V>>() {
            private int index_ = -1;
            private Iterator<Map.Entry<C,Integer>> it =
              altHeader().columnMap_.entrySet().iterator();
    
            public boolean hasNext() {
              return it.hasNext();
            }
            public Map.Entry<C,V> next() {
              Map.Entry<C,Integer> ent = it.next();
              index_ = ent.getValue().intValue();
              if (0 <= index_ && index_ < values_.length) {
                return new SimpleEntry<C,V>(ent.getKey(), values_[index_]);
              }
              else {
                throw new NoSuchElementException();
              }
            }
            public void remove() {
              if (index_ < 0) {
                throw new IllegalStateException();
              }
              else if (index_ >= size()) {
                throw new NoSuchElementException();
              }
              else if (index_ < values_.length) {
                values_[index_] = null;
              }
            }
          };
        }
      };
    }
  }
}

