package online.model;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import core.config.Factory;
import core.exception.LogicalException;

/**
 * バンド
 *
 * @author Tadashi Nakayama
 */
public final class Band {
	/** 汎用モデル */
	private final UniModel model;
	/** 現在位置 */
	private final int index;
	/** 全サイズ */
	private final int size;

	/**
	 * コンストラクタ
	 * @param um 汎用モデル
	 * @param loc 位置
	 * @param len 長さ
	 */
	Band(final UniModel um, final int loc, final int len) {
		this.model = um;
		this.index = loc;
		this.size = len;
	}

	/**
	 * 次存在確認
	 * @return 次が存在する場合 true を返す。
	 */
	public boolean hasNext() {
		return this.index + 1 < this.size;
	}

	/**
	 * 値存在確認
	 * @param key 項目名
	 * @return 存在する場合 true を返す。
	 */
	public boolean hasValue(final String key) {
		return this.index < this.model.getArraySize(key);
	}

	/**
	 * 現在位置
	 * @return 現在位置
	 */
	public int index() {
		return this.index;
	}

	/**
	 * 文字列取得
	 * @param key 項目名
	 * @return 文字列
	 */
	public String string(final String key) {
		checkIndex(key);
		return ModelUtil.getValueAsString(this.model, key, this.index);
	}

	/**
	 * 数値取得
	 * @param <T> Type
	 * @param key 項目名
	 * @return 数値
	 */
	public <T extends Number> T number(final String key) {
		checkIndex(key);
		return Factory.cast(this.model.getNumberArray(key)[this.index]);
	}

	/**
	 * 日時取得
	 * @param <T> type
	 * @param key 項目名
	 * @return 日時
	 */
	public <T extends Date> T date(final String key) {
		checkIndex(key);
		return Factory.cast(this.model.getDateArray(key)[this.index]);
	}

	/**
	 * 真偽値取得
	 * @param key 項目名
	 * @return 真偽値
	 */
	public Boolean bool(final String key) {
		checkIndex(key);
		return this.model.getBooleanArray(key)[this.index];
	}

	/**
	 * キーが含まれているかを返す。
	 *
	 * @param key キー
	 * @return boolean
	 */
	public boolean containsKey(final String key) {
		return this.model.containsKey(key);
	}

	/**
	 *  BeanにBandの値をセットします。
	 * @param bean 設定Bean
	 */
	public void setValueTo(final Object bean) {
		if (bean != null) {
			for (final Method mt : bean.getClass().getMethods()) {
				if (mt.getName().startsWith("set") && !Modifier.isStatic(mt.getModifiers())) {
					String mname = Factory.toItemName(mt);
					if (containsKey(mname) && ModelUtil.isTarget(mt.getParameterTypes())) {
						ModelUtil.setValue(bean, mt,
										getValue(mname), getValueClass(mname), index());
					}
				}
			}
		}
	}

	/**
	 * 値取得
	 * @param key キー
	 * @return 値
	 */
	private Object getValue(final String key) {
		return this.model.toMap().get(key);
	}

	/**
	 * 値クラス取得
	 * @param <T> Type
	 * @param key キー
	 * @return クラス
	 */
	private <T> Class<T> getValueClass(final String key) {
		return this.model.getValueClass(key);
	}

	/**
	 * 値設定
	 * @param key 項目名
	 * @param val 値
	 */
	public void set(final String key, final String val) {
		checkIndex(key);
		String[] vals = this.model.getStringArray(key);
		vals[this.index] = val;
	}

	/**
	 * 値設定
	 * @param key 項目名
	 * @param val 値
	 */
	public void set(final String key, final Number val) {
		checkIndex(key);
		Number[] vals = this.model.getNumberArray(key);
		vals[this.index] = val;
	}

	/**
	 * 値設定
	 * @param key 項目名
	 * @param val 値
	 */
	public void set(final String key, final Date val) {
		checkIndex(key);
		Date[] vals = this.model.getDateArray(key);
		vals[this.index] = val;
	}

