#a dynamic module that contains data that can be re-used, hence the name
module Persistable

  module ClassMethods

    def table_name
      "#{self.to_s.downcase}s"
    end

    #this method will dynamically create a new instance with the assigned attrs and values
    #by doing mass assignment of the hash's key/value pairs
    def create(attributes_hash)
      #the tap method allows preconfigured methods and values to
      #be associated with the instance during instantiation while also automatically returning
      #the object after its creation is concluded.
      self.new.tap do |card|
        attributes_hash.each do |key, value|
          #sending the new instance the key name as a setter with the value
          card.send("#{key}=", value)
          #string interpolation is used as the method doesn't know the key name yet
          #but an = sign is implemented into the string in order to asssociate it as a setter
        end
        #saves the new instance into the database
        card.save
      end
    end

    def create_table
      sql = <<-SQL
          CREATE TABLE IF NOT EXISTS #{self.table_name} ( #{self.create_sql} )
      SQL

      DB[:conn].execute(sql)
    end

    def remove_table
      sql = <<-SQL
          DROP TABLE IF EXISTS #{self.table_name}
      SQL

      DB[:conn].execute(sql)
    end

    def table_empty?
      sql = <<-SQL
          SELECT * FROM #{self.table_name}
      SQL

      table = DB[:conn].execute(sql)
      check = table.empty?
      if check == true
        true
      end
    end

    def table_rows
      sql = <<-SQL
      SELECT COUNT(*) FROM #{self.table_name}
      SQL

      table = DB[:conn].execute(sql)
      rows = table.flatten.join.to_i
      rows
    end

    def make_csv_file
      #collects everything in sql table
      rows = DB[:conn].execute("SELECT * FROM #{self.table_name}")
      date = "#{Time.now}"[0..9].gsub!("-", "_")
      #naming the csv file with today's date
      fname = "#{self}_#{date}.csv"
      #collecting the table's column names
      col_names = "#{self.attributes.keys.join(", ")} \n"
      unless File.exists? fname
        #opening the csv file to write data into
        File.open(fname, 'w') do |ofile|
          #first row will be column names
          ofile << col_names
          rows.each_with_index do |value, index|
            #iterates through all the rows values to replace commas so as to avoid line breaking errors
            value.each { |find| find.gsub!(", ", "_") if find.is_a?(String) }
            #pushing each array within rows as a newline into csv while removing nil values
            ofile << "#{rows[index].compact.join(", ")} \n"
          end
          sleep(1 + rand)
        end
      end
    end

    def buy_link(id)
      name = self.find(id)
      begin
        #collect the instant's values as a string
        word = name.card + " " + name.sets
      rescue
        #instead of getting an undefined method error in .card & .sets for nil:NilClass
        #just re-run method until user sets it to a true value
        Parser.purchase
      else
        #replace whitespace chars
        word.gsub!(/\s+/m, '%20')
        #create url for purchasing the chosen id card
        buy = "http://www.ebay.com/sch/?_nkw=#{word}&_sacat=0".fg COLORS[3]
        puts ""
        puts "Please highlight, right click and copy the #{"url".fg COLORS[3]} below and paste it to your preferred browser:"
        puts "--------------------------------------------------------------------------------------------"
        puts ""
        puts buy
        puts ""
      end
    end

    def find(id)
      sql = <<-SQL
          SELECT * FROM #{self.table_name} WHERE id=(?)
      SQL

      row = DB[:conn].execute(sql, id)
      #if a row is actually returned i.e. the id actually exists
      if row.first
        self.reify_from_row(row.first)
        #using .first array method to return only the first nested array
        #that is taken from self.reify_from_row(row) which is the resulting id of the query
      else
        puts "This card does not exist"
      end
    end

    #opposite of abstraction is reification i.e. I'm getting the raw data of these variables
    def reify_from_row(row)
      #the tap method allows preconfigured methods and values to
      #be associated with the instance during instantiation while also automatically returning
      #the object after its creation is concluded.
      self.new.tap do |card|
        self.attributes.keys.each.with_index do |key, index|
          #sending the new instance the key name as a setter with the value located at the 'row' index
          card.send("#{key}=", row[index])
          #string interpolation is used as the method doesn't know the key name yet
          #but an = sign is implemented into the string in order to asssociate it as a setter
        end
      end
    end

    def create_sql
      #will apply the column names ('key') and their schemas ('value') into sql strings without having to hard code them
      #the collect method returns the revised array and then we concatenate it into a string separating the contents with a comma
      self.attributes.collect {|key, value| "#{key} #{value}"}.join(", ")
    end

    def attributes_names_insert_sql
      #same idea as self.create_sql only it's returning the 'key' for sql insertions
      self.attributes.keys[1..-1].join(", ")
    end

    def question_marks_insert_sql
      #returns the number of key-value pairs in the hash minus one for the 'id'
      questions = self.attributes.keys.size-1
      #converts them into '?' array that is then turned into comma separated string
      questions.times.collect {"?"}.join(", ")
    end

    def sql_columns_to_update
      #returns the number of keys in the hash minus one for the 'id'
      columns = self.attributes.keys[1..-1]
      #converts them into 'attribute=(?)' array that is then turned into comma separated string
      columns.collect {|attr| "#{attr}=(?)"}.join(", ")
    end
  end

  module InstanceMethods

    def save
      #if the card has already been saved, then call update method
      persisted? ? update : insert
      #if not call insert method instead
    end

    def attribute_values_for_sql_check
      self.class.attributes.keys[1..-1].collect {|attr_names| self.send(attr_names)}
      #I go through the key names (minus 'id') and return an array containing their values for the recieving instance
      #basically like getting an array of getter methods for that instance
    end

    def persisted?
      #the '!!' double bang converts object into a truthy value statement
      !!self.id
    end

    def update
      #updates by the unique identifier of 'id'
      sql = <<-SQL
           UPDATE #{self.class.table_name} SET #{self.class.sql_columns_to_update} WHERE id=(?)
      SQL

      #using splat operator to signify that there may be more than one argument in terms of attr_readers
      DB[:conn].execute(sql, *attribute_values_for_sql_check, self.id)
    end

    def insert
      sql = <<-SQL
          INSERT INTO #{self.class.table_name} (#{self.class.attributes_names_insert_sql}) VALUES (#{self.class.question_marks_insert_sql})
      SQL

      #using splat operator to signify that there may be more than one argument in terms of attr_readers
      DB[:conn].execute(sql, *attribute_values_for_sql_check)
      #after inserting the card to the database, I want to get the primary key that is auto assigned to it
      #from sql and set it to the instance method 'id' of this very instance variable.
      self.id = DB[:conn].execute("SELECT last_insert_rowid() FROM #{self.class.table_name}")[0][0]
      #returns first array with the first value of the array (i.e. index 0)
    end
  end

end
