module Liquor
  class Diagnostic < StandardError
    attr_reader :location
    attr_reader :raw_message

    def initialize(message, location=nil)
      location_info = ""
      if location
        if location.include? :file
          location_info << "`#{location[:file]}'"
        end

        if location.include? :line
          location_info << ": line #{location[:line] + 1}"
          if location.include? :start
            location_info << ", column #{location[:start] + 1}"
          end
        end
      end

      @location    = location
      @raw_message = message

      if location_info.empty?
        super(message)
      else
        super("#{message} at #{location_info}")
      end
    end

    def error?
      false
    end

    def decorate(source)
      if @location && @location.has_key?(:line)
        line   = source.lines.drop(@location[:line]).first
        line ||= "(source spans only #{source.lines.count} lines)"
        line   = line.rstrip

        if @location.has_key? :start
          start_col = tabify_column line, @location[:start]
          pointer =  " " * start_col

          if location.has_key? :end
            end_col = tabify_column line, @location[:end]

            pointer += "^" * (end_col - start_col + 1)
          else
            pointer += "^"
          end
        end
      end

      [ line, pointer ].compact
    end

    # Dammit, why should I replicate parts of VT-52 all over again?!
    # Curse backwards compatibility and C.
    def tabify_column(line, column)
      display_column = 0

      line[0...column].each_char do |char|
        if char == "\t"
          display_column += 8 - (display_column % 8)
        else
          display_column += 1
        end
      end

      display_column
    end

    def as_json(options = nil)
      {
        message:  @raw_message,
        is_error: error?,
        location: @location,
      }
    end

    def to_json(options = nil)
      as_json.to_json(options)
    end
  end

  class Error < Diagnostic
    def error?
      true
    end
  end

  class SyntaxError < Error
  end

  class PartialError < Error
  end

  class ArgumentError < Error
  end

  class NameError < Error
  end

  class TypeError < Error
  end

  class ArgumentTypeError < Error
    attr_reader :location

    def initialize(message, location=nil)
      location_info = ""
      if location
        if location.include? :function
          location_info << "function `#{location[:function]}'"
          if location.include? :argument
            if location[:argument].nil?
              location_info << ", unnamed argument"
            else
              location_info << ", argument `#{location[:argument]}'"
            end
          end
        end
      end

      if location_info.empty?
        super(message, location)
      else
        super("#{message} at #{location_info}", location)
      end
    end
  end

  class HostError < Error
    attr_reader :original_error, :host_backtrace

    def initialize(message, original_error, host_backtrace, location=nil)
      @original_error = original_error
      @host_backtrace = host_backtrace

      if message.nil?
        message = "#{original_error.class}: #{original_error.message}"
      end

      super(message, location)
    end
  end

  class Deprecation < Diagnostic
  end
end
