//  Program:  Internet Ads
//  Author:   Chuck Stewart
//  Purpose:  main function for the solution to HW 3, CS II, Spring 2008. 
//
//  The program is organized around a vector of user objects.  For
//  each keyword, each user is searched to generate an ad for a given
//  keyword.  This does not scale well and we will discuss alternative
//  approaches in class.

#include <algorithm>
#include <fstream>
#include <iostream>
#include <vector>
#include "user.h"

//  Add a keyword and url for an ad.  If the user id is new, a new
//  user object must be added to the vector of users.   The function
//  returns true if and only if the input data is correctly
//  formatted.  This is used for error checking on the test data, but
//  is not strictly necessary for the assignment. 

bool add_keyword_and_url( std::istream & ad_ifs,
                          std::ostream & output_ofs,
                          std::vector<user> & users )
{
  std::string keyword, user_id, url;
  float post_cost, click_cost;

  // Input the user. 
  if ( ad_ifs >> keyword >> user_id >> url >> post_cost >> click_cost )
    {
      //  Echo the input
      output_ofs << '\n' << "add " << keyword << ' ' << user_id << ' ' << url
                 << ' ' << post_cost << ' ' << click_cost << '\n';

      //  Search for the user id.  The result of the loop that if the
      //  user id is among the users, the found flag will be set to
      //  true and index will store the index in the vector.
      unsigned int index = 0;
      bool found = false;
      for ( unsigned int i=0; i<users.size() && !found; ++i )
        { 
          if ( users[i].id() == user_id )
            {
              found = true;
              index = i;
            }
        }

      //  If the user id is not there, add a new user and sent the
      //  index to the last index in the vector.
      if ( !found )
        {
          user new_user(user_id);
          users.push_back(new_user);
          index = users.size()-1;
          output_ofs << "added user " << user_id << '\n';
        }

      //  Do the work of adding the keyword.  The add_keyword function
      //  sets is_new to true if the keyword is new to the user. 
      bool is_new;
      users[index].add_keyword( keyword, url, post_cost, click_cost, is_new );
      if ( is_new )
        output_ofs << "keyword " << keyword << " and url "
                   << url << " added for user " << user_id << '\n';
      else
        output_ofs << "new url " << url << " for keyword "
                   << keyword << " and user " << user_id << '\n';

      return true;  // input was ok
    }
  else
    return false;   // input failed
}


//  A temporary class for the urls that will be displayed.  This is
//  only temporary and does very little.

class url_display {
public:
  url_display( const std::string& url, 
               float cost )
    : m_url(url), m_cost(cost)
  {}
  const std::string & url() const { return m_url; }
  float cost() const { return m_cost; }
private:
  std::string m_url;
  float m_cost;
};

//  Comparison function for sorting ads.
bool greater_cost( const url_display& a, const url_display& b )
{
  return a.cost() > b.cost();
}


//  Search for a keyword among the users and the ads, generating the
//  required adds in their required order.

bool search_keyword( std::istream & ad_ifs,
                     std::ostream & output_ofs,
                     std::vector<user> & users )
{
  std::string keyword;

  //  Get the input keyword and proceed if it is correct.
  if ( ad_ifs >> keyword )
    {
      // Echo the input
      output_ofs << "\n" << "search " <<  keyword << '\n';
      
      // Build a vector of urls to display.  The ad_for_keyword
      // function returns true if an ad was generated for this keyword
      // and it assigns the url and post_cost values for this ad.
      std::string url;
      float post_cost;
      std::vector< url_display > displays;
      for (unsigned i=0; i!=users.size(); ++i )
        if ( users[i].ad_for_keyword( keyword, url, post_cost ) )
          displays.push_back( url_display(url,post_cost) );
      
      //  Output the ads.
      if ( displays.empty() )
        output_ofs << "no ads\n";
      else
        {
          output_ofs << "ads:\n";
          std::sort( displays.begin(), displays.end(), greater_cost );
          for ( unsigned int i=0; i<displays.size(); ++i )
            output_ofs << "  " << displays[i].url() << '\n';
        }
      return true; // input was ok
    }
  else
    return false;  // input failed
}


//  A url ad has been clicked, so find the associated user and charge
//  the user's account.

