const escapeCharacter = '\\'
const alternationCharacter = '/'
const beginParameterCharacter = '{'
const endParameterCharacter = '}'
const beginOptionalCharacter = '('
const endOptionalCharacter = ')'

export function symbolOf(token: TokenType): string {
  switch (token) {
    case TokenType.beginOptional:
      return beginOptionalCharacter
    case TokenType.endOptional:
      return endOptionalCharacter
    case TokenType.beginParameter:
      return beginParameterCharacter
    case TokenType.endParameter:
      return endParameterCharacter
    case TokenType.alternation:
      return alternationCharacter
  }
  return ''
}

export function purposeOf(token: TokenType): string {
  switch (token) {
    case TokenType.beginOptional:
    case TokenType.endOptional:
      return 'optional text'
    case TokenType.beginParameter:
    case TokenType.endParameter:
      return 'a parameter'
    case TokenType.alternation:
      return 'alternation'
  }
  return ''
}

export interface Located {
  readonly start: number
  readonly end: number
}

export class Node implements Located {
  constructor(
    public readonly type: NodeType,
    public readonly nodes: readonly Node[] | undefined,
    private readonly token: string | undefined,
    public readonly start: number,
    public readonly end: number
  ) {
    if (nodes === undefined && token === undefined) {
      throw new Error('Either nodes or token must be defined')
    }
  }

  text(): string {
    if (this.nodes && this.nodes.length > 0) {
      return this.nodes.map((value) => value.text()).join('')
    }
    return this.token || ''
  }
}

export enum NodeType {
  text = 'TEXT_NODE',
  optional = 'OPTIONAL_NODE',
  alternation = 'ALTERNATION_NODE',
  alternative = 'ALTERNATIVE_NODE',
  parameter = 'PARAMETER_NODE',
  expression = 'EXPRESSION_NODE',
}

export class Token implements Located {
  readonly type: TokenType
  readonly text: string
  readonly start: number
  readonly end: number

  constructor(type: TokenType, text: string, start: number, end: number) {
    this.type = type
    this.text = text
    this.start = start
    this.end = end
  }

  static isEscapeCharacter(codePoint: string): boolean {
    return codePoint === escapeCharacter
  }

  static canEscape(codePoint: string): boolean {
    if (codePoint === ' ') {
      // TODO: Unicode whitespace?
      return true
    }
    switch (codePoint) {
      case escapeCharacter:
        return true
      case alternationCharacter:
        return true
      case beginParameterCharacter:
        return true
      case endParameterCharacter:
        return true
      case beginOptionalCharacter:
        return true
      case endOptionalCharacter:
        return true
    }
    return false
  }

  static typeOf(codePoint: string): TokenType {
    if (codePoint === ' ') {
      // TODO: Unicode whitespace?
      return TokenType.whiteSpace
    }
    switch (codePoint) {
      case alternationCharacter:
        return TokenType.alternation
      case beginParameterCharacter:
        return TokenType.beginParameter
      case endParameterCharacter:
        return TokenType.endParameter
      case beginOptionalCharacter:
        return TokenType.beginOptional
      case endOptionalCharacter:
        return TokenType.endOptional
    }
    return TokenType.text
  }
}

export enum TokenType {
  startOfLine = 'START_OF_LINE',
  endOfLine = 'END_OF_LINE',
  whiteSpace = 'WHITE_SPACE',
  beginOptional = 'BEGIN_OPTIONAL',
  endOptional = 'END_OPTIONAL',
  beginParameter = 'BEGIN_PARAMETER',
  endParameter = 'END_PARAMETER',
  alternation = 'ALTERNATION',
  text = 'TEXT',
}
