/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.weex.el.parse;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * Created by furture on 2017/8/28.
 */
public class Operators {


    public static Object dot(Token left, Token right, Object context){
        if(left == null || right == null){
            return null;
        }
        Object leftValue = left.execute(context);
        if(leftValue == null){
            return null;
        }
        Object value =  null;
        if(right.getType() != Token.TYPE_IDENTIFIER){
            Object identifter = right.execute(context);
            if(identifter instanceof Double){
                identifter = ((Double) identifter).intValue();
            }
            String key = identifter == null ? "" : identifter.toString().trim();
            value = Operators.el(leftValue, key);
        }else{
            value = right.execute(leftValue);
        }
        if(value != null){
            return value;
        }
        return  specialKey(leftValue, right.getToken());
    }

    /**
     * get key's value on object
     * */
    public static  Object el(Object context, String key){
        if(context == null){
            return  null;
        }
        // TODO
//        if(context instanceof CellRenderContext){
//            if(WXEnvironment.isApkDebugable()){
//                throw new IllegalArgumentException("rong context CellRenderContext, you should pass it's stack");
//            }
//            context = ((CellRenderContext) context).stack;
//        }
        if(context instanceof ArrayStack){
            ArrayStack stack = (ArrayStack) context;
            for(int index=stack.size()-1; index >= 0; index--){
                Object value = stack.get(index);
                if(value instanceof  Map){
                    Map map = (Map) value;
                    if(map.containsKey(key)){
                        return map.get(key);
                    }
                }
            }
        }
        if(context instanceof Stack){
            Stack stack = (Stack) context;
            for(int index=stack.size()-1; index >= 0; index--){
                Object value = stack.get(index);
                if(value instanceof  Map){
                    Map map = (Map) value;
                    if(map.containsKey(key)){
                        return map.get(key);
                    }
                }
            }
        }

        if(context instanceof  Map){
            return ((Map) context).get(key);
        }

        if(context instanceof List){
            List list = (List) context;
            try{
                return list.get(Integer.parseInt(key));
            }catch (Exception e){}
        }
        if(context.getClass().isArray()){
            try{
                return Array.get(context, Integer.parseInt(key));
            }catch (Exception e){}
        }
        return  null;
    }

    public static  Object specialKey(Object leftValue, String key){
        if("length".equals(key)){
            if(leftValue instanceof  CharSequence){
                return ((CharSequence) leftValue).length();
            }
            if(leftValue instanceof  Map){
                return  ((Map) leftValue).size();
            }
            if(leftValue instanceof  Map){
                return  ((Map) leftValue).size();
            }
            if(leftValue instanceof  List){
                return  ((List) leftValue).size();
            }
            if(leftValue.getClass().isArray()){
                return Array.getLength(leftValue);
            }
        }
        return null;
    }

    public static Object plus(Token left, Token right, Object context){
        Object leftValue = null;
        Object rightValue = null;
        if(left != null){
            leftValue = left.execute(context);
        }
        if(right  != null){
            rightValue = right.execute(context);
        }
        if(leftValue instanceof  CharSequence || rightValue instanceof  CharSequence){
            if(leftValue == null){
                return  rightValue;
            }
            return leftValue.toString() + (rightValue == null ? "" : rightValue.toString());
        }
        if(leftValue instanceof  Number || rightValue instanceof Number){
            return getNumber(leftValue) + getNumber(rightValue);
        }
        if(leftValue == null && rightValue == null){
            return  null;
        }
        if(leftValue == null){
            return  rightValue.toString();
        }
        return leftValue.toString() + (rightValue == null ? "" : rightValue.toString());
    }


    public static Object sub(Token left, Token right, Object context){
        Object leftValue = null;
        Object rightValue = null;
        if(left != null){
            leftValue = left.execute(context);
        }
        if(right  != null){
            rightValue = right.execute(context);
        }
        return getNumber(leftValue) - getNumber(rightValue);
    }

    public static Object div(Token left, Token right, Object context){
        Object leftValue = null;
        Object rightValue = null;
        if(left != null){
            leftValue = left.execute(context);
        }
        if(right  != null){
            rightValue = right.execute(context);
        }
        return getNumber(leftValue)/getNumber(rightValue);
    }