bool handle_click( std::istream & ad_ifs,
                   std::ostream & output_ofs,
                   std::vector<user> & users )
{
  std::string keyword, url;

  // Check the rest of the input, checking to see if it is correct.
  if ( ad_ifs >> keyword >> url )
    {
      //  Echo the input
      output_ofs << '\n' << "click " << keyword << ' ' << url << '\n';

      //  Search the vector of users for the url, charging the user
      //  found.  The click function searches for the keyword / url
      //  combination among its ads, and if it is found charges for
      //  the click and returns true.
      for ( unsigned int i=0; i<users.size(); ++i )
        {
          if ( users[i].click( keyword, url ) )
            {
              output_ofs << "user " << users[i].id() << " charged\n";
              return true;
            }
        }
      //  This final output, generated only if no user is found, is
      //  mostly needed for debugging purposes and is not part of the
      //  original assignment.
      output_ofs << "no user found\n";
      return true;  // input was ok
    }
  else
    return false;  // input failed
}


//  Remove the keyword ad from the user.

bool remove_keyword_from_user( std::istream & ad_ifs,
                               std::ostream & output_ofs,
                               std::vector<user> & users )
{
  //  Get the keyword and ad
  std::string keyword, user_id;
  if ( ad_ifs >> keyword >> user_id )
    {
      //  Echo the input
      output_ofs << '\n' << "remove " << keyword << ' ' << user_id << '\n';

      //  Find the user and remove the ad.
      for ( unsigned int i=0; i<users.size(); ++i )
        {
          if ( users[i].id() == user_id )
            {
              if ( users[i].remove( keyword ) )
                output_ofs << keyword << " removed\n";
              else
                output_ofs << keyword << " not found\n";
            }
        }
      return true;  // input ok
    }
  else
    return false;   // input failure
}


//  Generate the statistics for a user by searching for the user id.

bool generate_statistics_for_user( std::istream & ad_ifs,
                                   std::ostream & output_ofs,
                                   std::vector<user> & users )
{ 
  //  Get the user id
  std::string user_id;
  if ( ad_ifs >> user_id )
    {
      //  Echo the input
      output_ofs << '\n' << "statistics " << user_id << '\n';

      //  Find the user id and when found generate the output.  It is
      //  crucial  that  output_ofs be passed by reference to the
      //  statistics function.  Passing it by value will result in
      //  a program crash.
      for ( unsigned int i=0; i<users.size(); ++i )
        if ( users[i].id() == user_id )
          {
            users[i].statistics( output_ofs );
            return true; // done here, input ok
          }
      output_ofs << "user not found\n";
      return true;  // input ok
    }
  else
    return false;   // input failure
}



int main( int argc, char* argv[] )
{
  //  Check the number of command-line arguments
  if ( argc != 3 )
    {
      std::cerr << "Usage: " << argv[0] << " commands output\n";
      return 1;
    }

  //  Attach an input stream to the input file specified in
  //  the first command-line argument.
  std::ifstream ad_ifs( argv[1] );
  if ( !ad_ifs )
    {
      std::cerr << "Can not open the search file " << argv[1] << "\n";
      return 1;
    }

  //  Create the output stream
  std::ofstream output_ofs( argv[2] );
  if ( !output_ofs )
    {
      std::cerr << "Can not open the output file " << argv[2] << "\n";
      return 1;
    }

  std::string command;
  std::vector<user> users;

  //  Handle the internet ad searches one at a time, building up the
  //  vector of users.  Each input line is handled by a separate
  //  function.  If one of these returns false, the input is corrupted
  //  and the program should quit.
  while ( ad_ifs >> command )
    {
      if ( command == "add" )
        {
          if ( !add_keyword_and_url( ad_ifs, output_ofs, users ) )
            return 0;
        }
      else if ( command == "search" )
        {
          if ( !search_keyword( ad_ifs, output_ofs, users ) )
            return 0;
        }
      else if ( command == "click" )
        {
          if ( !handle_click( ad_ifs, output_ofs, users ) )
            return 0;
        }
      else if ( command == "remove" )
        {
          remove_keyword_from_user( ad_ifs, output_ofs, users );
        }
      else if ( command == "statistics" )
        {
          generate_statistics_for_user( ad_ifs, output_ofs, users );
        }
      else
        {
          std::cerr << "Invalid command: " << command << '\n';
          return 0;
        }
    }
  return 0;
}

