package com.gh.mygreen.xlsmapper.util;

import java.awt.Point;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.util.CellReference;

/**
 * 単一のセルのアドレスを表現するクラス。
 * <p>{@link java.awt.Point}だと、行、列の表現がわかりづらくmutableなので、、その代わりに使用する。
 * <p>{@link CellReference}との違いは、セルのアドレスの絶対一致を表現するためのもの。
 * <p>POIに同じ用途のクラス{@link CellAddress}が存在するが、
 *    こちらは{@link Serializable}や{@link Cloneable}が実装されておらず、使い勝手が悪い。</p>
 *
 * @since 1.4
 * @author T.TSUCHIE
 *
 */
public class CellPosition implements Serializable, Comparable<CellPosition>, Cloneable {

    /** serialVersionUID */
    private static final long serialVersionUID = 8579701754512731611L;

    /**
     * シート上の先頭の位置を表現するための定数。
     */
    public static final CellPosition A1 = new CellPosition(0, 0);

    private final int row;

    private final int column;

    private final String toStringText;

    /**
     * CellAddressのインスタンスを作成する。
     *
     * @param row 行番号 (0から始まる)
     * @param column 列番号 (0から始まる)
     * @throws IllegalArgumentException {@literal row < 0 || column < 0}
     */
    private CellPosition(int row, int column) {
        ArgUtils.notMin(row, 0, "row");
        ArgUtils.notMin(column, 0, "column");
        this.row = row;
        this.column = column;
        this.toStringText = CellReference.convertNumToColString(column) + (row + 1);
    }

    /**
     * CellAddressのインスタンスを作成する。
     *
     * @param row 行番号 (0から始まる)
     * @param column 列番号 (0から始まる)
     * @return {@link CellPosition}のインスタンス
     * @throws IllegalArgumentException {@literal row < 0 || column < 0}
     */
    public static CellPosition of(int row, int column) {
        ArgUtils.notMin(row, 0, "row");
        ArgUtils.notMin(column, 0, "column");

        return new CellPosition(row, column);
    }

    /**
     * CellAddressのインスタンスを作成する。
     * @param cell セルのインスタンス。
     * @return {@link CellPosition}のインスタンス
     * @throws IllegalArgumentException {@literal cell == null.}
     */
    public static CellPosition of(final Cell cell) {
        ArgUtils.notNull(cell, "cell");

        return of(cell.getRowIndex(), cell.getColumnIndex());
    }

    /**
     * CellAddressのインスタンスを作成する。
     * @param reference セルの参照形式。
     * @return {@link CellPosition}のインスタンス
     * @throws IllegalArgumentException {@literal reference == null.}
     */
    public static CellPosition of(final CellReference reference) {
        ArgUtils.notNull(reference, "reference");

        return of(reference.getRow(), reference.getCol());
    }

    /**
     * CellAddressのインスタンスを作成する。
     * @param address セルのアドレス
     * @return {@link CellPosition}のインスタンス
     * @throws IllegalArgumentException {@literal address == null.}
     */
    public static CellPosition of(final CellAddress address) {
        ArgUtils.notNull(address, "address");

        return of(address.getRow(), address.getColumn());
    }

    /**
     * CellAddressのインスタンスを作成する。
     * @param address 'A1'の形式のセルのアドレス
     * @return {@link CellPosition}のインスタンス
     * @throws IllegalArgumentException {@literal address == null || address.length() == 0 || アドレスの書式として不正}
     */
    public static CellPosition of(final String address) {
        ArgUtils.notEmpty(address, "address");

        if(!matchedCellAddress(address)) {
            throw new IllegalArgumentException(address + " is wrong cell address pattern.");
        }

        return of(new CellReference(address));
    }

    private static final Pattern PATTERN_CELL_ADREESS = Pattern.compile("^([a-zA-Z]+)([0-9]+)$");

    private static boolean matchedCellAddress(final String address) {
        final Matcher matcher = PATTERN_CELL_ADREESS.matcher(address);
        return matcher.matches();
    }

    /**
     * CellAddressのインスタンスを作成する。
     * @param point セルの座標
     * @return {@link CellPosition}のインスタンス
     * @throws IllegalArgumentException {@literal point == null.}
     */
    public static CellPosition of(final Point point) {
        ArgUtils.notNull(point, "point");
        return of(point.y, point.x);
    }

    @Override
    public int compareTo(final CellPosition other) {
        ArgUtils.notNull(other, "other");

        int r = this.row - other.row;
        if (r != 0) {
            return r;
        }

        int c = this.column - other.column;
        if (c != 0) {
            return c;
        }

        return 0;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + column;
        result = prime * result + row;
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if(this == obj) {
            return true;
        }
        if(obj == null) {
            return false;
        }
        if(!(obj instanceof CellPosition)) {
            return false;
        }
        CellPosition other = (CellPosition) obj;
        if(column != other.column) {
            return false;
        }
        if(row != other.row) {
            return false;
        }
        return true;
    }

    @Override
    public CellPosition clone() {
        return new CellPosition(row, column);
    }

    /**
     * 行番号を取得する。
     * @return 行番号 (0から始まる)
     */
    public int getRow() {
        return row;
    }

    /**
     * 列番号を取得する。
     * @return 列番号 (0から始まる)
     */
    public int getColumn() {
        return column;
    }

    /**
     * {@link Point}の形式に変換します。
     * @return {@link Point}のインスタンス。
     *          x座標が列番号(column index)、y座標が行番号(row index)なります。
     */
    public Point toPoint() {
        return new Point(column, row);
    }

    /**
     * POIの{@link CellAddress}の形式に変換します。
     * @return {@link CellAddress}のインスタンス。
     */
    public CellAddress toCellAddress() {
        return new CellAddress(row, column);
    }

    /**
     * セルのアドレスを取得する。
     * @return 'A1'の形式で、セルノアドレスを文字列として表現する。
     */
    public String formatAsString() {
        return toStringText;
    }

    /**
     * 行番号に指定した値を加算する。
     * @param value 加算する値
     * @return 加算したインスタンス
     */
    public CellPosition addRow(int value) {
        return new CellPosition(row + value, column);
    }

    /**
     * 列番号に指定した値を加算する。
     * @param value 加算する値
     * @return 加算したインスタンス
     */
    public CellPosition addColumn(int value) {
        return new CellPosition(row, column + value);
    }

    /**
     * {@link #formatAsString()}の値を返します。
     */
    @Override
    public String toString() {
        return formatAsString();
    }

}