module Torque
  module PostgreSQL
    module Adapter
      module DatabaseStatements

        EXTENDED_DATABASE_TYPES = %i(enum enum_set interval)

        # Switch between dump mode or not
        def dump_mode!
          @_dump_mode = !!!@_dump_mode
        end

        # Check if a given type is valid.
        def valid_type?(type)
          super || extended_types.include?(type)
        end

        # Get the list of extended types
        def extended_types
          EXTENDED_DATABASE_TYPES
        end

        # Returns true if type exists.
        def type_exists?(name)
          user_defined_types.key? name.to_s
        end
        alias data_type_exists? type_exists?

        # Configure the interval format
        def configure_connection
          super
          execute("SET SESSION IntervalStyle TO 'iso_8601'", 'SCHEMA')
        end

        # Change some of the types being mapped
        def initialize_type_map(m = type_map)
          super
          m.register_type 'box',      OID::Box.new
          m.register_type 'circle',   OID::Circle.new
          m.register_type 'interval', OID::Interval.new
          m.register_type 'line',     OID::Line.new
          m.register_type 'segment',  OID::Segment.new
        end

        # :nodoc:
        if Torque::PostgreSQL::AR521
          def load_additional_types(oids = nil)
            super
            torque_load_additional_types(oids)
          end
        else
          def load_additional_types(type_map, oids = nil)
            super
            torque_load_additional_types(oids)
          end
        end

        # Add the composite types to be loaded too.
        def torque_load_additional_types(oids = nil)
          filter = "AND     a.typelem::integer IN (%s)" % oids.join(", ") if oids

          query = <<-SQL
            SELECT      a.typelem AS oid, t.typname, t.typelem,
                        t.typdelim, t.typbasetype, t.typtype,
                        t.typarray
            FROM        pg_type t
            INNER JOIN  pg_type a ON (a.oid = t.typarray)
            LEFT JOIN   pg_catalog.pg_namespace n ON n.oid = t.typnamespace
            WHERE       n.nspname NOT IN ('pg_catalog', 'information_schema')
            AND     t.typtype IN ( 'e' )
            #{filter}
            AND     NOT EXISTS(
                      SELECT 1 FROM pg_catalog.pg_type el
                        WHERE el.oid = t.typelem AND el.typarray = t.oid
                      )
            AND     (t.typrelid = 0 OR (
                      SELECT c.relkind = 'c' FROM pg_catalog.pg_class c
                        WHERE c.oid = t.typrelid
                      ))
          SQL

          execute_and_clear(query, 'SCHEMA', []) do |records|
            records.each do |row|
              case row['typtype']
              when 'e' then OID::Enum.create(row, type_map)
              end
            end
          end
        end

        # Gets a list of user defined types.
        # You can even choose the +category+ filter
        def user_defined_types(*categories)
          category_condition = categories.present? \
            ? "AND t.typtype IN ('#{categories.join("', '")}')" \
            : "AND t.typtype NOT IN ('b', 'd')"

          select_all(<<-SQL, 'SCHEMA').rows.to_h
            SELECT t.typname AS name,
                   CASE t.typtype
                     WHEN 'e' THEN 'enum'
                     END     AS type
            FROM pg_type t
                   LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
            WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
              #{category_condition}
              AND NOT EXISTS(
                SELECT 1
                FROM pg_catalog.pg_type el
                WHERE el.oid = t.typelem
                  AND el.typarray = t.oid
              )
              AND (t.typrelid = 0 OR (
              SELECT c.relkind = 'c'
              FROM pg_catalog.pg_class c
              WHERE c.oid = t.typrelid
            ))
            ORDER BY t.typtype DESC
          SQL
        end

        # Get the list of inherited tables associated with their parent tables
        def inherited_tables
          tables = query(<<-SQL, 'SCHEMA')
            SELECT child.relname             AS table_name,
                   array_agg(parent.relname) AS inheritances
            FROM pg_inherits
            JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
            JOIN pg_class child  ON pg_inherits.inhrelid  = child.oid
            GROUP BY child.relname, pg_inherits.inhrelid
            ORDER BY pg_inherits.inhrelid
          SQL

          tables.map do |(table, refs)|
            [table, Coder.decode(refs)]
          end.to_h
        end

        # Get the list of columns, and their definition, but only from the
        # actual table, does not include columns that comes from inherited table
        def column_definitions(table_name) # :nodoc:
          local_condition = 'AND a.attislocal IS TRUE' if @_dump_mode
          query(<<-SQL, 'SCHEMA')
              SELECT a.attname, format_type(a.atttypid, a.atttypmod),
                     pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
             (SELECT c.collname FROM pg_collation c, pg_type t
               WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
                     col_description(a.attrelid, a.attnum) AS comment
                FROM pg_attribute a
           LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
               WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
                 AND a.attnum > 0
                 AND a.attisdropped IS FALSE
                 #{local_condition}
               ORDER BY a.attnum
          SQL
        end

        # Extracts the value from a PostgreSQL column default definition.
        def extract_value_from_default(default)
          case default
            # Array elements
          when /\AARRAY\[(.*)\]\z/
            # TODO: Improve this since it's not the most safe approach
            eval(default.gsub(/ARRAY|::\w+(\[\])?/, ''))
          else
            super
          end
        rescue SyntaxError
          # If somethin goes wrong with the eval, just return nil
        end

      end
    end
  end
end