package com.metaweb.lessen.expr;

import com.metaweb.lessen.Scope;
import com.metaweb.lessen.tokens.Color;
import com.metaweb.lessen.tokens.NumericToken;
import com.metaweb.lessen.tokens.NumericWithUnitToken;
import com.metaweb.lessen.tokens.OneTokenColor;
import com.metaweb.lessen.tokens.Token;
import com.metaweb.lessen.tokens.Token.Type;

public class OperatorCall implements Evaluable {
    final protected String      _operator;
    final protected Evaluable   _left;
    final protected Evaluable   _right;
    
    public OperatorCall(String operator, Evaluable left, Evaluable right) {
        _operator = operator;
        _left = left;
        _right = right;
    }

    @Override
    public Token eval(Scope scope) {
        Token left = _left != null ? _left.eval(scope) : null;
        Token right = _right != null ? _right.eval(scope) : null;
        
        if (left != null) {
            Color c = OneTokenColor.tokenToColor((Token) left);
            if (c != null) {
                left = c;
            }
        }
        if (right != null) {
            Color c = OneTokenColor.tokenToColor((Token) right);
            if (c != null) {
                right = c;
            }
        }
        
        if (left != null && left instanceof Color) {
            if (right != null) {
                if (right instanceof Color) {
                    if (_operator.equals("+") || _operator.equals("-")) {
                        return ((Color) left).performOperation(_operator, (Color) right);
                    }
                } else if (right instanceof NumericToken) {
                    NumericToken t = (NumericToken) right;
                    
                    if (_operator.equals("*") || _operator.equals("/")) {
                        if (t.type == Type.Number) {
                            return ((Color) left).performOperation(_operator, t.n);
                        } else if (t.type == Type.Percentage) {
                            return ((Color) left).performOperation(_operator, t.n.doubleValue() / 100);
                        }
                    } else if (_operator.equals("+") || _operator.equals("-")) {
                        if (t.type == Type.Percentage) {
                            return ((Color) left).performOperation("*",
                                1 + (_operator.equals("+") ? 1 : -1) * t.n.doubleValue() / 100);
                        }
                    }
                }
            }
            return null;
            
        } else if (right != null && right instanceof Color) {
            if (left != null && _operator.equals("*")) {
                if (left instanceof NumericToken) {
                    NumericToken t = (NumericToken) left;
                    if (t.type == Type.Number) {
                        return ((Color) right).performOperation(_operator, t.n);
                    } else if (t.type == Type.Percentage) {
                        return ((Color) right).performOperation(_operator, t.n.doubleValue() / 100);
                    }
                }
            }
            return null;
        }
        
        if (left != null && left instanceof NumericToken) {
            if (right != null) {
                NumericToken leftT = (NumericToken) left;
                
                if (right instanceof NumericToken) {
                    NumericToken rightT = (NumericToken) right;
                    
                    NumericToken resultT = performNumericOperation(_operator, leftT, rightT);
                    if (resultT != null) {
                        return resultT;
                    }
                }
            }
            return null;
        }
        
        return null;
    }
    
    static public NumericToken performNumericOperation(String op, NumericToken leftT, double n) {
        double leftN = leftT.n.doubleValue();
        
        if (op.equals("+")) {
            return makeNumericToken(leftT, leftN + n);
        } else if (op.equals("-")) {
            return makeNumericToken(leftT, leftN - n);
        } else if (op.equals("*")) {
            return makeNumericToken(leftT, leftN * n);
        } else if (op.equals("/")) {
            return makeNumericToken(leftT, leftN / n);
        }
        return null;
    }
    
