

import {Type, IType, ITypeParams} from "./Type";
import {isNaN, invalidValuesAsString} from "../utils";
import {
    ConflictFloorCeilRoundError,
    ConflictNullAndZeroParameterError,
    InvalidNumberError,
    InvalidRoundError
} from "../errors";

export interface INumberTypeParams extends ITypeParams {
    nullAsZero?: boolean;
    zeroAsNull?: boolean;
    ceil?: number;
    round?: number;
    floor?: number;
    prepare?: (value: any, key: string, model: any) => number;
    validate?: 
        ((value: number, key: string) => boolean) |
        RegExp
    ;
    enum?: number[];
    default?: number | (() => number);
}

export interface INumberType extends IType {
    (params: INumberTypeParams): INumberType;
    TOutput: number;
    TInput: number;
    TJson: number;
}

export class NumberType extends Type {
    nullAsZero: boolean;
    zeroAsNull: boolean;
    ceil?: number;
    round?: number;
    floor?: number;

    constructor({
        nullAsZero = false,
        zeroAsNull = false,
        ceil,
        round,
        floor,
        ...otherParams
    }: INumberTypeParams) {
        super(otherParams);

        
        this.nullAsZero = nullAsZero;
        this.zeroAsNull = zeroAsNull;

        if ( nullAsZero && zeroAsNull ) {
            throw new ConflictNullAndZeroParameterError({});
        }

        if ( ceil != null ) {
            if ( isNaN(+ceil) ) {
                throw new InvalidRoundError({
                    roundType: "ceil",
                    invalidValue: invalidValuesAsString(ceil)
                });
            }
            ceil = +ceil;
        }
        this.ceil = ceil;

        if ( round != null ) {
            if ( isNaN(+round) ) {
                throw new InvalidRoundError({
                    roundType: "round",
                    invalidValue: invalidValuesAsString(round)
                });
            }

            if ( ceil != null ) {
                throw new ConflictFloorCeilRoundError({});
            }

            round = +round;
        }
        this.round = round;

        if ( floor != null ) {
            if ( isNaN(+floor) ) {
                throw new InvalidRoundError({
                    roundType: "floor",
                    invalidValue: invalidValuesAsString(floor)
                });
            }

            if ( ceil != null ) {
                throw new ConflictFloorCeilRoundError({});
            }
            if ( round != null ) {
                throw new ConflictFloorCeilRoundError({});
            }
            
            floor = +floor;
        }
        this.floor = floor;
    }

    prepare(originalValue: any,  key: string) {
        if ( originalValue == null ) {
            if ( this.nullAsZero ) {
                return 0;
            }
            return null;
        }
    
    
        let value: number | null = +originalValue;
    
        if ( 
            isNaN(value) ||
            typeof originalValue === "boolean" ||
            originalValue instanceof RegExp ||
            originalValue instanceof Date ||
            Array.isArray(originalValue) ||
            // infinity
            value === 1 / 0 ||
            value === -1 / 0
        ) {
            const valueAsString = invalidValuesAsString( originalValue );
    
            throw new InvalidNumberError({
                key,
                invalidValue: valueAsString
            });
        }
    
        
        if ( this.round != null ) {
            const desc = Math.pow( 10, this.round );
    
            value = Math.round(  
                value * 
                desc
            ) / desc;
        }
    
        else if ( this.floor != null ) {
            const desc = Math.pow( 10, this.floor );
    
            value = Math.floor(
                value * 
                desc
            ) / desc;
        }
    
        else if ( this.ceil != null ) {
            // 1.12 * 100 == 112.00000000000001

            let valueStr = value.toString();
            let commaPosition = valueStr.indexOf(".");
            
            if ( commaPosition !== -1 ) {
                commaPosition += this.ceil;
                valueStr = valueStr.replace(".", "");
                valueStr = (
                    valueStr.slice(0, commaPosition) + 
                    "." +
                    valueStr.slice(commaPosition)
                );

                value = +valueStr;
                
                const desc = Math.pow( 10, this.ceil );
                value = Math.ceil(value) / desc;
            }
        }
    
    
        if ( this.zeroAsNull ) {
            if ( value === 0 ) {
                value = null;
            }
        }
    
        return value;
    }
}
