# Author::    Eric Crane  (mailto:eric.crane@mac.com)
# Copyright:: Copyright (c) 2020 Eric Crane.  All rights reserved.
#
# A SQL database query.
# Relies on a database connection object.
#

module GlooLang
  module Objs
    class Query < GlooLang::Core::Obj

      KEYWORD = 'query'.freeze
      KEYWORD_SHORT = 'sql'.freeze

      DB = 'database'.freeze
      SQL = 'sql'.freeze
      RESULT = 'result'.freeze
      PARAMS = 'params'.freeze

      DB_MISSING_ERR = 'The database connection is missing!'.freeze

      #
      # The name of the object type.
      #
      def self.typename
        return KEYWORD
      end

      #
      # The short name of the object type.
      #
      def self.short_typename
        return KEYWORD_SHORT
      end

      # ---------------------------------------------------------------------
      #    Children
      # ---------------------------------------------------------------------

      #
      # Does this object have children to add when an object
      # is created in interactive mode?
      # This does not apply during obj load, etc.
      #
      def add_children_on_create?
        return true
      end

      #
      # Add children to this object.
      # This is used by containers to add children needed
      # for default configurations.
      #
      def add_default_children
        fac = @engine.factory
        fac.create_alias DB, nil, self
        fac.create_string SQL, nil, self
        fac.create_can RESULT, self
      end

      # ---------------------------------------------------------------------
      #    Messages
      # ---------------------------------------------------------------------

      #
      # Get a list of message names that this object receives.
      #
      def self.messages
        return super + [ 'run' ]
      end

      #
      # SSH to the host and execute the command, then update result.
      #
      def msg_run
        db = db_obj
        unless db
          @engine.err DB_MISSING_ERR
          return
        end

        result = db.query( sql_value, param_array )
        process_result result
      end

      # ---------------------------------------------------------------------
      #    Show Results
      # ---------------------------------------------------------------------

      # 
      # The first row will be a header row, 
      # so if there are exactly 2 rows, then we have only
      # a single row of data.
      # 
      def single_row_result?( data )
        return data.count == 2 
      end

      # 
      # Show a single row in a vertical, form style view.
      # 
      def show_single_row( data )
        head = data[0]
        row = data[1]

        head.each_with_index do |h, i|
          puts "#{h}: \t #{row[i]}"
        end
      end

      # 
      # Show multiple rows in a table view.
      # 
      def show_rows( data )
        data.each_with_index do |row, i|
          # Show header for the first row
          puts row.map { |k, _| k }.join( " \t " ).white if i.zero?

          # Show the row data
          puts row.map { |_, v| v }.join( " \t " )
        end
      end

      # ---------------------------------------------------------------------
      #    Private functions
      # ---------------------------------------------------------------------

      private

      #
      # Get the database connection.
      #
      def db_obj
        o = find_child DB
        return GlooLang::Objs::Alias.resolve_alias( @engine, o )
      end

      #
      # Get the SQL from the child object.
      # Returns nil if there is none.
      #
      def sql_value
        o = find_child SQL
        return nil unless o

        return o.value
      end

      #
      # Do something with the result of the SQL Query call.
      # If there's a result container, we'll create objects in it.
      # If not, we'll just show the output in the console.
      #
      def process_result( data )
        r = find_child RESULT
        if r
          update_result_contaier data
        else
          show_result data
        end
      end

      #
      # Get the arrya of parameters.
      # If there is no PARAM container of if it is empty,
      # we'll return a nil value.
      #
      def param_array
        o = find_child PARAMS
        return nil unless o

        return nil if o.child_count.zero?

        params = []
        o.children.each do |p|
          params << p.value
        end

        return params
      end

      #
      # Show the result of the query in the console.
      #
      def show_result( data )
        return if data.nil?

        if single_row_result?( data )
          show_single_row( data )
        else
          show_rows( data )
        end
      end

      #
      # Update the result container with the data from the query.
      #
      def update_result_contaier( data )
        r = find_child RESULT
        r = GlooLang::Objs::Alias.resolve_alias( @engine, r )
        data.each_with_index do |row, i|
          can = r.find_add_child( i.to_s, 'can' )
          row.each do |k, v|
            o = can.find_add_child( k, 'untyped' )
            o.set_value v
          end
        end
      end

    end
  end
end