/*
 * Copyright (C) 2012 Google Inc.
 *
 * 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.googlecode.eyesfree.braille.selfbraille;

import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;

/**
 * Represents what should be shown on the braille display for a
 * part of the accessibility node tree.
 */
public class WriteData implements Parcelable {

    private static final String PROP_SELECTION_START = "selectionStart";
    private static final String PROP_SELECTION_END = "selectionEnd";

    private AccessibilityNodeInfo mAccessibilityNodeInfo;
    private CharSequence mText;
    private Bundle mProperties = Bundle.EMPTY;

    /**
     * Returns a new {@link WriteData} instance for the given {@code view}.
     */
    public static WriteData forView(View view) {
        AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view);
        WriteData writeData = new WriteData();
        writeData.mAccessibilityNodeInfo = node;
        return writeData;
    }

    public AccessibilityNodeInfo getAccessibilityNodeInfo() {
        return mAccessibilityNodeInfo;
    }

    /**
     * Sets the text to be displayed when the accessibility node associated
     * with this instance has focus.  If this method is not called (or
     * {@code text} is {@code null}), this client relinquishes control over
     * this node.
     */
    public WriteData setText(CharSequence text) {
        mText = text;
        return this;
    }

    public CharSequence getText() {
        return mText;
    }

    /**
     * Sets the start position in the text of a text selection or cursor that
     * should be marked on the display.  A negative value (the default) means
     * no selection will be added.
     */
    public WriteData setSelectionStart(int v) {
        writableProperties().putInt(PROP_SELECTION_START, v);
        return this;
    }

    /**
     * @see {@link #setSelectionStart}.
     */
    public int getSelectionStart() {
        return mProperties.getInt(PROP_SELECTION_START, -1);
    }

    /**
     * Sets the end of the text selection to be marked on the display.  This
     * value should only be non-negative if the selection start is
     * non-negative.  If this value is <= the selection start, the selection
     * is a cursor.  Otherwise, the selection covers the range from
     * start(inclusive) to end (exclusive).
     *
     * @see {@link android.text.Selection}.
     */
    public WriteData setSelectionEnd(int v) {
        writableProperties().putInt(PROP_SELECTION_END, v);
        return this;
    }

    /**
     * @see {@link #setSelectionEnd}.
     */
    public int getSelectionEnd() {
        return mProperties.getInt(PROP_SELECTION_END, -1);
    }

    private Bundle writableProperties() {
        if (mProperties == Bundle.EMPTY) {
            mProperties = new Bundle();
        }
        return mProperties;
    }

    /**
     * Checks constraints on the fields that must be satisfied before sending
     * this instance to the self braille service.
     * @throws IllegalStateException
     */
    public void validate() throws IllegalStateException {
        if (mAccessibilityNodeInfo == null) {
            throw new IllegalStateException(
                "Accessibility node info can't be null");
        }
        int selectionStart = getSelectionStart();
        int selectionEnd = getSelectionEnd();
        if (mText == null) {
            if (selectionStart > 0 || selectionEnd > 0) {
                throw new IllegalStateException(
                    "Selection can't be set without text");
            }
        } else {
            if (selectionStart < 0 && selectionEnd >= 0) {
                throw new IllegalStateException(
                    "Selection end without start");
            }
            int textLength = mText.length();
            if (selectionStart > textLength || selectionEnd > textLength) {
                throw new IllegalStateException("Selection out of bounds");
            }
        }
    }

    // For Parcelable support.

    public static final Parcelable.Creator<WriteData> CREATOR =
        new Parcelable.Creator<WriteData>() {
            @Override
            public WriteData createFromParcel(Parcel in) {
                return new WriteData(in);
            }

            @Override
            public WriteData[] newArray(int size) {
                return new WriteData[size];
            }
        };

    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * {@inheritDoc}
     * <strong>Note:</strong> The {@link AccessibilityNodeInfo} will be
     * recycled by this method, don't try to use this more than once.
     */
    @Override
    public void writeToParcel(Parcel out, int flags) {
        mAccessibilityNodeInfo.writeToParcel(out, flags);
        // The above call recycles the node, so make sure we don't use it
        // anymore.
        mAccessibilityNodeInfo = null;
        out.writeString(mText.toString());
        out.writeBundle(mProperties);
    }

    private WriteData() {
    }

    private WriteData(Parcel in) {
        mAccessibilityNodeInfo =
                AccessibilityNodeInfo.CREATOR.createFromParcel(in);
        mText = in.readString();
        mProperties = in.readBundle();
    }
}
