/*                            */
/*  Limit operator rule base  */
/*                            */

/* Special case: constant */
100 # Lim(_var, _tar, _dir, p_IsFreeOf(var)) <-- p;

/* Special case: limits of polynomials as x approaches infinity */
100 # Lim(_var, _tar, _dir, _p)_(CanBeUni(var, p) And Degree(p, var) > 0 And IsConstant(LeadingCoef(p,var)) And IsInfinity(tar))
    <-- LeadingCoef(p,var) * Sign(tar)^Degree(p,var) * Infinity;

/* Special case: rational functions */
110 # Lim(_var, _tar, _dir, _p)_(IsRationalFunction(p, var) And IsInfinity(tar)) <--
    Lim(var, tar, dir, Div(Numer(p), Denom(p)));

/* Special case: make use of the logarithm properties */
120 # Lim(_var, _tar, _dir, Ln(_a) + Ln(_b)) <-- Lim(var, tar, dir, Ln(a*b));
120 # Lim(_var, _tar, _dir, Ln(_a) - Ln(_b)) <-- Lim(var, tar, dir, Ln(a/b));

/*  Exponentiation rules  */

/*  Special limit #1:  0 ^ 0;  #2:  1 ^ Infinity;  #3:  Infinity ^ 0  */
200 # Lim(_var, _tar, _dir, _x ^ _y)_
( [
    Local(lx,ly); lx := Lim(var, tar, dir, x); ly := Lim(var, tar, dir, y);
    ((IsZero(lx) And IsZero(ly)) Or ((lx = 1) And IsInfinity(ly)) Or (IsInfinity(lx) And IsZero(ly)));
] )
<-- Exp(Lim(var, tar, dir, y * Ln(x)));

/*  Default rule  */
210 # Lim(_var, _tar, _dir, _x ^ _y)
<-- Lim(var, tar, dir, x)^Lim(var, tar, dir, y);


/*  Division rules  */

/*  Special limit #4:  0 / 0;  #5:  Infinity / Infinity  */
300 # Lim(_var, _tar, _dir, _x / _y)_
( [
    Local(lx,ly,infx,infy);
     lx := Lim(var, tar, dir, x);
     ly := Lim(var, tar, dir, y);
     infx := (IsInfinity(lx) Or (IsZero(Re(lx)) And IsInfinity(Im(lx))));
     infy := (IsInfinity(ly) Or (IsZero(Re(ly)) And IsInfinity(Im(ly))));
    ((IsZero(lx) And IsZero(ly)) Or
     (infx And infy)
     );
] )
<-- Lim(var, tar, dir, ApplyPure("D", {var, x})/ApplyPure("D", {var, y}));

/*  Special limit #6: null denominator  */
/*  Probably there are still some problems.  */

Dir(Right) <-- 1;
Dir(Left) <-- -1;

UnProtect(Sign);

/*  To get the sign of the denominator on one side:  */
Sign(_var, _tar, _dir, _exp, _n)
<-- [
  Local(der, coef); der := ApplyPure("D", {var, exp});
  coef := Eval(ApplyPure("Subst", {var, tar, der}));
  If ( coef = 0,
       Sign(var, tar, dir, der, n+1),
       (Sign(coef)*Dir(dir)) ^ n
     );
];

Protect(Sign);

/*  To avoid infinite recursion (with 1/Exp(-x) for instance)  */
310 # Lim(_var, _tar, _dir, _x / _y)_
(IsInfinity(tar) And IsZero(Lim(var, tar, dir, y)))
<-- Sign(Lim(var, tar, dir, x))*Sign(Lim(var, tar, dir, ApplyPure("D", {var, y})))*tar;

320 # Lim(_var, _tar, _dir, _x / _y)_IsZero(Lim(var, tar, dir, y))
<-- Sign(Lim(var, tar, dir, x))*Sign(var, tar, dir, y, 1)*Infinity;


/*  Default rule  */
330 # Lim(_var, _tar, _dir, _x / _y) <-- [
    Local(u,v,r);

    u := Lim(var, tar, dir, x);
    v := Lim(var, tar, dir, y);

    r := u / v;

    If (u = Undefined And IsInfinity(v), [
        Local(li, ls);

        li := LimInf(var,tar,dir,x);
        ls := LimSup(var,tar,dir,x);
        r := (li * ls) / v;
    ]);

    r;
];


/*  Multiplication rules  */

/*  To avoid some infinite recursions  */
400 # Lim(_var, _tar, _dir, _x * Exp(_y))_
(IsInfinity(Lim(var, tar, dir, x)) And (Lim(var, tar, dir, y) = -Infinity))
<-- Lim(var, tar, dir, x/Exp(-y));
400 # Lim(_var, _tar, _dir, Exp(_x) * _y)_
((Lim(var, tar, dir, x) = -Infinity) And IsInfinity(Lim(var, tar, dir, y)))
<-- Lim(var, tar, dir, y/Exp(-x));
400 # Lim(_var, _tar, _dir, Ln(_x) * _y)_
(IsZero(Lim(var, tar, dir, x)) And IsZero(Lim(var, tar, dir, y)))
<-- Lim(var, tar, dir, y*Ln(x));

/*  Special limit #7:  0 * Infinity  */
410 # Lim(_var, _tar, _dir, _x * _y)_
((IsZero(Lim(var, tar, dir, x)) And IsInfinity(Lim(var, tar, dir, y)))
  Or (IsInfinity(Lim(var, tar, dir, x)) And IsZero(Lim(var, tar, dir, y))))