    static public NumericToken performNumericOperation(String op, NumericToken leftT, NumericToken rightT) {
        double leftN = leftT.n.doubleValue();
        double rightN = rightT.n.doubleValue();
        
        if (leftT.type == Type.Number) {
            if (rightT.type == Type.Number) {
                return performNumericOperation(op, leftT, rightN);
            } else if (rightT.type == Type.Percentage) {
                if (op.equals("+")) {
                    // number + percentage
                    return makeNumberToken(leftT, leftN * (1 + rightN / 100));
                } else if (op.equals("-")) {
                    // number - percentage
                    return makeNumberToken(leftT, leftN * (1 - rightN / 100));
                } else if (op.equals("*")) {
                    // number * percentage
                    return makePercentageToken(leftT, leftN * rightN);
                } // else: number / percentage is meaningless
                
            } else if (rightT.type == Type.Dimension) {
                String rightUnit = ((NumericWithUnitToken) rightT).unit;
                
                if (op.equals("+")) {
                    // number + dimension
                    return makeDimensionToken(leftT, leftN + rightN, rightUnit);
                } else if (op.equals("-")) {
                    // number - dimension
                    return makeDimensionToken(leftT, leftN - rightN, rightUnit);
                } else if (op.equals("*")) {
                    // number * dimension
                    return makeDimensionToken(leftT, leftN * rightN, rightUnit);
                } // else: number / dimension is meaningless
            }
        } else if (leftT.type == Type.Percentage) {
            if (rightT.type == Type.Number || rightT.type == Type.Percentage) {
                if (op.equals("+")) {
                    return makePercentageToken(leftT, leftN + rightN);
                } else if (op.equals("-")) {
                    return makePercentageToken(leftT, leftN - rightN);
                } else if (op.equals("*")) {
                    return makePercentageToken(leftT, leftN * rightN);
                } else if (op.equals("/")) {
                    return makePercentageToken(leftT, leftN / rightN);
                }
            } else if (rightT.type == Type.Dimension) {
                String rightUnit = ((NumericWithUnitToken) rightT).unit;
                
                if (op.equals("*")) {
                    // number - percentage
                    return makeDimensionToken(leftT, leftN * rightN / 100, rightUnit);
                }
                // number / percentage is meaningless
            }
        } else if (leftT.type == Type.Dimension) {
            String leftUnit = ((NumericWithUnitToken) leftT).unit;
            
            if (rightT.type == Type.Percentage) {
                if (op.equals("+")) {
                    // dimension + percentage
                    return makeDimensionToken(leftT, leftN * (1 + rightN / 100), leftUnit);
                } else if (op.equals("-")) {
                    // dimension - percentage
                    return makeDimensionToken(leftT, leftN * (1 - rightN / 100), leftUnit);
                } else if (op.equals("*")) {
                    // dimension * percentage
                    return makeDimensionToken(leftT, leftN * rightN, leftUnit);
                } // else: dimension / percentage is meaningless
                
            } else if (rightT.type == Type.Number || 
                (rightT.type == Type.Dimension && 
                 leftUnit.equals(((NumericWithUnitToken) rightT).unit))) {
                
                if (op.equals("+")) {
                    return makeDimensionToken(leftT, leftN + rightN, leftUnit);
                } else if (op.equals("-")) {
                    return makeDimensionToken(leftT, leftN - rightN, leftUnit);
                } else if (op.equals("*")) {
                    return makeDimensionToken(leftT, leftN * rightN, leftUnit);
                } else if (op.equals("/")) {
                    return makeDimensionToken(leftT, leftN / rightN, leftUnit);
                }
            }
        }
        return null;
    }
    
    static public NumericToken makeNumericToken(Token t, double d) {
        return t.type == Type.Percentage ?
                makePercentageToken(t, d) : makeNumberToken(t, d);
    }
    
    static public NumericToken makeNumberToken(Token t, double d) {
        Number n = doubleToNumber(d);
        
        return new NumericToken(Type.Number, t.start, t.end, n.toString(), n);
    }
    
    static public NumericToken makePercentageToken(Token t, double d) {
        Number n = doubleToNumber(d);
        
        return new NumericToken(Type.Percentage, t.start, t.end, n.toString() + "%", n);
    }
    
    static public NumericToken makeDimensionToken(Token t, double d, String unit) {
        Number n = doubleToNumber(d);
        
        return new NumericWithUnitToken(Type.Dimension, t.start, t.end, n.toString() + unit, n, unit);
    }
    
    static public Number doubleToNumber(double n) {
        if (n - Math.round(n) == 0f) {
            return Math.round(n);
        } else {
            return n;
        }
    }
}
