package com.adgad.kboard;

import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.vdurmont.emoji.Emoji;
import com.vdurmont.emoji.EmojiManager;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;

/**
 * Created by arjun on 27/12/16.
 */


@SuppressWarnings("ALL")
public class KCommands {

    private static final int MAX_LOOKBACK = 500;

    private final InputConnection inputConnection;
    private final EditorInfo inputEditor;
    private final boolean mAutoSpace;
    private final boolean mPassiveAggressive;
    private final Collection<Emoji> mEmoji;
    private final List<String> mTextKeys = new ArrayList<>();
    private final Map<String,String> mCommandKeys = new HashMap<>();
    private String buffer = null;
    private final KboardIME mIme;

    public KCommands(
            KboardIME ime,
            InputConnection ic,
            EditorInfo ei,
            List<String> keys,
            boolean autoSpace,
            boolean passiveAggressive) {
        inputConnection = ic;
        inputEditor = ei;
        mAutoSpace = autoSpace;
        mPassiveAggressive = passiveAggressive;
        List<String> mKeys = keys;
        mIme = ime;
        for (String key: mKeys) {
            if (!(key.startsWith("/") && key.contains("!"))) {
                mTextKeys.add(key);
            } else {
                mCommandKeys.put(key.split("!", 2)[0].substring(1), key.split("!", 2)[1]);
            }
        }
        mEmoji = EmojiManager.getAll();
    }

    private void commitText(String key) {
        String word = "";
        if(mAutoSpace && inputConnection != null && inputConnection.getTextBeforeCursor(1,0) != null && inputConnection.getTextBeforeCursor(1,0).length() > 0) {
            word = " ";
        }

        if(mPassiveAggressive && key.length() > 0) {
            String lastLetter = key.substring(key.length() - 1);
            key = key.substring(0,1).toUpperCase() + key.substring(1);
            key = key.replace('!', '.');
            if(!lastLetter.equals(lastLetter.toUpperCase())) {
                key = key + ".";
            }
        }
        Objects.requireNonNull(inputConnection).commitText(word + key, 1);
    }

    private int getCursorPosition() {
        ExtractedText extracted = inputConnection.getExtractedText(
                new ExtractedTextRequest(), 0);
        if (extracted == null) {
            return -1;
        }
        return extracted.startOffset + extracted.selectionStart;
    }

    //delete character
    public void d(int n) {
        CharSequence selected = inputConnection.getSelectedText(0);

        if(selected == null || selected.length() == 0 ) {
            buffer = (inputConnection.getTextBeforeCursor(n, 0).toString());
            inputConnection.deleteSurroundingText(n,0);
        } else {
            buffer = selected.toString();
            inputConnection.commitText("", 1);
        }
    }

    //delete previous word
    public void dw(int n) {
        StringBuilder buf = new StringBuilder();
        for(int i =0; i<n;i++) {
            final int charactersToGet = 30;
            final String splitRegexp = " ";

            // delete trailing spaces
            while (inputConnection.getTextBeforeCursor(1, 0).toString().equals(splitRegexp)) {
                buf.append(inputConnection.getTextBeforeCursor(1, 0).toString());
                inputConnection.deleteSurroundingText(1, 0);
            }

            // delete last word letters
            String[] words = inputConnection.getTextBeforeCursor(charactersToGet, 0).toString().split(splitRegexp);
            String lastWord = words[words.length - 1];
            buf.append(lastWord);
            inputConnection.deleteSurroundingText(lastWord.length(), 0);
        }
       buffer = (buf.toString());
    }

    //delete to a character
    public void dt(int n, String parameter) {
        StringBuilder buf = new StringBuilder();
        for(int i =0; i<n;i++) {
            final int charactersToGet = 50;
            final String splitRegexp = parameter.replace("\\", "\\\\");

            // delete trailing spaces
            while (inputConnection.getTextBeforeCursor(1, 0).toString().equals(splitRegexp)) {
                buf.append(inputConnection.getTextBeforeCursor(1, 0).toString());
                inputConnection.deleteSurroundingText(1, 0);
            }

            // delete last word letters
            String[] words = inputConnection.getTextBeforeCursor(charactersToGet, 0).toString().split(splitRegexp);
            String lastWord = words[words.length - 1];
            buf.append(parameter).append(lastWord);
            inputConnection.deleteSurroundingText(lastWord.length() + parameter.length(), 0);
        }
        buffer = (buf.toString());
    }

    //delete everything
    public void dd(int n) {
        inputConnection.performContextMenuAction(android.R.id.selectAll);
        buffer = (inputConnection.getSelectedText(0).toString());
        inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
        inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
    }

    //delete selected or all
    public void ds(int n) {
        CharSequence selected = inputConnection.getSelectedText(0);
        if(selected == null || selected.length() == 0 ) {
            inputConnection.performContextMenuAction(android.R.id.selectAll);
        }
        buffer = (inputConnection.getSelectedText(0).toString());
        inputConnection.commitText("", 1);
    }

