package com.github.mygreen.supercsv.cellprocessor.format;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 書式がない数値のフォーマッタ。
 *
 * @since 2.0
 * @author T.TSUCHIE
 *
 */
public class SimpleNumberFormatter<T extends Number> extends AbstractTextFormatter<T> {
    
    private final Class<T> type;
    
    private final boolean lenient;
    
    private final MathContext mathContext;
    
    /**
     * デフォルトコンストラクタ
     * @param type 数値のクラスタイプ
     * @param lenient 曖昧にパースするかどうか。
     * @throws NullPointerException {@literal type is null.}
     * 
     */
    public SimpleNumberFormatter(final Class<T> type, final boolean lenient) {
        this(type, lenient, null);
        
    }
    
    /**
     * デフォルトコンストラクタ
     * @param type 数値のクラスタイプ
     * @param lenient 曖昧にパースするかどうか。
     * @param mathContext 丸めの方法を指定します。nullを渡すと省略できます。
     * @throws NullPointerException {@literal type is null.}
     * 
     */
    public SimpleNumberFormatter(final Class<T> type, final boolean lenient, final MathContext mathContext) {
        Objects.requireNonNull(type);
        
        this.type = type;
        this.lenient = lenient;
        this.mathContext = mathContext;
        
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public T parse(final String text) {
        
        try {
            final BigDecimal number = mathContext == null ? new BigDecimal(text) : new BigDecimal(text, mathContext);
            return (T) parseFromBigDecimal(type, number);
            
        } catch(NumberFormatException | ArithmeticException e) {
            throw new TextParseException(text, type, e);
        }
    }
    
    private Number parseFromBigDecimal(final Class<? extends Number> type, final BigDecimal number) {
        
        if(Byte.class.isAssignableFrom(type) || byte.class.isAssignableFrom(type)) {
            return lenient ? number.byteValue() : number.byteValueExact();
            
        } else if(Short.class.isAssignableFrom(type) || short.class.isAssignableFrom(type)) {
            return lenient ? number.shortValue() : number.shortValueExact();
            
        } else if(Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
            return lenient ? number.intValue() : number.intValueExact();
            
        } else if(Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type)) {
            return lenient ? number.longValue() : number.longValueExact();
            
        } else if(Float.class.isAssignableFrom(type) || float.class.isAssignableFrom(type)) {
            return number.floatValue();
            
        } else if(Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type)) {
            return number.doubleValue();
            
        } else if(type.isAssignableFrom(BigInteger.class)) {
            return lenient ? number.toBigInteger() : number.toBigIntegerExact();
            
        } else if(type.isAssignableFrom(BigDecimal.class)) {
            return number;
            
        }
        
        throw new IllegalArgumentException(String.format("Not support class type : %s", type.getCanonicalName()));
        
    }
    
    @Override
    public String print(final T object) {
        
        if(mathContext != null) {
            return printNumber(object);
        }
        
        return object.toString();
    }
    
    private String printNumber(final Object object) {
        
        if(Byte.class.isAssignableFrom(type) || byte.class.isAssignableFrom(type)) {
            return new BigDecimal((byte)object, mathContext).toPlainString();
            
        } else if(Short.class.isAssignableFrom(type) || short.class.isAssignableFrom(type)) {
            return new BigDecimal((short)object, mathContext).toPlainString();
            
        } else if(Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
            return new BigDecimal((int)object, mathContext).toPlainString();
            
        } else if(Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type)) {
            return new BigDecimal((long)object, mathContext).toPlainString();
            
        } else if(Float.class.isAssignableFrom(type) || float.class.isAssignableFrom(type)) {
            return new BigDecimal((float)object, mathContext).toPlainString();
            
        } else if(Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type)) {
            return new BigDecimal((double)object, mathContext).toPlainString();
            
        } else if(BigInteger.class.isAssignableFrom(type)) {
            return new BigDecimal((BigInteger)object, mathContext).toPlainString();
            
        } else {
            return object.toString();
        }
    }
    
    /**
     * パースする際に、数値に変換可能な部分のみ変換するかどうか。
     * <p>例えば、trueのときは、{@literal 123abc} をパースする際に{@literal 123}を数値としてパースします。
     *   <br>falseの場合は、例外{@link TextParseException}をスローします。
     * </p>
     * @return trueの場合、曖昧にパースします。
     */
    public boolean isLenient() {
        return lenient;
    }
    
    /**
     * パースする際の数値の丸め方法の指定情報を取得します。
     * @return 数値の丸め方法
     */
    public MathContext getMathContext() {
        return mathContext;
    }
    
    @Override
    public Map<String, Object> getMessageVariables() {
        
        final Map<String, Object> vars = new HashMap<>();
        
        if(mathContext != null) {
            vars.put("precision", mathContext.getPrecision());
        }
        
        return vars;
    }
    
}