/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed 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 com.android.monkeyrunner.easy;

import com.google.common.base.Preconditions;

import com.android.chimpchat.core.TouchPressType;
import com.android.chimpchat.hierarchyviewer.HierarchyViewer;
import com.android.hierarchyviewerlib.models.ViewNode;
import com.android.monkeyrunner.JythonUtils;
import com.android.monkeyrunner.MonkeyDevice;
import com.android.monkeyrunner.doc.MonkeyRunnerExported;

import org.eclipse.swt.graphics.Point;
import org.python.core.ArgParser;
import org.python.core.ClassDictInit;
import org.python.core.Py;
import org.python.core.PyException;
import org.python.core.PyInteger;
import org.python.core.PyObject;
import org.python.core.PyTuple;

import java.util.Set;

/**
 * Extends {@link MonkeyDevice} to support looking up views using a 'selector'.
 * Currently, only identifiers can be used as a selector. All methods on
 * MonkeyDevice can be used on this class in Python.
 *
 * WARNING: This API is under development, expect the interface to change
 * without notice.
 */
@MonkeyRunnerExported(doc = "MonkeyDevice with easier methods to refer to objects.")
public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
    public static void classDictInit(PyObject dict) {
        JythonUtils.convertDocAnnotationsForClass(EasyMonkeyDevice.class, dict);
    }

    private MonkeyDevice mDevice;
    private HierarchyViewer mHierarchyViewer;

    private static final Set<String> EXPORTED_METHODS =
        JythonUtils.getMethodNames(EasyMonkeyDevice.class);

    @MonkeyRunnerExported(doc = "Creates EasyMonkeyDevice with an underlying MonkeyDevice.",
            args = { "device" },
            argDocs = { "MonkeyDevice to extend." })
    public EasyMonkeyDevice(MonkeyDevice device) {
        this.mDevice = device;
        this.mHierarchyViewer = device.getImpl().getHierarchyViewer();
    }

    @MonkeyRunnerExported(doc = "Sends a touch event to the selected object.",
            args = { "selector", "type" },
            argDocs = {
                    "The selector identifying the object.",
                    "The event type as returned by TouchPressType()." })
    public void touch(PyObject[] args, String[] kws) {
        ArgParser ap = JythonUtils.createArgParser(args, kws);
        Preconditions.checkNotNull(ap);

        By selector = getSelector(ap, 0);
        String tmpType = ap.getString(1);
        TouchPressType type = TouchPressType.fromIdentifier(tmpType);
        Preconditions.checkNotNull(type, "Invalid touch type: " + tmpType);
        // TODO: try catch rethrow PyExc
        touch(selector, type);
    }

    public void touch(By selector, TouchPressType type) {
        Point p = getElementCenter(selector);
        mDevice.getImpl().touch(p.x, p.y, type);
    }

    @MonkeyRunnerExported(doc = "Types a string into the specified object.",
            args = { "selector", "text" },
            argDocs = {
                    "The selector identifying the object.",
                    "The text to type into the object." })
    public void type(PyObject[] args, String[] kws) {
        ArgParser ap = JythonUtils.createArgParser(args, kws);
        Preconditions.checkNotNull(ap);

        By selector = getSelector(ap, 0);
        String text = ap.getString(1);
        type(selector, text);
    }

    public void type(By selector, String text) {
        Point p = getElementCenter(selector);
        mDevice.getImpl().touch(p.x, p.y, TouchPressType.DOWN_AND_UP);
        mDevice.getImpl().type(text);
    }

    @MonkeyRunnerExported(doc = "Locates the coordinates of the selected object.",
            args = { "selector" },
            argDocs = { "The selector identifying the object." },
            returns = "Tuple containing (x,y,w,h) location and size.")
    public PyTuple locate(PyObject[] args, String[] kws) {
        ArgParser ap = JythonUtils.createArgParser(args, kws);
        Preconditions.checkNotNull(ap);

        By selector = getSelector(ap, 0);

        ViewNode node = selector.findView(mHierarchyViewer);
        Point p = HierarchyViewer.getAbsolutePositionOfView(node);
        PyTuple tuple = new PyTuple(
                new PyInteger(p.x),
                new PyInteger(p.y),
                new PyInteger(node.width),
                new PyInteger(node.height));
        return tuple;
    }

    @MonkeyRunnerExported(doc = "Checks if the specified object exists.",
            args = { "selector" },
            returns = "True if the object exists.",
            argDocs = { "The selector identifying the object." })
    public boolean exists(PyObject[] args, String[] kws) {
        ArgParser ap = JythonUtils.createArgParser(args, kws);
        Preconditions.checkNotNull(ap);

        By selector = getSelector(ap, 0);
        return exists(selector);
    }

    public boolean exists(By selector) {
        ViewNode node = selector.findView(mHierarchyViewer);
        return node != null;
    }

    @MonkeyRunnerExported(doc = "Checks if the specified object is visible.",
            args = { "selector" },
            returns = "True if the object is visible.",
            argDocs = { "The selector identifying the object." })
    public boolean visible(PyObject[] args, String[] kws) {
        ArgParser ap = JythonUtils.createArgParser(args, kws);
        Preconditions.checkNotNull(ap);

        By selector = getSelector(ap, 0);
        return visible(selector);
    }

    public boolean visible(By selector) {
        ViewNode node = selector.findView(mHierarchyViewer);
        return mHierarchyViewer.visible(node);
    }

    @MonkeyRunnerExported(doc = "Obtain the text in the selected input box.",
            args = { "selector" },
            argDocs = { "The selector identifying the object." },
            returns = "Text in the selected input box.")
    public String getText(PyObject[] args, String[] kws) {
        ArgParser ap = JythonUtils.createArgParser(args, kws);
        Preconditions.checkNotNull(ap);

        By selector = getSelector(ap, 0);
        return getText(selector);
    }

    public String getText(By selector) {
        ViewNode node = selector.findView(mHierarchyViewer);
        return mHierarchyViewer.getText(node);
    }

    @MonkeyRunnerExported(doc = "Gets the id of the focused window.",
            returns = "The symbolic id of the focused window or None.")
    public String getFocusedWindowId(PyObject[] args, String[] kws) {
        return getFocusedWindowId();
    }

    public String getFocusedWindowId() {
        return mHierarchyViewer.getFocusedWindowName();
    }

    /**
     * Forwards unknown methods to the original MonkeyDevice object.
     */
    @Override
    public PyObject __findattr_ex__(String name) {
        if (!EXPORTED_METHODS.contains(name)) {
            return mDevice.__findattr_ex__(name);
        }
        return super.__findattr_ex__(name);
    }

    /**
     * Get the selector object from the argument parser.
     *
     * @param ap argument parser to get it from.
     * @param i argument index.
     * @return selector object.
     */
    private By getSelector(ArgParser ap, int i) {
        return (By)ap.getPyObject(i).__tojava__(By.class);
    }

    /**
     * Get the coordinates of the element's center.
     *
     * @param selector the element selector
     * @return the (x,y) coordinates of the center
     */
    private Point getElementCenter(By selector) {
        ViewNode node = selector.findView(mHierarchyViewer);
        if (node == null) {
            throw new PyException(Py.ValueError,
                    String.format("View not found: %s", selector));
        }

        Point p = HierarchyViewer.getAbsoluteCenterOfView(node);
        return p;
    }

}
