let range = (n: number) => {
  return [...Array(n).keys()]
}
export class BaseN {
  private _regex = /^[a-z0-9]+$/i
  private _base_regex: RegExp
  private _symbols_string: string
  private _base: number
  private _symbols: Array<string>
  private _numbers: Array<number>
  private _symbol_number = {}
  private _number_symbol = {}

  constructor(symbols: Array<string> | string) {
    if (typeof symbols === 'string') {
      symbols = symbols.split('')
    }
    this._symbols_string = symbols.join('')
    if (!this.is_valid_symbols()) throw new Error('Invalid symbols')
    this._base = symbols.length
    this._symbols = symbols
    this._base_regex = new RegExp(`^[${this._symbols_string}]+$`)
    this._numbers = range(this._base)
    this._init()
  }

  private _init(): void {
    for (let idx = 0; idx < this._base; idx++) {
      let num = this._numbers[idx]
      let symbol = this._symbols[idx]
      this._symbol_number[symbol] = num
      this._number_symbol[num] = symbol
    }
  }

  private is_valid_symbols(): boolean {
    let regex_test = this._regex.test(this._symbols_string)
    if (!regex_test) return regex_test
    let duplicate_test = !this.has_duplicate()
    return duplicate_test
  }

  private has_duplicate(): boolean {
    let charset = {}
    for (let char of this._symbols_string) {
      if (charset[char] === 1) return true
      charset[char] = 1
    }
    return false
  }

  encode(n: number): string {
    n = parseInt(n + '')
    let result = ''
    while (n > 0) {
      let rem = n % this._base
      result = this._number_symbol[rem] + result
      n = parseInt((n / this._base) + '')
    }
    if (result.length < 1) {
      result = this._number_symbol[0]
    }
    return result
  }

  decode(s: string): number {
    if (!this._base_regex.test(s)) throw new Error('Invalid symbols exist')
    s = s.trim()
    let result = 0
    let pow = s.length - 1
    for (let symbol of s) {
      result += Math.pow(this._base, pow) * this._symbol_number[symbol]
      pow -= 1
    }
    return result
  }

  add(n1: string, n2: string) {
    if (!this._base_regex.test(n1) || !this._base_regex.test(n2)) throw new Error('Invalid symbols exist')
    let n1_array = Array.from(n1)
    let n2_array = Array.from(n2)
    let result = ''
    let sum = ''
    let carry = ''
    while (n1_array.length > 0 || n2_array.length > 0) {
      [sum, carry] = this._add(n1_array.pop(), n2_array.pop(), carry)
      result = sum + result
    }
    if (this._symbol_number[carry] !== 0) result = carry + result
    result = this.remove_left_zeros(result)
    return result
  }

  private _add(n1: string, n2: string, carry: string): Array<string> {

    let n1_value = this._symbol_number[n1] || 0
    let n2_value = this._symbol_number[n2] || 0
    let carry_value = this._symbol_number[carry] || 0

    let sum = n1_value + n2_value + carry_value
    carry_value = parseInt((sum / this._base) + '')
    let result = sum % this._base
    return [this._number_symbol[result], this._number_symbol[carry_value]]
  }

  subtract(s1: string, s2: string, abs = true): string {
    if (!this._base_regex.test(s1) || !this._base_regex.test(s2)) throw new Error('Invalid symbols exist')

    let compare_value = this.compare(s1, s2)
    if (compare_value === -1) {
      [s1, s2] = [s2, s1]
    }
    let result = ''
    let borrow = ''
    let s1_array = Array.from(s1)
    let s2_array = Array.from(s2)
    let value = ''
    while (s1_array.length > 0 || s2_array.length > 0) {
      [value, borrow] = this._subtract(s1_array.pop(), s2_array.pop(), borrow)
      if (s1.length > 1 && s1_array.length === 0 && this._symbol_number[value] === 0)
        continue
      result = value + result
    }
    if (!abs) {
      let prefix = compare_value === -1 ? '-' : ''
      return prefix + result
    }
    result = this.remove_left_zeros(result)
    return result
  }

  private _subtract(s1: string, s2: string, borrow: string): Array<string> {
    let s1_value = this._symbol_number[s1] || 0
    let s2_value = this._symbol_number[s2] || 0
    let borrow_value = this._symbol_number[borrow] || 0
    let value = s1_value - s2_value - borrow_value
    if (value < 0) {
      borrow_value = 1
      value += this._base
    }
    let result = this._number_symbol[value]
    borrow = this._number_symbol[borrow_value]
    return [result, borrow]
  }

  compare(s1: string, s2: string): number {
    if (!this._base_regex.test(s1) || !this._base_regex.test(s2)) throw new Error('Invalid symbols exist')
    s1 = this.remove_left_zeros(s1)
    s2 = this.remove_left_zeros(s2)
    if (s1.length > s2.length) return 1
    else if (s1.length < s2.length) return -1
    let max_len = s1.length
    for (let idx = 0; idx < max_len; idx++) {
      let s1_char = s1[idx]
      let s2_char = s2[idx]
      let s1_char_value = this._symbol_number[s1_char] || 0
      let s2_char_value = this._symbol_number[s2_char] || 0
      if (s1_char_value === s2_char_value) {
        continue
      } else if (s1_char_value < s2_char_value) {
        return -1
      } else {
        return 1
      }
    }
    return 0
  }

  private remove_left_zeros(s: string): string {
    let zero_char = this._number_symbol[0]
    let idx = 0
    for (; idx < s.length && s[idx] === zero_char; idx++);
    let result = s.slice(idx)
    if(result.length < 1){
      result = this._number_symbol[0]
    }
    return result
  }

}