/*
 * Copyright (C) 2009 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.browser;

import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.TwoLineListItem;

import java.util.Vector;

/* package */ class ErrorConsoleView extends LinearLayout {

    /**
     * Define some constants to describe the visibility of the error console.
     */
    public static final int SHOW_MINIMIZED = 0;
    public static final int SHOW_MAXIMIZED = 1;
    public static final int SHOW_NONE      = 2;

    private TextView mConsoleHeader;
    private ErrorConsoleListView mErrorList;
    private LinearLayout mEvalJsViewGroup;
    private EditText mEvalEditText;
    private Button mEvalButton;
    private WebView mWebView;
    private int mCurrentShowState = SHOW_NONE;

    private boolean mSetupComplete = false;

    // Before we've been asked to display the console, cache any messages that should
    // be added to the console. Then when we do display the console, add them to the view
    // then.
    private Vector<ErrorConsoleMessage> mErrorMessageCache;

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

    public ErrorConsoleView(Context context, AttributeSet attributes) {
        super(context, attributes);
    }

    private void commonSetupIfNeeded() {
        if (mSetupComplete) {
            return;
        }

        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.error_console, this);

        // Get references to each ui element.
        mConsoleHeader = (TextView) findViewById(R.id.error_console_header_id);
        mErrorList = (ErrorConsoleListView) findViewById(R.id.error_console_list_id);
        mEvalJsViewGroup = (LinearLayout) findViewById(R.id.error_console_eval_view_group_id);
        mEvalEditText = (EditText) findViewById(R.id.error_console_eval_text_id);
        mEvalButton = (Button) findViewById(R.id.error_console_eval_button_id);

        mEvalButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                // Send the javascript to be evaluated to webkit as a javascript: url
                // TODO: Can we expose access to webkit's JS interpreter here and evaluate it that
                // way? Note that this is called on the UI thread so we will need to post a message
                // to the WebCore thread to implement this.
                if (mWebView != null) {
                    mWebView.loadUrl("javascript:" + mEvalEditText.getText());
                }

                mEvalEditText.setText("");
            }
        });

        // Make clicking on the console title bar min/maximse it.
        mConsoleHeader.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                if (mCurrentShowState == SHOW_MINIMIZED) {
                    showConsole(SHOW_MAXIMIZED);
                } else {
                    showConsole(SHOW_MINIMIZED);
                }
            }
        });

        // Add any cached messages to the list now that we've assembled the view.
        if (mErrorMessageCache != null) {
            for (ErrorConsoleMessage msg : mErrorMessageCache) {
                mErrorList.addErrorMessage(msg.getMessage(), msg.getSourceID(), msg.getLineNumber());
            }
            mErrorMessageCache.clear();
        }

        mSetupComplete = true;
    }

    /**
     * Adds a message to the set of messages the console uses.
     */
    public void addErrorMessage(String msg, String sourceId, int lineNumber) {
        if (mSetupComplete) {
            mErrorList.addErrorMessage(msg, sourceId, lineNumber);
        } else {
            if (mErrorMessageCache == null) {
                mErrorMessageCache = new Vector<ErrorConsoleMessage>();
            }
            mErrorMessageCache.add(new ErrorConsoleMessage(msg, sourceId, lineNumber));
        }
    }

    /**
     * Removes all error messages from the console.
     */
    public void clearErrorMessages() {
        if (mSetupComplete) {
            mErrorList.clearErrorMessages();
        } else if (mErrorMessageCache != null) {
            mErrorMessageCache.clear();
        }
    }

    /**
     * Returns the current number of errors displayed in the console.
     */
    public int numberOfErrors() {
        if (mSetupComplete) {
            return mErrorList.getCount();
        } else {
            return (mErrorMessageCache == null) ? 0 : mErrorMessageCache.size();
        }
    }

    /**
     * Sets the webview that this console is associated with. Currently this is used so
     * we can call into webkit to evaluate JS expressions in the console.
     */
    public void setWebView(WebView webview) {
        mWebView = webview;
    }

    /**
     * Sets the visibility state of the console.
     */
    public void showConsole(int show_state) {
        commonSetupIfNeeded();
        switch (show_state) {
            case SHOW_MINIMIZED:
                mConsoleHeader.setVisibility(View.VISIBLE);
                mConsoleHeader.setText(R.string.error_console_header_text_minimized);
                mErrorList.setVisibility(View.GONE);
                mEvalJsViewGroup.setVisibility(View.GONE);
                break;

            case SHOW_MAXIMIZED:
                mConsoleHeader.setVisibility(View.VISIBLE);
                mConsoleHeader.setText(R.string.error_console_header_text_maximized);
                mErrorList.setVisibility(View.VISIBLE);
                mEvalJsViewGroup.setVisibility(View.VISIBLE);
                break;

            case SHOW_NONE:
                mConsoleHeader.setVisibility(View.GONE);
                mErrorList.setVisibility(View.GONE);
                mEvalJsViewGroup.setVisibility(View.GONE);
                break;
        }
        mCurrentShowState = show_state;
    }

    /**
     * Returns the current visibility state of the console.
     */
    public int getShowState() {
        if (mSetupComplete) {
            return mCurrentShowState;
        } else {
            return SHOW_NONE;
        }
    }

    /**
     * This class extends ListView to implement the View that will actually display the set of
     * errors encountered on the current page.
     */
    private static class ErrorConsoleListView extends ListView {
        // An adapter for this View that contains a list of error messages.
        private ErrorConsoleMessageList mConsoleMessages;

        public ErrorConsoleListView(Context context, AttributeSet attributes) {
            super(context, attributes);
            mConsoleMessages = new ErrorConsoleMessageList(context);
            setAdapter(mConsoleMessages);
        }

        public void addErrorMessage(String msg, String sourceId, int lineNumber) {
            mConsoleMessages.add(msg, sourceId, lineNumber);
            setSelection(mConsoleMessages.getCount());
        }

        public void clearErrorMessages() {
            mConsoleMessages.clear();
        }

        /**
         * This class is an adapter for ErrorConsoleListView that contains the error console
         * message data.
         */
        private class ErrorConsoleMessageList extends android.widget.BaseAdapter
                implements android.widget.ListAdapter {

            private Vector<ErrorConsoleMessage> mMessages;
            private LayoutInflater mInflater;

            public ErrorConsoleMessageList(Context context) {
                mMessages = new Vector<ErrorConsoleMessage>();
                mInflater = (LayoutInflater)context.getSystemService(
                        Context.LAYOUT_INFLATER_SERVICE);
            }

            /**
             * Add a new message to the list and update the View.
             */
            public void add(String msg, String sourceID, int lineNumber) {
                mMessages.add(new ErrorConsoleMessage(msg, sourceID, lineNumber));
                notifyDataSetChanged();
            }

            /**
             * Remove all messages from the list and update the view.
             */
            public void clear() {
                mMessages.clear();
                notifyDataSetChanged();
            }

            @Override
            public boolean areAllItemsEnabled() {
                return false;
            }

            @Override
            public boolean isEnabled(int position) {
                return false;
            }

            public long getItemId(int position) {
                return position;
            }

            public Object getItem(int position) {
                return mMessages.get(position);
            }

            public int getCount() {
                return mMessages.size();
            }

            @Override
            public boolean hasStableIds() {
                return true;
            }

            /**
             * Constructs a TwoLineListItem for the error at position.
             */
            public View getView(int position, View convertView, ViewGroup parent) {
                View view;
                ErrorConsoleMessage error = mMessages.get(position);

                if (error == null) {
                    return null;
                }

                if (convertView == null) {
                    view = mInflater.inflate(android.R.layout.two_line_list_item, parent, false);
                } else {
                    view = convertView;
                }

                TextView headline = (TextView) view.findViewById(android.R.id.text1);
                TextView subText = (TextView) view.findViewById(android.R.id.text2);
                headline.setText(error.getSourceID() + ":" + error.getLineNumber());
                subText.setText(error.getMessage());
                return view;
            }

        }
    }

    /**
     * This class holds the data for a single error message in the console.
     */
    private static class ErrorConsoleMessage {
        private String mMessage;
        private String mSourceID;
        private int mLineNumber;

        public ErrorConsoleMessage(String msg, String sourceID, int lineNumber) {
            mMessage = msg;
            mSourceID = sourceID;
            mLineNumber = lineNumber;
        }

        public String getMessage() {
            return mMessage;
        }

        public String getSourceID() {
            return mSourceID;
        }

        public int getLineNumber() {
            return mLineNumber;
        }
    }
}