    public static Object mul(Token left, Token right, Object context){
        Object leftValue = null;
        Object rightValue = null;
        if(left != null){
            leftValue = left.execute(context);
        }
        if(right  != null){
            rightValue = right.execute(context);
        }
        return getNumber(leftValue)*getNumber(rightValue);
    }

    public static Object mod(Token left, Token right, Object context){
        Object leftValue = null;
        Object rightValue = null;
        if(left != null){
            leftValue = left.execute(context);
        }
        if(right  != null){
            rightValue = right.execute(context);
        }
        return (getNumber(leftValue))%(getNumber(rightValue));
    }

    /**
     * condition expression
     * */
    public static Object condition(Token selfs, Token first, Token second, Object context){
        boolean value = false;
        if(selfs != null){
            value = isTrue(selfs.execute(context));
        }
        if(value){
            if(first != null){
                return  first.execute(context);
            }
        }else{
            if(second != null){
                return  second.execute(context);
            }
        }
        return null;
    }


    /**
     * is token value is true
     * */
    public static boolean tokenTrue(Token selfs, Object context) {
        if(selfs == null){
            return false;
        }
        Object value = selfs.execute(context);
        return  isTrue(value);
    }

    /**
     * get token number
     * */
    public static double tokenNumber(Token self,  Object context) {
        if(self == null){
            return  0;
        }
        Object value = self.execute(context);
        return  getNumber(value);
    }



    /**
     * isEquls operation
     * */
    public static boolean isEquals(Token left, Token right, Object context){
        if(left == null && right == null){
            return  true;
        }
        Object leftValue =  null;
        if(left != null){
            leftValue  = left.execute(context);
        }
        Object rightValue = null;
        if(right != null) {
            rightValue = right.execute(context);
        }
        if(leftValue == null){
            if(rightValue == null){
                return  true;
            }
            if(rightValue instanceof  CharSequence){
                if(isEmpty(rightValue.toString())){
                    return  true;
                }
            }
            return  false;
        }
        if(rightValue == null){
            if(isEmpty(leftValue.toString())){
                return  true;
            }
            return  false;
        }
        if(leftValue instanceof  Number){
            if(rightValue instanceof  Number){
                return  ((Number) leftValue).doubleValue() == ((Number) rightValue).doubleValue();
            }
            return ((Number) leftValue).doubleValue() == getNumber(rightValue);
        }
        if(rightValue instanceof  Number){
            return getNumber(leftValue) == ((Number) rightValue).doubleValue();
        }
        if(leftValue instanceof CharSequence
                ||  rightValue instanceof CharSequence){
            return  leftValue.toString().trim().equals(rightValue.toString().trim());
        }
        return  leftValue.equals(rightValue);
    }


    /**
     * check whether value is true
     * */
    public static boolean isTrue(Object value){
        if(value == null){
            return  false;
        }
        if(value instanceof  Number){
            return ((Number) value).doubleValue() != 0;
        }
        String bool = value.toString().trim();
        if("false".equals(bool)
                || "undefined".equals(bool)
                || "null".equals(bool)){
            return  false;
        }
        if(isEmpty(bool)){
            return  false;
        }
        return  true;
    }

    /**
     * check String value is empty
     * */
    public static boolean isEmpty(String value){
        if (value == null){
            return  true;
        }
        for(int i=0; i<value.length(); i++){
            if(value.charAt(i) != ' '){
                return  false;
            }
        }
        return true;
    }


    /**
     * get number
     * */
    public static double getNumber(Object value){
        if(value == null){
            return  0;
        }
        if(value instanceof  Number){
            return  ((Number) value).doubleValue();
        }
        try{
            return Double.parseDouble(value.toString());
        }catch (Exception e){return  0;}
    }

    public static boolean isOpEnd(String op){
        return isOpEnd(op.charAt(0));
    }
    /**
     * op end, has none operation, should not enter operator stack.
     * */
    public static boolean isOpEnd(char op){
        if(op == BRACKET_END
                || op == ARRAY_END
                || op == SPACE
                || op == ARRAY_SEPRATOR){
            return true;
        }
        return  false;
    }

    /**
     * is not
     * */
    public static boolean isDot(String opStr){
        char op = opStr.charAt(0);
        return  op == DOT || op == ARRAY_START;
    }