    //copy all
    public void yy(int n) {
        int currentPosition = getCursorPosition();
        inputConnection.performContextMenuAction(android.R.id.selectAll);
        buffer = (inputConnection.getSelectedText(0).toString());
        inputConnection.performContextMenuAction(android.R.id.copy);
        inputConnection.setSelection(currentPosition, currentPosition);
    }

    //copy selected
    public void y(int n) {
        buffer = (inputConnection.getSelectedText(0).toString());
        inputConnection.performContextMenuAction(android.R.id.copy);
    }

    //copy selected or all
    public void ys(int n) {
        CharSequence selected = inputConnection.getSelectedText(0);
        if(selected == null || selected.length() == 0 ) {
            int currentPosition = getCursorPosition();
            inputConnection.performContextMenuAction(android.R.id.selectAll);
            buffer = (inputConnection.getSelectedText(0).toString());
            inputConnection.performContextMenuAction(android.R.id.copy);
            inputConnection.setSelection(currentPosition, currentPosition);
        } else {
            buffer = (inputConnection.getSelectedText(0).toString());
        }
    }


    //select all
    public void sa(int n) {
        int currentPosition = getCursorPosition();
        inputConnection.performContextMenuAction(android.R.id.selectAll);
    }


    //insert text
    public void i(int n, String parameter) {
        for(int i=0;i<n;i++) {
            if (buffer != null) {
                commitText(parameter.replaceAll("\\$0", buffer));
            } else {
                commitText(parameter);
            }
        }

    }

    //insert text raw (without autospace etc)
    public void iraw(int n, String parameter) {
        for(int i=0;i<n;i++) {
            if (buffer != null) {
                inputConnection.commitText(parameter.replaceAll("\\$0", buffer), 1);
            } else {
                inputConnection.commitText(parameter, 1);
            }
        }
    }

    //
    public void fancy(int n, String parameter) {
        for(int i=0;i<n;i++) {
            inputConnection.commitText(ConvertUnicode.convert(buffer, parameter), 1);
        }
    }

    //find and replace
    public void fr(int n, String parameter) {
        String from = parameter.split(";")[0];
        String to = parameter.split(";")[1];
        dd(1);
        String contents = buffer;
        inputConnection.commitText(contents.replaceAll(from, to), 1);

    }

