// Things to Remember:
// 1. Syntax of overloading operators: 
//    -- for any operator @, x @ y  can be thought of as 
//         x.operator@( y )  for a member function called "operator@( . )", OR
//         operator@( x, y ) for a friend function "operator@( ., . )"
//
// 2. Semantics of overloading: 
//    -- take parameter (the second operand) by REFERENCE (to avoid copying)
//    -- return the result by VALUE (which usually results in creation of an 
//         unnamed temporary), because the result of an operation is a new 
//         object of the same class
//
// 3. Assignment (operator=) and C.C.:
//    -- are always generated even if not defined in your code. If default, 
//           they only do member-wise copy
//    -- operator= should check agains "assignment to self" ("this" helps out)
//    -- how are C.C. and operator= different? 
//
// 4. "this" (defined inside any memeber function of a class X) is a pointer 
//       to the current instance, i.e. the current variable of type X) 

// Class declarations normally go into a separate header file

#ifndef MY_FRAC
#define MY_FRAC

class myFrac {
private:
    int num;     // numerator
    int denom;   // denominator
public:
    myFrac() {   // needed for arrays etc.
        cout << "myFrac default constructor called\n"; 
    }  

    myFrac( int n, int d = 1) : num(n), denom(d) {     // works for   
        cout << "myFrac other constructor called\n";   //   myFrac a(1,2); 
    }                                                  //   myFrac b( 3 ); 
                                                       // (recall: 3 == 3/1 )

    myFrac( const myFrac& y ) : num(y.num), denom(y.denom) {
        cout << "myFrac copy constructor called\n";    // works for  
    }                                                  //   myFrac c = a;  
                                                       //   myFrac c(a); 

    // NOTE:  myFrac a=c;  and  myFrac a(c); are the SAME, and both call C.C.

    ~myFrac() { cout << "myFrac destructor called\n"; }

    // overloaded assignment. This kind of code will be generated automatically
    myFrac& operator= ( const myFrac& y ){  // returns a reference to allow 
        cout << "operator= for myFrac\n";   //     a = b = c;  
        if( this != &y ) {                  // check against x = x;
            num = y.num;
            denom = y.denom;
        }
        return *this;             // returns a reference to the current myFrac
    }


    // Basic Operations
    
    myFrac operator+ ( const myFrac& y ){  
        return myFrac( num * y.denom + denom * y.num, denom * y.denom );
    }
    
    myFrac operator* ( const myFrac& y ){  
        return myFrac( num * y.num, denom * y.denom );
    }

    // implement -, / . Guard again division by 0
    
    // we want to allow 3*myFrac(1,2) (== 3/2) and 2 + myFrac(2,5) (== 12/5 )

    // friend functions are NOT members. Only their prototypes may appear here
    // Notice the overloading!

    friend myFrac operator+( int x, const myFrac& y );  // definition deferred
    friend myFrac operator*( int x, const myFrac& y );  // definition deferred
    
    friend myFrac operator+( const myFrac& y, int x );  // definition deferred
    friend myFrac operator*( const myFrac& y, int x );  // definition deferred
    
    //NOTE: operator+, operator* for myFracs themselves can also be defined as
    //      friends rather than members. In fact, defining them as friends is 
    //      preferred, because it makes code more symmetric (see below)
    
    friend bool operator== ( const myFrac& x, const myFrac& y ); 

    //  friend bool operator== ( int x, const myFrac& y );  -- implement these
    //  friend bool operator== ( const myFrac& x, int y );
    // implement operator<, operator>  etc. as friends
 
    // we want myFracs to be anble to print themselves:  cout << a; 
    // So we overload operator<< , which is just like any other operator. 
    //   Recall:  cout << a   is actually   operator<< ( cout, a )  !!! 
    //   Q: What type is the object "cout"? 
    //   A: ostream (defined in iostream.h)

    friend ostream& operator<<( ostream& , const myFrac& ); 

};  // end class myFrac  

// Definitions of friends and others

myFrac operator+( int x, const myFrac& y ){  // x/1 + a/b 
    return myFrac( x*y.denom + y.num , y.denom );
}

myFrac operator*( int x, const myFrac& y ){
    return myFrac( x*y.num , y.denom );
}
    
myFrac operator+( const myFrac& x, int y ){  
    return myFrac( x.num + y*x.denom , x.denom );
}

myFrac operator*( const myFrac& x, int y ){
    return myFrac( x.num * y , x.denom );
}

// a/b == c/d  if and only if  a*d == b*c . Why not just a==c, b==d ?

bool operator== ( const myFrac& x, const myFrac& y ){
    return ( x.num * y.denom == y.num * x.denom );
}

ostream& operator<<( ostream& os , const myFrac& x ){
    os << x.num << "/" << x.denom ;
    return os;                    // must return  ostream&  to allow smth like
                                  //   cout << a << " " << b << endl; 
}

#endif  // MY_FRAC