	/**
	 * 値設定
	 * @param key 項目名
	 * @param val 値
	 */
	public void set(final String key, final Boolean val) {
		checkIndex(key);
		Boolean[] vals = this.model.getBooleanArray(key);
		vals[this.index] = val;
	}

	/**
	 * 配列長確認
	 * @param key 項目名
	 */
	private void checkIndex(final String key) {
		if (this.model.getArraySize(key) <= this.index) {
			throw new LogicalException("Invalid Index.[size of "
					+ key + ":" + this.model.getArraySize(key) + " index:" + this.index + "]");
		}
	}

	/**
	 * Stream取得
	 * @param um 汎用モデル
	 * @param leader 項目名
	 * @return Stream
	 */
	public static Stream<Band> stream(final UniModel um, final String leader) {
		return StreamSupport.stream(Band.iterable(um, leader).spliterator(), false);
	}

	/**
	 * Iterable取得
	 * @param um 汎用モデル
	 * @param leader 項目名
	 * @return BandIterable
	 */
	public static Iterable<Band> iterable(final UniModel um, final String leader) {
		return new Iterable<Band>() {
			/**
			 * @see java.lang.Iterable#iterator()
			 */
			@Override
			public Iterator<Band> iterator() {
				return new InnerIterator<Band>(um, um.getArraySize(leader)) {
					@Override
					Band getItem(final UniModel m, final int loc, final int len) {
						return new Band(m, loc, len);
					}
				};
			}
		};
	}

	/**
	 * 内部Iterator
	 * @author Tadashi Nakayama
	 * @param <E> Type
	 */
	private abstract static class InnerIterator<E> implements ListIterator<E> {
		/** 現在位置 */
		private int location = 0;
		/** 長さ */
		private final int length;
		/** BandMap */
		private final UniModel model;

		/**
		 * コンストラクタ
		 * @param um UniModel
		 * @param len 長さ
		 */
		InnerIterator(final UniModel um, final int len) {
			this.model = um;
			this.length = len;
		}

		/**
		 * @see java.util.Iterator#hasNext()
		 */
		@Override
		public boolean hasNext() {
			return 0 <= this.location && this.location < this.length;
		}

		/**
		 * @see java.util.Iterator#next()
		 */
		@Override
		public E next() {
			if (this.length <= this.location) {
				throw new NoSuchElementException();
			}
			this.location++;
			return getItem(this.model, this.location, this.length);
		}

		/**
		 * @see java.util.ListIterator#nextIndex()
		 */
		@Override
		public int nextIndex() {
			return this.location;
		}

		/**
		 * @see java.util.ListIterator#hasPrevious()
		 */
		@Override
		public boolean hasPrevious() {
			return 0 < this.location;
		}

		/**
		 * @see java.util.ListIterator#previous()
		 */
		@Override
		public E previous() {
			if (this.location <= 0) {
				throw new NoSuchElementException();
			}
			this.location--;
			return getItem(this.model, this.location, this.length);
		}

		/**
		 * @see java.util.ListIterator#previousIndex()
		 */
		@Override
		public int previousIndex() {
			return this.location - 1;
		}

		/**
		 * アイテム取得
		 * @param um UniModel
		 * @param loc 位置
		 * @param len 長さ
		 * @return アイテム
		 */
		abstract E getItem(final UniModel um, final int loc, final int len);

		/**
		 * @see java.util.Iterator#remove()
		 */
		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		/**
		 * @see java.util.ListIterator#set(java.lang.Object)
		 */
		@Override
		public void set(final E e) {
			throw new UnsupportedOperationException();
		}

		/**
		 * @see java.util.ListIterator#add(java.lang.Object)
		 */
		@Override
		public void add(final E e) {
			throw new UnsupportedOperationException();
		}
	}
}