    //send
    public void s(int n) {
        if((inputEditor.imeOptions & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEND) {
            inputConnection.performEditorAction(EditorInfo.IME_ACTION_SEND);
        }
    }

    //paste from buffer
    public void p(int n) {
        String str = buffer;
        if(str != null) {
            for(int i=0;i<n;i++) {
                inputConnection.commitText(str, 0);
            }
        } else {
            inputConnection.performContextMenuAction(android.R.id.paste);
        }

    }

    //paste from clipboard
    public void pc(int n) {
        for(int i=0;i<n;i++) {
            inputConnection.performContextMenuAction(android.R.id.paste);
        }
    }

    //make uppercase
    public void upper(int n, String parameter) {
        for(int i=0;i<n;i++) {
            String lastBufferWord = buffer;
            if (lastBufferWord != null) {
                inputConnection.commitText(parameter.replaceAll("\\$0", lastBufferWord).toUpperCase(), 1);
            } else {
                inputConnection.commitText(parameter.toUpperCase(), 1);
            }
        }
    }

    //make lowercase
    public void lower(int n, String parameter) {
        for(int i=0;i<n;i++) {
            String lastBufferWord = buffer;
            if (lastBufferWord != null) {
                inputConnection.commitText(parameter.replaceAll("\\$0", lastBufferWord).toLowerCase(), 1);
            } else {
                inputConnection.commitText(parameter.toLowerCase(), 1);
            }
        }
    }

    //random from kboard keys
    public void rnd(int n) {
        rnd(n, null);
    }

    //random from list or all kboard keys
    public void rnd(int n, String parameter) {
        List<String> textKeys;
        if(parameter!= null && parameter.length() > 0) {
            textKeys = new ArrayList<>(Arrays.asList(parameter.split(";", 100)));
        } else {
            textKeys = mTextKeys;
        }
        for(int i=0; i<n; i++) {
            Random random = new Random();
            int index = random.nextInt(textKeys.size());
            i(1, textKeys.get(index));
        }
    }

    //random emoji
    public void rnde(int n) {
        for(int i=0; i<n; i++) {
            Random random = new Random();
            int index = random.nextInt(mEmoji.size());
            iraw(1, ((Emoji) mEmoji.toArray()[index]).getUnicode());
        }
    }

    //move cursor left
    public void j(int n) {
        int position = getCursorPosition() - n;
        if(position < 0) {
            position = 0;
        }
        inputConnection.setSelection(position, position);
    }

    //move cursor right
    public void k(int n) {
        int position = getCursorPosition() + n;
        CharSequence textAfterCursor = inputConnection.getTextAfterCursor(n, 0);
        if(textAfterCursor.length() == n) {
            inputConnection.setSelection(position, position);
        } else {
            inputConnection.setSelection(position-(textAfterCursor.length() - n), position-(textAfterCursor.length() - n));
        }
    }

    //move back a word
    public void b(int n) {
        for(int i=0;i<n;i++) {
            final String splitRegexp = " ";


            // delete last word letters
            String[] words = inputConnection.getTextBeforeCursor(MAX_LOOKBACK, 0).toString().split(splitRegexp);
            String lastWord = words[words.length - 1];
            int position = getCursorPosition() - lastWord.length() - 1;
            if(position < 0) {
                position = 0;
            }
            inputConnection.setSelection(position, position);
        }

    }

    //move forward a word
    public void w(int n) {
        for(int i=0;i<n;i++) {
            final String splitRegexp = " ";

            String[] words = inputConnection.getTextAfterCursor(MAX_LOOKBACK, 0).toString().split(splitRegexp);
            String nextWord = words[0];

            int position = getCursorPosition() + nextWord.length() + 1;
            if(inputConnection.getTextAfterCursor(nextWord.length() + 1, 0).length() == nextWord.length() + 1) {
                inputConnection.setSelection(position, position);
            } else {
                inputConnection.setSelection(position-1, position-1);
            }
        }
    }

    //move to start of line
    public void startOfLine(int n) {
        for(int i=0;i<n;i++) {
            final String splitRegexp = "(?<=\\n)";

            String[] lines = inputConnection.getTextBeforeCursor(MAX_LOOKBACK, 0).toString().split(splitRegexp);
            String lastLine = lines[lines.length - 1];
            int position = getCursorPosition() - lastLine.length();
            if(position < 0) {
                position = 0;
            }
            inputConnection.setSelection(position, position);
        }
    }

    //move to end of line
    public void endOfLine(int n) {
        for(int i=0;i<n;i++) {
            final String splitRegexp = "\\n";

            String[] lines = inputConnection.getTextAfterCursor(MAX_LOOKBACK, 0).toString().split(splitRegexp);
            int nextLineLength = lines[0].equals("") ? lines[1].length() + 1 : lines[0].length();
            int position = getCursorPosition() + nextLineLength;
            inputConnection.setSelection(position, position);
        }
    }

    public void curl(int n, String parameter) {
        // Instantiate the RequestQueue.
        RequestQueue queue = Volley.newRequestQueue(mIme);
        final int repeat = n;

        // Request a string response from the provided URL.
        StringRequest stringRequest = new StringRequest(Request.Method.GET, parameter,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        i(repeat, response);
                    }


                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {

            }
        }) {
            @Override
            public Map<String, String> getHeaders(){
                Map<String, String> headers = new HashMap<>();
                headers.put("User-agent", "curl");
                headers.put("Accept", "text/plain");
                return headers;
            }
        };

// Add the request to the RequestQueue.
        queue.add(stringRequest);
    }

    //execute subcommand
    public void e(int n, String cmd) {
        //Remove leading !
        if(cmd.startsWith("!")) {
            cmd = cmd.substring(1);
        }
        for(int i=0;i<n;i++) {
            String commands[];
            if(cmd.matches("^\\d+e.*")) {
                commands = new String[1];
                commands[0] = cmd;
                int numberOfTimes = cmd.indexOf("e") > 0 ? Integer.parseInt(cmd.substring(0, cmd.indexOf("e"))) : 1;
                String parameter = cmd.substring(cmd.indexOf("(") + 1, cmd.lastIndexOf(")"));
                e(numberOfTimes, parameter);
            } else {
                commands = cmd.split(",");
                for(String command : commands) {
                    String commandMethod;
                    String parameter = null;
                    int numberOfTimes = 1;
                    String[] commandMethodParts = command.split("(\\((?!\\))|,|(?<!\\()\\))"); //split out parameter in brackets
                    if(commandMethodParts.length > 1) { //has parameter
                        commandMethod = commandMethodParts[0];
                        parameter = commandMethodParts[1].replaceAll("\\$0", buffer);
                    } else {
                        commandMethod = commandMethodParts[0];
                    }


                    String[] commandParts = commandMethod.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)"); //split between number and non-number
                    if(commandParts.length > 1) { //has numericPart
                        numberOfTimes = Integer.parseInt(commandParts[0]);
                        commandMethod = commandParts[1];
                    } else {
                        commandMethod = commandParts[0];
                    }

                    execute(commandMethod, numberOfTimes, parameter);

                }
            }


        }

    }

    private void execute(String cmd, int n, String parameter) {
        inputConnection.beginBatchEdit();

        if(cmd.equals("^")) {
            cmd = "startOfLine";
        }
        if(cmd.equals("$")) {
            cmd = "endOfLine";
        }
        try {
            if(parameter != null) {
                getClass().getMethod(cmd, int.class, String.class).invoke(this, n, parameter);
            } else {
                getClass().getMethod(cmd, int.class).invoke(this, n);

            }
            return;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            if(mCommandKeys.containsKey(cmd) && inputConnection != null) {
                e(n, mCommandKeys.get(cmd));
            }
            e.printStackTrace();
        }
        inputConnection.endBatchEdit();
    }

}