    public static final char BRACKET_END = ')';
    public static final String BRACKET_END_STR = ")";
    public static final char BRACKET_START = '(';
    public static final String BRACKET_START_STR = "(";
    public static final char QUOTE = '"';
    public static final char SINGLE_QUOTE = '\'';
    public static final char DOT = '.';
    public static final String DOT_STR = ".";
    public static final char ARRAY_START = '[';
    public static final String ARRAY_START_STR = "[";
    public static final char ARRAY_SEPRATOR = ',';
    public static final String ARRAY_SEPRATOR_STR = ",";
    public static final char ARRAY_END = ']';
    public static final String ARRAY_END_STR = "]";
    public static final String SPACE_STR = " ";
    public static final char SPACE = ' ';
    public static final char BLOCK_START = '{';
    public static final String BLOCK_START_STR = "{";
    public static final char BLOCK_END = '}';
    public static final String BLOCK_END_STR = "}";
    public static final char DOLLAR = '$';
    public static final String DOLLAR_STR = "$";

    /**
     * condition
     * */
    public static final char  CONDITION_IF = '?';
    public static final String  CONDITION_IF_STRING = "?";
    public static final char  CONDITION_IF_MIDDLE = ':';



    /**
     * match
     * */
    public static final String PLUS ="+";
    public static final String SUB = "-";
    public static final String MUL = "*";
    public static final String DIV = "/";
    public static final String MOD = "%";



    public static final String AND  = "&&";
    public static final String OR  = "||";
    /**
     * and operator
     * */
    public static final String EQUAL = "===";
    public static final String EQUAL2 = "==";
    public static final String NOT_EQUAL = "!==";
    public static final String NOT_EQUAL2 = "!=";
    public static final String AND_NOT = "!";


    public static final String G = ">";
    public static final String GE = ">=";
    public static final String LE = "<=";
    public static final String L = "<";





    /**
     * https://github.com/jquery/esprima/blob/master/src/parser.ts
     * */
    public static Map<String, Integer> OPERATORS_PRIORITY = new HashMap<>();
    static {
        OPERATORS_PRIORITY.put(BLOCK_END_STR, 0);
        OPERATORS_PRIORITY.put(BRACKET_END_STR, 0);
        OPERATORS_PRIORITY.put(SPACE_STR, 0);
        OPERATORS_PRIORITY.put(ARRAY_SEPRATOR_STR, 0);
        OPERATORS_PRIORITY.put(ARRAY_END_STR, 0);


        OPERATORS_PRIORITY.put(OR, 1);
        OPERATORS_PRIORITY.put(AND, 1);

        OPERATORS_PRIORITY.put(EQUAL, 2);
        OPERATORS_PRIORITY.put(EQUAL2, 2);
        OPERATORS_PRIORITY.put(NOT_EQUAL, 2);
        OPERATORS_PRIORITY.put(NOT_EQUAL2, 2);

        OPERATORS_PRIORITY.put(G, 7);
        OPERATORS_PRIORITY.put(GE, 7);
        OPERATORS_PRIORITY.put(L, 7);
        OPERATORS_PRIORITY.put(LE, 8);

        OPERATORS_PRIORITY.put(PLUS, 9);
        OPERATORS_PRIORITY.put(SUB, 9);
        OPERATORS_PRIORITY.put(MUL, 10);
        OPERATORS_PRIORITY.put(DIV, 10);
        OPERATORS_PRIORITY.put(MOD, 10);
        OPERATORS_PRIORITY.put(AND_NOT, 11);

        OPERATORS_PRIORITY.put(DOT_STR, 15);
        
        OPERATORS_PRIORITY.put(ARRAY_START_STR, 16);


        OPERATORS_PRIORITY.put(BRACKET_START_STR, 17);
        OPERATORS_PRIORITY.put(BLOCK_START_STR, 17);


    }


    public static final Map<String,Object> KEYWORDS = new HashMap<>();
    static {
        KEYWORDS.put("null", null);
        KEYWORDS.put("true", Boolean.TRUE);
        KEYWORDS.put("false", Boolean.FALSE);
        KEYWORDS.put("undefined", null);
    }

}
