/*
 * Navit, a modular navigation system.
 * Copyright (C) 2005-2008 Navit Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

package org.navitproject.navit;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Message;
import android.support.annotation.RequiresApi;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;

import java.util.ArrayList;
import java.util.List;


class NavitGraphics {
    private static final String            TAG = "NavitGraphics";
    private static final long              TIME_FOR_LONG_PRESS = 300L;
    private static boolean                 sInMap;
    private final NavitGraphics            mParentGraphics;
    private final ArrayList<NavitGraphics> mOverlays;
    private int                            mBitmapWidth;
    private int                            mBitmapHeight;
    private int                            mPosX;
    private int                            mPosY;
    private int                            mPosWraparound;
    private int                            mOverlayDisabled;
    private float                          mTrackballX;
    private float                          mTrackballY;
    private int                            mPaddingLeft;
    private int                            mPaddingRight;
    private int                            mPaddingTop;
    private int                            mPaddingBottom;
    private NavitView                      mView;
    private SystemBarTintView              mLeftTintView;
    private SystemBarTintView              mRightTintView;
    private SystemBarTintView              mTopTintView;
    private SystemBarTintView              mBottomTintView;
    private FrameLayout                    mFrameLayout;
    private RelativeLayout                 mRelativeLayout;
    private NavitCamera                    mCamera;
    private Navit                          mActivity;
    private boolean                        mTinting;


    @SuppressWarnings("unused")
    void setBackgroundColor(int bgcolor) {
        if (mLeftTintView != null) {
            mLeftTintView.setBackgroundColor(bgcolor);
        }
        if (mRightTintView != null) {
            mRightTintView.setBackgroundColor(bgcolor);
        }
        if (mTopTintView != null) {
            mTopTintView.setBackgroundColor(bgcolor);
        }
        if (mBottomTintView != null) {
            mBottomTintView.setBackgroundColor(bgcolor);
        }
    }

    private void setCamera(int useCamera) {
        if (useCamera != 0 && mCamera == null) {
            // mActivity.requestWindowFeature(Window.FEATURE_NO_TITLE);
            mCamera = new NavitCamera(mActivity);
            mRelativeLayout.addView(mCamera);
            mRelativeLayout.bringChildToFront(mView);
        }
    }

    private Rect get_rect() {
        Rect ret = new Rect();
        ret.left = mPosX;
        ret.top = mPosY;
        if (mPosWraparound != 0) {
            if (ret.left < 0) {
                ret.left += mParentGraphics.mBitmapWidth;
            }
            if (ret.top < 0) {
                ret.top += mParentGraphics.mBitmapHeight;
            }
        }
        ret.right = ret.left + mBitmapWidth;
        ret.bottom = ret.top + mBitmapHeight;
        if (mPosWraparound != 0) {
            if (mBitmapWidth < 0) {
                ret.right = ret.left + mBitmapWidth + mParentGraphics.mBitmapWidth;
            }
            if (mBitmapHeight < 0) {
                ret.bottom = ret.top + mBitmapHeight + mParentGraphics.mBitmapHeight;
            }
        }
        return ret;
    }

    private class NavitView extends View implements Runnable, MenuItem.OnMenuItemClickListener {
        int               mTouchMode = NONE;
        float             mOldDist = 0;
        static final int  NONE       = 0;
        static final int  DRAG       = 1;
        static final int  ZOOM       = 2;
        static final int  PRESSED    = 3;
        PointF mPressedPosition = null;
        // mContextMenuMapViewIntent is the ACTION_VIEW intent for a geo coordinates.
        // it is used when clicking on view in the map contextual menu
        Intent mContextMenuMapViewIntent = null;

        NavitView(Context context) {
            super(context);
            // assumption usefull for the KitKat tinting only
            sInMap = true;
        }

        boolean isDrag() {
            return mTouchMode == DRAG;
        }

        public void onWindowFocusChanged(boolean hasWindowFocus) {
            Log.v(TAG,"onWindowFocusChanged = " + hasWindowFocus);
            if (Navit.sShowSoftKeyboardShowing && hasWindowFocus) {
                InputMethodManager imm  = (InputMethodManager) mActivity
                        .getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.showSoftInput(this,InputMethodManager.SHOW_FORCED);
            }
            if (Navit.sShowSoftKeyboardShowing && !hasWindowFocus) {
                InputMethodManager imm  = (InputMethodManager) mActivity
                        .getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
            }
        }


        @TargetApi(21)
        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
            Log.d(TAG,"onApplyWindowInsets");
            if (mTinting) {
                mPaddingLeft = insets.getSystemWindowInsetLeft();
                mPaddingRight = insets.getSystemWindowInsetRight();
                mPaddingBottom = insets.getSystemWindowInsetBottom();
                mPaddingTop = insets.getSystemWindowInsetTop();
                Log.v(TAG, String.format("Padding -1a- left=%d top=%d right=%d bottom=%d",
                        mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom));
                int width = this.getWidth();
                int height = this.getHeight();
                if (width > 0 && height > 0) {
                    adjustSystemBarsTintingViews();
                    sizeChangedCallback(mSizeChangedCallbackID, width, height);
                }
            }
            return insets;
        }

        /**
         * Create an intent for a view action of a point provided by its x and y position on the display.
         *
         * @param x The x coordinates of the point on the display
         * @param y The y coordinates of the point on the display
         *
         * @return An intent to start to view the specified point on a third-party app on Android (can be null if a
         *         view action is not possible)
        **/
        Intent getViewIntentForDisplayPoint(int x, int y) {
            Intent result = null;

            /* Check if there is at least one application that can process a geo intent... */
            String selectedPointCoord = getCoordForPoint(x, y, true);
            Uri intentUri = Uri.parse("geo:" + selectedPointCoord);
            Intent defaultShareIntent = new Intent(Intent.ACTION_VIEW, intentUri);

            List<Intent> customShareIntentList = new ArrayList<>();
            List<ResolveInfo> intentTargetAppList;
            intentTargetAppList = this.getContext().getPackageManager().queryIntentActivities(defaultShareIntent, 0);

            String selfPackageName = this.getContext().getPackageName();

            if (!intentTargetAppList.isEmpty()) {
                for (ResolveInfo resolveInfo : intentTargetAppList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    Intent copiedIntent = new Intent(Intent.ACTION_VIEW, intentUri);
                    if (!packageName.equals(selfPackageName)) {
                        Log.d(TAG, "Adding package \"" + packageName + "\" to app chooser");
                        copiedIntent.setPackage(packageName);
                        copiedIntent.setClassName(
                                resolveInfo.activityInfo.packageName,
                                resolveInfo.activityInfo.name);
                        customShareIntentList.add(copiedIntent);
                    } else {
                        Log.d(TAG, "Excluding ourselves (package " + packageName + ") from intent targets");
                    }
                }
                if (customShareIntentList.size() > 0) {
                    result = Intent.createChooser(customShareIntentList.remove(customShareIntentList.size() - 1),
                            NavitAppConfig.getTstring(R.string.use_position_with));
                    result.putExtra(Intent.EXTRA_INITIAL_INTENTS,
                            customShareIntentList.toArray(new Intent[0]));
                    Log.d(TAG, "Preparing action intent (" + customShareIntentList.size() + 1
                               + " candidate apps) to view selected coord: " + selectedPointCoord);
                }
            }
            return result;
        }

        private static final int MENU_DRIVE_HERE = 1;
        private static final int MENU_VIEW = 2;
        private static final int MENU_CANCEL = 3;

        @Override
        protected void onCreateContextMenu(ContextMenu menu) {
            super.onCreateContextMenu(menu);
            String clickCoord = getCoordForPoint((int)mPressedPosition.x, (int)mPressedPosition.y, false);
            menu.setHeaderTitle(NavitAppConfig.getTstring(R.string.position_popup_title) + " " + clickCoord);
            menu.add(1, MENU_DRIVE_HERE, NONE, NavitAppConfig.getTstring(R.string.position_popup_drive_here))
                    .setOnMenuItemClickListener(this);
            mContextMenuMapViewIntent = getViewIntentForDisplayPoint((int)mPressedPosition.x, (int)mPressedPosition.y);
            if (mContextMenuMapViewIntent != null) {
                menu.add(1, MENU_VIEW, NONE,
                         NavitAppConfig.getTstring(R.string.position_popup_view)).setOnMenuItemClickListener(this);
            } else {
                Log.w(TAG, "No application available to handle ACTION_VIEW intent, view option not displayed");
            }
            menu.add(1, MENU_CANCEL, NONE,
                     NavitAppConfig.getTstring(R.string.cancel)).setOnMenuItemClickListener(this);
        }

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            int itemId = item.getItemId();
            if (itemId == MENU_DRIVE_HERE) {
                Message msg = Message.obtain(NavitCallbackHandler.sCallbackHandler,
                        NavitCallbackHandler.MsgType.CLB_SET_DISPLAY_DESTINATION.ordinal(),
                        (int) mPressedPosition.x, (int) mPressedPosition.y);
                msg.sendToTarget();
            } else if (itemId == MENU_VIEW) {
                mContextMenuMapViewIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                if (mContextMenuMapViewIntent.resolveActivity(this.getContext().getPackageManager()) != null) {
                    this.getContext().startActivity(mContextMenuMapViewIntent);
                } else {
                    Log.w(TAG, "View menu selected but intent is not handled by any application. Ignoring...");
                }
            }
            mContextMenuMapViewIntent = null;
            return true;
        }


        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawBitmap(mDrawBitmap, mPosX, mPosY, null);
            if (mOverlayDisabled == 0) {
                // assume we ARE in map view mode!
                sInMap = true;
                for (NavitGraphics overlay : mOverlays) {
                    if (overlay.mOverlayDisabled == 0) {
                        Rect r = overlay.get_rect();
                        canvas.drawBitmap(overlay.mDrawBitmap, r.left, r.top, null);
                    }
                }
            }
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            Log.d(TAG, "onSizeChanged pixels x=" + w + " pixels y=" + h);
            super.onSizeChanged(w, h, oldw, oldh);
            mDrawBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            mDrawCanvas = new Canvas(mDrawBitmap);
            mBitmapWidth = w;
            mBitmapHeight = h;

            handleResize(w, h);
            sizeChangedCallback(mSizeChangedCallbackID, w, h);
        }

        void doLongpressAction() {
            Log.d(TAG, "doLongpressAction enter");
            mActivity.openContextMenu(this);
        }


        @SuppressLint("ClickableViewAccessibility")
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            super.onTouchEvent(event);
            int x = (int) event.getX();
            int y = (int) event.getY();
            int switchValue = (event.getActionMasked());
            Log.d(TAG, "ACTION_ value =  " + switchValue);

            if (switchValue == MotionEvent.ACTION_DOWN) {
                mTouchMode = PRESSED;
                Log.d(TAG, "ACTION_DOWN mode PRESSED");
                if (!sInMap) {
                    buttonCallback(mButtonCallbackID, 1, 1, x, y); // down
                }
                mPressedPosition = new PointF(x, y);
                postDelayed(this, TIME_FOR_LONG_PRESS);

            } else if (switchValue == MotionEvent.ACTION_POINTER_DOWN) {
                mOldDist = spacing(event);
                if (mOldDist > 2f) {
                    mTouchMode = ZOOM;
                    Log.d(TAG, "ACTION_DOWN mode ZOOM started");
                }
            } else if (switchValue == MotionEvent.ACTION_UP) {
                Log.d(TAG, "ACTION_UP");
                switch (mTouchMode) {
                    case DRAG:
                        Log.d(TAG, "onTouch move");
                        motionCallback(mMotionCallbackID, x, y);
                        buttonCallback(mButtonCallbackID, 0, 1, x, y); // up
                        break;
                    case PRESSED:
                        if (sInMap) {
                            buttonCallback(mButtonCallbackID, 1, 1, x, y); // down
                        }
                        buttonCallback(mButtonCallbackID, 0, 1, x, y); // up
                        break;
                    default:
                        Log.i(TAG, "Unexpected touchmode: " + mTouchMode);
                }
                mTouchMode = NONE;
            } else if (switchValue == MotionEvent.ACTION_MOVE) {
                switch (mTouchMode) {
                    case DRAG:
                        motionCallback(mMotionCallbackID, x, y);
                        break;
                    case ZOOM:
                        doZoom(event);
                        break;
                    case PRESSED:
                        Log.d(TAG, "Start drag mode");
                        if (spacing(mPressedPosition, new PointF(event.getX(), event.getY())) > 20f) {
                            buttonCallback(mButtonCallbackID, 1, 1, x, y); // down
                            mTouchMode = DRAG;
                        }
                        break;
                    default:
                        Log.i(TAG, "Unexpected touchmode: " + mTouchMode);
                }
            }
            return true;
        }

        private void doZoom(MotionEvent event) {
            if (event.findPointerIndex(0) == -1 || event.findPointerIndex(1) == -1) {
                Log.e(TAG,"missing pointer");
                return;
            }
            float newDist = spacing(event);
            float scale;
            if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
                scale = newDist / mOldDist;
                Log.v(TAG, "New scale = " + scale);
                if (scale > 1.2) {
                    // zoom in
                    NavitCallbackHandler.sendCommand(NavitCallbackHandler.CmdType.CMD_ZOOM_IN);
                    mOldDist = newDist;
                } else if (scale < 0.8) {
                    mOldDist = newDist;
                    // zoom out
                    NavitCallbackHandler.sendCommand(NavitCallbackHandler.CmdType.CMD_ZOOM_OUT);
                }
            }
        }

        private float spacing(MotionEvent event) {
            float x = event.getX(0) - event.getX(1);
            float y = event.getY(0) - event.getY(1);
            return (float) Math.sqrt(x * x + y * y);
        }

        private float spacing(PointF a, PointF b) {
            float x = a.x - b.x;
            float y = a.y - b.y;
            return (float)Math.sqrt(x * x + y * y);
        }

        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            Log.d(TAG,"onkeydown = " + keyCode);
            String keyStr = null;
            switch (keyCode) {
                case KeyEvent.KEYCODE_ENTER:
                case KeyEvent.KEYCODE_DPAD_CENTER:
                    keyStr = String.valueOf((char) 13);
                    break;
                case KeyEvent.KEYCODE_DEL:
                    keyStr = String.valueOf((char) 8);
                    break;
                //case KeyEvent.KEYCODE_MENU:
                //    if (!sInMap) {
                //        this.postInvalidate();
                //        return true;
                //    }
                //    break;
                case KeyEvent.KEYCODE_SEARCH:
                    /* Handle event in Main Activity if map is shown */
                    if (!sInMap) {
                        keyStr = String.valueOf((char) 19);
                    }
                    break;
                case KeyEvent.KEYCODE_BACK:
                    keyStr = String.valueOf((char) 27);
                    break;
                case KeyEvent.KEYCODE_CALL:
                    keyStr = String.valueOf((char) 3);
                    break;
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    keyStr = String.valueOf((char) 14);
                    break;
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    keyStr = String.valueOf((char) 2);
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    keyStr = String.valueOf((char) 6);
                    break;
                case KeyEvent.KEYCODE_DPAD_UP:
                    keyStr = String.valueOf((char) 16);
                    break;
                default:
                    Log.v(TAG, "keycode: " + keyCode);
            }
            if (keyStr != null) {
                keypressCallback(mKeypressCallbackID, keyStr);
                return true;
            }
            return false;
        }

        @Override
        public boolean onKeyUp(int keyCode, KeyEvent event) {
            Log.d(TAG,"onkeyUp = " + keyCode);
            int i;
            String s = null;
            i = event.getUnicodeChar();

            if (i == 0) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_VOLUME_UP:
                    case KeyEvent.KEYCODE_VOLUME_DOWN:
                        return (!sInMap);
                    case KeyEvent.KEYCODE_SEARCH:
                        /* Handle event in Main Activity if map is shown */
                        if (sInMap) {
                            return false;
                        }
                        break;
                    case KeyEvent.KEYCODE_BACK:
                        if (Navit.sShowSoftKeyboardShowing) {
                            Navit.sShowSoftKeyboardShowing = false;
                        }
                        //s = java.lang.String.valueOf((char) 27);
                        return true;
                    case KeyEvent.KEYCODE_MENU:
                        if (!sInMap) {
                            if (!Navit.sShowSoftKeyboardShowing) {
                                // if in menu view:
                                // use as OK (Enter) key
                                s = String.valueOf((char) 13);
                            } // if soft keyboard showing on screen, dont use menu button as select key
                        } else {
                            return false;
                        }
                        break;
                    default:
                        Log.v(TAG, "keycode: " + keyCode);
                }
            } else if (i != 10) {
                s = java.lang.String.valueOf((char) i);
            }
            if (s != null) {
                keypressCallback(mKeypressCallbackID, s);
            }
            return true;
        }

        @Override
        public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
            String s;
            if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
                s = event.getCharacters();
                keypressCallback(mKeypressCallbackID, s);
                return true;
            }
            return super.onKeyMultiple(keyCode, count, event);
        }

        @Override
        public boolean onTrackballEvent(MotionEvent event) {
            String s = null;
            if (event.getAction() == android.view.MotionEvent.ACTION_DOWN) {
                s = java.lang.String.valueOf((char) 13);
            }
            if (event.getAction() == android.view.MotionEvent.ACTION_MOVE) {
                mTrackballX += event.getX();
                mTrackballY += event.getY();
                if (mTrackballX <= -1) {
                    s = java.lang.String.valueOf((char) 2);
                    mTrackballX += 1;
                }
                if (mTrackballX >= 1) {
                    s = java.lang.String.valueOf((char) 6);
                    mTrackballX -= 1;
                }
                if (mTrackballY <= -1) {
                    s = java.lang.String.valueOf((char) 16);
                    mTrackballY += 1;
                }
                if (mTrackballY >= 1) {
                    s = java.lang.String.valueOf((char) 14);
                    mTrackballY -= 1;
                }
            }
            if (s != null) {
                keypressCallback(mKeypressCallbackID, s);
            }
            return true;
        }

        public void run() {
            if (sInMap && mTouchMode == PRESSED) {
                doLongpressAction();
                mTouchMode = NONE;
            }
        }
    }

    private class SystemBarTintView extends View {

        public SystemBarTintView(Context context) {
            super(context);
        }

    }

    @SuppressWarnings("unused")
    NavitGraphics(final Activity navit, NavitGraphics parent, int x, int y, int w, int h,
                  int wraparound, int useCamera) {
        if (parent == null) {
            mOverlays = new ArrayList<>();
            if (useCamera != 0) {
                setCamera(useCamera);
            }
            setmActivity((Navit)navit);
        } else {
            mOverlays = null;
            mDrawBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            mBitmapWidth = w;
            mBitmapHeight = h;
            mPosX = x;
            mPosY = y;
            mPosWraparound = wraparound;
            mDrawCanvas = new Canvas(mDrawBitmap);
            parent.mOverlays.add(this);
        }
        mParentGraphics = parent;
    }

    /**
     * Sets up the main view.
     * @param navit The main activity.
     */
    private void setmActivity(final Navit navit) {
        this.mActivity = navit;
        mView = new NavitView(mActivity);
        mView.setClickable(false);
        mView.setFocusable(true);
        mView.setFocusableInTouchMode(true);
        mView.setKeepScreenOn(true);
        mRelativeLayout = new RelativeLayout(mActivity);
        mRelativeLayout.addView(mView);
        /* The navigational and status bar tinting code is meaningful only on API19+ */
        mTinting = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
        if (mTinting) {
            mFrameLayout = new FrameLayout(mActivity);
            mFrameLayout.addView(mRelativeLayout);
            mLeftTintView = new SystemBarTintView(mActivity);
            mRightTintView = new SystemBarTintView(mActivity);
            mTopTintView = new SystemBarTintView(mActivity);
            mBottomTintView = new SystemBarTintView(mActivity);
            mFrameLayout.addView(mLeftTintView);
            mFrameLayout.addView(mRightTintView);
            mFrameLayout.addView(mTopTintView);
            mFrameLayout.addView(mBottomTintView);
            mActivity.setContentView(mFrameLayout);
        } else {
            mActivity.setContentView(mRelativeLayout);
        }
        mView.requestFocus();
    }


    private native void sizeChangedCallback(long id, int x, int y);

    private native void paddingChangedCallback(long id, int left, int top, int right, int bottom);

    private native void keypressCallback(long id, String s);

    private native void buttonCallback(long id, int pressed, int button, int x, int y);

    private native void motionCallback(long id, int x, int y);

    private native String getCoordForPoint(int x, int y, boolean absoluteCoord);

    static native String[][] getAllCountries();

    private Canvas mDrawCanvas;
    private Bitmap mDrawBitmap;
    private long mSizeChangedCallbackID;
    private long mPaddingChangedCallbackID;
    private long mButtonCallbackID;
    private long mMotionCallbackID;
    private long mKeypressCallbackID;

    /**
     * Adjust views used to tint navigation and status bars.
     *
     * <p>This method is called from handleResize.
     *
     * It (re-)evaluates if and where the navigation bar is going to be shown, and calculates the
     * padding for objects which should not be obstructed.</p>
     *
     */
    private void adjustSystemBarsTintingViews() {

        /* hide tint bars during update to prevent ugly effects */
        mLeftTintView.setVisibility(View.GONE);
        mRightTintView.setVisibility(View.GONE);
        mTopTintView.setVisibility(View.GONE);
        mBottomTintView.setVisibility(View.GONE);

        mFrameLayout.post(new Runnable() {
            @Override
            public void run() {
                FrameLayout.LayoutParams leftLayoutParams = new FrameLayout.LayoutParams(mPaddingLeft,
                        LayoutParams.MATCH_PARENT, Gravity.BOTTOM | Gravity.LEFT);
                mLeftTintView.setLayoutParams(leftLayoutParams);

                FrameLayout.LayoutParams rightLayoutParams = new FrameLayout.LayoutParams(mPaddingRight,
                        LayoutParams.MATCH_PARENT, Gravity.BOTTOM | Gravity.RIGHT);
                mRightTintView.setLayoutParams(rightLayoutParams);

                FrameLayout.LayoutParams topLayoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                        mPaddingTop, Gravity.TOP);
                /* Prevent horizontal and vertical tint views from overlapping */
                topLayoutParams.setMargins(mPaddingLeft, 0, mPaddingRight, 0);
                mTopTintView.setLayoutParams(topLayoutParams);

                FrameLayout.LayoutParams bottomLayoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                        mPaddingBottom, Gravity.BOTTOM);
                /* Prevent horizontal and vertical tint views from overlapping */
                bottomLayoutParams.setMargins(mPaddingLeft, 0, mPaddingRight, 0);
                mBottomTintView.setLayoutParams(bottomLayoutParams);

                /* show tint bars again */
                mLeftTintView.setVisibility(View.VISIBLE);
                mRightTintView.setVisibility(View.VISIBLE);
                mTopTintView.setVisibility(View.VISIBLE);
                mBottomTintView.setVisibility(View.VISIBLE);
            }
        });

        paddingChangedCallback(mPaddingChangedCallbackID, mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
    }

    /**
     * Handles resize events.
     *
     * <p>This method is called whenever the main View is resized in any way. This is the case when its
     * {@code onSizeChanged()} event handler fires.</p>
     */
    private void handleResize(int w, int h) {
        if (this.mParentGraphics == null) {
            Log.d(TAG, String.format("handleResize w=%d h=%d", w, h));
            if (mTinting) {
                if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
                    resizePaddingKitkat();
                }
                adjustSystemBarsTintingViews(); // is incl paddingchangedcallback
            }
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void resizePaddingKitkat() {
        /*
         * API 19 does not support window insets.
         *
         * All system bar sizes are device defaults and do not change with rotation, but we have
         * to figure out which ones apply.
         *
         * The navigation bar is shown on devices that report they have no physical menu button. This seems to
         * work even on devices that allow disabling the physical buttons (and use the navigation bar, in which
         * case they report no physical menu button is available; tested with a OnePlus One running CyanogenMod)
         *
         * If shown, the navigation bar may appear on the side or at the bottom. The logic to determine this is
         * taken from AOSP RenderSessionImpl.findNavigationBar()
         * platform/frameworks/base/tools/layoutlib/bridge/src/com/android/
         * layoutlib/bridge/impl/RenderSessionImpl.java
         */
        mPaddingLeft = 0;
        if (!sInMap) {
            mPaddingTop = 0;
            mPaddingRight = 0;
            mPaddingBottom = 0;
            return;
        }
        Resources resources = NavitAppConfig.sResources;
        int shid = resources.getIdentifier("status_bar_height", "dimen", "android");
        int nhid = resources.getIdentifier("navigation_bar_height", "dimen", "android");
        int nhlid = resources.getIdentifier("navigation_bar_height_landscape", "dimen", "android");
        int nwid = resources.getIdentifier("navigation_bar_width", "dimen", "android");
        int statusBarHeight = (shid > 0) ? resources.getDimensionPixelSize(shid) : 0;
        int navigationBarHeight = (nhid > 0) ? resources.getDimensionPixelSize(nhid) : 0;
        int navigationBarHeightLandscape = (nhlid > 0) ? resources.getDimensionPixelSize(nhlid) : 0;
        int navigationBarWidth = (nwid > 0) ? resources.getDimensionPixelSize(nwid) : 0;
        Log.v(TAG, String.format("statusBarHeight=%d, navigationBarHeight=%d, "
                        + "navigationBarHeightLandscape=%d, navigationBarWidth=%d",
                        statusBarHeight, navigationBarHeight,
                        navigationBarHeightLandscape, navigationBarWidth));
        boolean isStatusShowing = !mActivity.mIsFullscreen;
        boolean isNavShowing = !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity));
        Log.v(TAG, String.format("isStatusShowing=%b isNavShowing=%b", isStatusShowing, isNavShowing));
        boolean isLandscape = (mActivity.getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE);
        boolean isNavAtBottom = (!isLandscape)
                || (mActivity.getResources().getConfiguration().smallestScreenWidthDp >= 600);
        Log.v(TAG, String.format("isNavAtBottom=%b (Config.smallestScreenWidthDp=%d, isLandscape=%b)",
                isNavAtBottom, mActivity.getResources().getConfiguration().smallestScreenWidthDp, isLandscape));

        mPaddingTop = isStatusShowing ? statusBarHeight : 0;
        mPaddingRight = (isNavShowing && !isNavAtBottom) ? navigationBarWidth : 0;
        mPaddingBottom = (!(isNavShowing && isNavAtBottom)) ? 0 : (
                isLandscape ? navigationBarHeightLandscape : navigationBarHeight);
    }


    /**
     * Returns whether the device has a hardware menu button.
     *
     * <p>Only Android versions starting with ICS (API version 14) support the API call to detect the presence of a
     * Menu button. On earlier Android versions, the following assumptions will be made: On API levels up to 10,
     * this method will always return {@code true}, as these Android versions relied on devices having a physical
     * Menu button. On API levels 11 through 13 (Honeycomb releases), this method will always return
     * {@code false}, as Honeycomb was a tablet-only release and did not require devices to have a Menu button.</p>
     *
     * <p>Note that this method is not aware of non-standard mechanisms on some customized builds of Android</p>
     */
    @SuppressWarnings("unused")
    boolean hasMenuButton() {
        if (Build.VERSION.SDK_INT <= 10) {
            return true;
        } else {
            if (Build.VERSION.SDK_INT <= 13) {
                return false;
            } else {
                return ViewConfiguration.get(mActivity.getApplication()).hasPermanentMenuKey();
            }
        }
    }

    @SuppressWarnings("unused")
    void setSizeChangedCallback(long id) {
        mSizeChangedCallbackID = id;
    }

    @SuppressWarnings("unused")
    void setPaddingChangedCallback(long id) {
        mPaddingChangedCallbackID = id;
    }

    @SuppressWarnings("unused")
    void setButtonCallback(long id) {
        Log.v(TAG,"set Buttononcallback");
        mButtonCallbackID = id;
    }

    @SuppressWarnings("unused")
    void setMotionCallback(long id) {
        mMotionCallbackID = id;
        Log.v(TAG,"set Motioncallback");
    }

    @SuppressWarnings("unused")
    void setKeypressCallback(long id) {
        Log.v(TAG,"set Keypresscallback");
        mKeypressCallbackID = id;
    }


    @SuppressWarnings("unused")
    protected void draw_polyline(Paint paint, int[] c) {
        paint.setStrokeWidth(c[0]);
        paint.setARGB(c[1],c[2],c[3],c[4]);
        paint.setStyle(Paint.Style.STROKE);
        //paint.setAntiAlias(true);
        //paint.setStrokeWidth(0);
        int ndashes = c[5];
        float[] intervals = new float[ndashes + (ndashes % 2)];
        for (int i = 0; i < ndashes; i++) {
            intervals[i] = c[6 + i];
        }

        if ((ndashes % 2) == 1) {
            intervals[ndashes] = intervals[ndashes - 1];
        }

        if (ndashes > 0) {
            paint.setPathEffect(new android.graphics.DashPathEffect(intervals,0.0f));
        }

        Path path = new Path();
        path.moveTo(c[6 + ndashes], c[7 + ndashes]);
        for (int i = 8 + ndashes; i < c.length; i += 2) {
            path.lineTo(c[i], c[i + 1]);
        }
        //global_path.close();
        mDrawCanvas.drawPath(path, paint);
        paint.setPathEffect(null);
    }

    // One might ask why the coordinates for the holes are smashed all together in one int array. It's because
    // I have no clue at all how to properly do JNI with an array of int arrays instead.
    protected void draw_polygon_with_holes(Paint paint, int strokeWidth, int r, int g, int b, int a, int[] c,
                                           int[] ccount, int[] holes) {
        paint.setStrokeWidth(strokeWidth);
        paint.setARGB(a,r,g,b);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        //paint.setAntiAlias(true);
        // Check if we got at least three coordinates (3 values)
        if (c.length < 6) {
            return;
        }

        // create a new paint path
        Path path = new Path();
        // Add outer polygon to paint path
        path.moveTo(c[0], c[1]);
        for (int i = 2; i < c.length; i += 2) {
            path.lineTo(c[i], c[i + 1]);
        }
        // close outer polygon
        path.close();
        // add holes if any
        int coordinatesUsed = 0;
        // for every hole
        for (int i = 0; i < ccount.length; i++) {
            // drop holes with less than 3 coordinates
            if (ccount[i] >= 6) {
                path.moveTo(holes[coordinatesUsed + 0], holes[coordinatesUsed + 1]);
                for (int j = 2; j < ccount[i]; j += 2) {
                    path.lineTo(holes[coordinatesUsed + j], holes[coordinatesUsed + j + 1]);
                }
                // close this hole
                path.close();
            }
            //remeber the used coordinates for next hole
            coordinatesUsed += ccount[i];
        }

        // Set fill rule
        path.setFillType(Path.FillType.EVEN_ODD);
        // Draw it
        mDrawCanvas.drawPath(path, paint);
    }

    @SuppressWarnings("unused")
    protected void draw_polygon(Paint paint, int[] c) {
        paint.setStrokeWidth(c[0]);
        paint.setARGB(c[1],c[2],c[3],c[4]);
        paint.setStyle(Paint.Style.FILL);
        //paint.setAntiAlias(true);
        //paint.setStrokeWidth(0);
        Path path = new Path();
        path.moveTo(c[5], c[6]);
        for (int i = 7; i < c.length; i += 2) {
            path.lineTo(c[i], c[i + 1]);
        }
        //global_path.close();
        mDrawCanvas.drawPath(path, paint);
    }

    @SuppressWarnings("unused")
    protected void draw_rectangle(Paint paint, int x, int y, int w, int h) {
        Rect r = new Rect(x, y, x + w, y + h);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        //paint.setStrokeWidth(0);
        mDrawCanvas.drawRect(r, paint);
    }

    @SuppressWarnings("unused")
    protected void draw_circle(Paint paint, int x, int y, int r) {
        paint.setStyle(Paint.Style.STROKE);
        mDrawCanvas.drawCircle(x, y, r / 2, paint);
    }

    @SuppressWarnings("unused")
    protected void draw_text(Paint paint, int x, int y, String text, int size, int dx, int dy, int bgcolor) {
        int oldcolor = paint.getColor();
        Path path = null;

        paint.setTextSize(size / 15);
        paint.setStyle(Paint.Style.FILL);

        if (dx != 0x10000 || dy != 0) {
            path = new Path();
            path.moveTo(x, y);
            path.rLineTo(dx, dy);
            paint.setTextAlign(android.graphics.Paint.Align.LEFT);
        }

        if (bgcolor != 0) {
            paint.setStrokeWidth(3);
            paint.setColor(bgcolor);
            paint.setStyle(Paint.Style.STROKE);
            if (path == null) {
                mDrawCanvas.drawText(text, x, y, paint);
            } else {
                mDrawCanvas.drawTextOnPath(text, path, 0, 0, paint);
            }
            paint.setStyle(Paint.Style.FILL);
            paint.setColor(oldcolor);
        }

        if (path == null) {
            mDrawCanvas.drawText(text, x, y, paint);
        } else {
            mDrawCanvas.drawTextOnPath(text, path, 0, 0, paint);
        }
        paint.clearShadowLayer();
    }

    protected void draw_image(Paint paint, int x, int y, Bitmap bitmap) {
        mDrawCanvas.drawBitmap(bitmap, x, y, null);
    }

    /* takes an image and draws it on the screen as a prerendered maptile.
     *
     *
     *
     * @param paint     Paint object used to draw the image
     * @param count     the number of points specified
     * @param p0x and p0y   specifying the top left point
     * @param p1x and p1y   specifying the top right point
     * @param p2x and p2y   specifying the bottom left point, not yet used but kept
     *                      for compatibility with the linux port
     * @param bitmap    Bitmap object holding the image to draw
     */
    protected void draw_image_warp(Paint paint, int count, int p0x, int p0y, int p1x, int p1y, int p2x, int p2y,
            Bitmap bitmap) {

        float width;
        float scale;
        float deltaY;
        float deltaX;
        float angle;
        Matrix matrix;

        if (count == 3) {
            matrix = new Matrix();
            deltaX = p1x - p0x;
            deltaY = p1y - p0y;
            width = (float) (Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)));
            angle = (float) (Math.atan2(deltaY, deltaX) * 180d / Math.PI);
            scale = width / bitmap.getWidth();
            matrix.preScale(scale, scale);
            matrix.postTranslate(p0x, p0y);
            matrix.postRotate(angle, p0x, p0y);
            mDrawCanvas.drawBitmap(bitmap, matrix, paint);
        }
    }

    /* These constants must be synchronized with enum draw_mode_num in graphics.h. */
    private static final int DRAW_MODE_BEGIN = 0;
    private static final int DRAW_MODE_END = 1;
    /* Used by the pedestrian plugin, draws without a mapbackground */
    private static final int DRAW_MODE_BEGIN_CLEAR = 2;

    @SuppressWarnings("unused")
    protected void draw_mode(int mode) {
        if (mode == DRAW_MODE_END) {
            if (mParentGraphics == null) {
                mView.invalidate();
            } else {
                mParentGraphics.mView.invalidate(get_rect());
            }
        }
        if (mode == DRAW_MODE_BEGIN_CLEAR || (mode == DRAW_MODE_BEGIN && mParentGraphics != null)) {
            mDrawBitmap.eraseColor(0);
        }

    }

    @SuppressWarnings("unused")
    protected void draw_drag(int x, int y) {
        mPosX = x;
        mPosY = y;
    }

    @SuppressWarnings("unused")
    protected void overlay_disable(int disable) {
        Log.v(TAG,"overlay_disable: " + disable + ", Parent: " + (mParentGraphics != null));
        if (mParentGraphics == null) {
            sInMap = (disable == 0);
            workAroundForGuiInternal();
        }
        if (mOverlayDisabled != disable) {
            mOverlayDisabled = disable;
            if (mParentGraphics != null) {
                mParentGraphics.mView.invalidate(get_rect());
            }
        }
    }

    private void workAroundForGuiInternal() {
        if (!mTinting) {
            return;
        }
        Log.v(TAG,"workaround gui internal");
        if (!sInMap && !mView.isDrag() && Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
            mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            return;
        }
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
            mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        }
    }

    @SuppressWarnings("unused")
    protected void overlay_resize(int x, int y, int w, int h, int wraparound) {
        mDrawBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mBitmapWidth = w;
        mBitmapHeight = h;
        mPosX = x;
        mPosY = y;
        mPosWraparound = wraparound;
        mDrawCanvas.setBitmap(mDrawBitmap);
    }
}