<-- Lim(var, tar, dir, Simplify(ApplyPure("D", {var, y})/ApplyPure("D", {var, 1/x})));

/*  Default rule  */
420 # Lim(_var, _tar, _dir, _x * _y) <-- [
    Local(u,v,r);

    u := Lim(var, tar, dir, x);
    v := Lim(var, tar, dir, y);

    r := u * v;

    If (u = 0 And v = Undefined, [
        Local(li, ls);
        li := LimInf(var,tar,dir,y);
        ls := LimSup(var,tar,dir,y);
        r := u * li * ls;
    ], If (u = Undefined And v = 0, [
        Local(li, ls);
        li := LimInf(var,tar,dir,x);
        ls := LimSup(var,tar,dir,x);
        r := v * li * ls;
    ]));

    r;
];


/*  Substraction rules  */

/*  Special limit #8a:  Sqrt(Infinity) - Sqrt(Infinity)  */
500 # Lim(_var, _tar, _dir, Sqrt(_x) - Sqrt(_y))_
( [
    Lim(var, tar, dir, x) = Infinity And Lim(var, tar, dir, y) = Infinity;
] )
<-- Lim(var, tar, dir, (x - y)/(Sqrt(x)+Sqrt(y)));

/*  Special limit #8b:  Infinity - Infinity  */
505 # Lim(_var, _tar, _dir, _x - _y)_
( [
    Local(lx,ly); lx := Lim(var, tar, dir, x); ly := Lim(var, tar, dir, y);
    ((lx = Infinity) And (ly = Infinity)) Or ((lx = -Infinity) And (ly = -Infinity));
] )
<-- Lim(var, tar, dir, x*(1-y/x));

/*  Default rule  */
510 # Lim(_var, _tar, _dir, _x - _y)
<-- Lim(var, tar, dir, x)-Lim(var, tar, dir, y);

/*  Unary minus  */
520 # Lim(_var, _tar, _dir, - _x)
<-- - Lim(var, tar, dir, x);


/*  Addition rules  */

/*  Special limit #9:  Infinity + (-Infinity)  */
600 # Lim(_var, _tar, _dir, _x + _y)_
( [
    Local(lx,ly); lx := Lim(var, tar, dir, x); ly := Lim(var, tar, dir, y);
    ((lx = Infinity) And (ly = -Infinity)) Or ((lx = -Infinity) And (ly = Infinity));
] )
<-- Lim(var, tar, dir, x*(1+y/x));

605 # Lim(_var, _tar, _dir, _x + _y)_
(
    Lim(var, tar, dir, x) = Infinity And Lim(var, tar, dir, y) = Undefined And LimInf(var, tar, dir, y) != -Infinity
    Or
    Lim(var, tar, dir, x) = Undefined And LimInf(var, tar, dir, x) != -Infinity And Lim(var, tar, dir, y) = Infinity

) <-- Infinity;


/*  Default rule  */
610 # Lim(_var, _tar, _dir, _x + _y)
<-- Lim(var, tar, dir, x)+Lim(var, tar, dir, y);

/*  Global default rule : evaluate expression  */

700 # Lim(_var, _tar, _dir, exp_IsFunction)
<-- Eval(MapArgs(exp,"LimitArgs"));

LimitArgs(_arg) <-- Lim(var,tar,dir,arg);
UnFence("LimitArgs",1); /* Allow LimitArgs to have access to the local variables of the caller. */

701 # Lim(_var, _tar, _dir, _exp)
<-- Eval(ApplyPure("Subst", {var, tar, exp}));


/*  Limit without direction  */

10 # Lim(_var, tar_IsInfinity, _exp) <-- Lim(var, tar, None, exp);

20 # Lim(_var, _tar, _exp)
<-- [
  Local(l); l := Lim(var, tar, Left, exp);
  If ( l = Lim(var, tar, Right, exp),
       l,
       Undefined
     );
];

100 # LimInf(_var, _tar, _dir, Cos( _exp ))_IsInfinity(Lim(var,tar,dir,exp)) <-- -1;
100 # LimInf(_var, _tar, _dir, Sin( _exp ))_IsInfinity(Lim(var,tar,dir,exp)) <-- -1;

500 # LimInf(_var, _tar, _dir, _exp) <-- Lim(var,tar,dir,exp);

100 # LimSup(_var, _tar, _dir, Cos( _exp ))_IsInfinity(Lim(var,tar,dir,exp)) <-- 1;
100 # LimSup(_var, _tar, _dir, Sin( _exp ))_IsInfinity(Lim(var,tar,dir,exp)) <-- 1;

500 # LimSup(_var, _tar, _dir, _exp) <-- Lim(var,tar,dir,exp);


/* User-callable function */

(Limit(_var,_lim)(_fie)) <-- [
    Check(IsAtom(var) And Not(IsNumber(var)), "Limit: " : (ToString() Write(var)) : " is not a valid variable");
    Lim(var,lim,fie);
];

(Limit(_var,_lim,_direction)(_fie)) <-- [
    Check(IsAtom(var) And Not(IsNumber(var)), "Limit: " : (ToString() Write(var)) : " is not a valid variable");
    Lim(var,lim,direction,fie);
];

UnFence("Limit",3);
