/*
 *  
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, 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 version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package javax.microedition.lcdui;

import java.util.Calendar;
import java.util.Date;

import com.sun.midp.i18n.Resource;
import com.sun.midp.i18n.ResourceConstants;
import com.sun.midp.lcdui.Text;
import com.sun.midp.lcdui.*;
import com.sun.midp.configurator.Constants;
import com.sun.midp.chameleon.*;
import com.sun.midp.chameleon.skins.DateFieldSkin;
import com.sun.midp.chameleon.skins.resources.DateFieldResources;
import com.sun.midp.chameleon.skins.DateEditorSkin;
import com.sun.midp.chameleon.skins.resources.DateEditorResources;
import com.sun.midp.chameleon.skins.ScreenSkin;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * This is the Look &amp; Feel implementation for DateField.
 */
class DateFieldLFImpl extends ItemLFImpl implements DateFieldLF {

    /**
     * Creates DateFieldLF for the passed in DateField object.
     * @param dateField the DateField object associated with this view
     */
    DateFieldLFImpl(DateField dateField) {
        super(dateField);

        DateFieldResources.load();
        DateEditorResources.load();

        df = dateField;
         if (editor == null) {
            editor = new DateEditor(this);
        }
    }
    
    // *****************************************************
    //  Package private methods
    // *****************************************************
    
    /**
     * Notifies Look &amp; Feel of a date change in the corresponding DateField.
     * @param date - the new Date set in the DateField
     */
    public void lSetDate(java.util.Date date) {
        lRequestPaint();
    }


    /**
     * Notifies Look &amp; Feel of a new input mode set in the corresponding
     * DateField.
     * @param mode the new input mode set in the DateField.
     */
    public void lSetInputMode(int mode) {
        lRequestInvalidate(true, true);
    }

    /**
     * Gets the date currently set on the date field widget.
     * This method is called by Date only if Date is initialized.
     * @return the date this widget is currently set to
     */
    public Date lGetDate() {
        return new java.util.Date(df.currentDate.getTime().getTime());
    }


    // *****************************************************
    //  Package private methods
    // *****************************************************
    
    /**
     * Indicate whether or not traversing should occur.
     *
     * @return <code>true</code> if traversing should be skipped;
     *     </code>false</code>, if the field is editable.
     */
    boolean shouldSkipTraverse() {
        return false;        
    }

    /**
     * Sets the content size in the passed in array.
     * Content is calculated based on the availableWidth.
     * size[WIDTH] and size[HEIGHT] should be set by this method.
     * @param size The array that holds Item content size and location 
     *             in Item internal bounds coordinate system.
     * @param availableWidth The width available for this Item
     */
    void lGetContentSize(int size[], int availableWidth) {
        Font f = DateFieldSkin.FONT;
        size[HEIGHT] = f.getHeight() + (2 * DateFieldSkin.PAD_V);
        int mode = df.mode;
        size[WIDTH] = 
            f.stringWidth(toString(df.currentDate, mode, df.initialized)) +
            (2 * DateFieldSkin.PAD_H);
            
        switch (mode) {
            case DateField.DATE:
                if (DateFieldSkin.IMAGE_ICON_DATE != null) {
                    size[WIDTH] += DateFieldSkin.IMAGE_ICON_DATE.getWidth();
                }
                if (size[WIDTH] < DateEditorSkin.WIDTH_DATE) {
                    size[WIDTH] = DateEditorSkin.WIDTH_DATE;
                }
                break;
            case DateField.TIME:
                if (DateFieldSkin.IMAGE_ICON_TIME != null) {
                    size[WIDTH] += DateFieldSkin.IMAGE_ICON_TIME.getWidth();
                }
                if (size[WIDTH] < DateEditorSkin.WIDTH_TIME) {
                    size[WIDTH] = DateEditorSkin.WIDTH_TIME;
                }
                break;
            case DateField.DATE_TIME:
                if (DateFieldSkin.IMAGE_ICON_DATETIME != null) {
                    size[WIDTH] += 
                        DateFieldSkin.IMAGE_ICON_DATETIME.getWidth();
                }
                if (size[WIDTH] < DateEditorSkin.WIDTH_DATETIME) {
                    size[WIDTH] = DateEditorSkin.WIDTH_DATETIME;
                }
                break;
            default:
                // for safety/completeness.
                Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                    "DateFieldLFImpl: mode=" + mode);
                break;
        }
        
        if (size[WIDTH] > availableWidth) {
            size[WIDTH] = availableWidth;
        }        
    }

    /**
     * Determine if this Item should have a newline after it
     *
     * @return true if it should have a newline after
     */
    boolean equateNLA() {
        if (super.equateNLA()) {
            return true;
        }

        return ((df.layout & Item.LAYOUT_2) != Item.LAYOUT_2);
    }
               

    /**
     * Determine if this Item should have a newline before it
     *
     * @return true if it should have a newline before
     */
    boolean equateNLB() {
        if (super.equateNLB()) {
            return true;
        }

        return ((df.layout & Item.LAYOUT_2) != Item.LAYOUT_2);
    }

    /**
     * Called from the Date Editor to save the selected Date.
     *
     * @param date The Date object to which current date should be set.
     */
    void saveDate(java.util.Date date) {
        synchronized (Display.LCDUILock) {
            df.setDateImpl(date);
            lSetDate(date);
        }
    }

    /**
     * Paints the content area of this DateField. 
     * Graphics is translated to contents origin.
     * @param g The graphics where DateField content should be painted
     * @param width The width available for the Item's content
     * @param height The height available for the Item's content
     */
    void lPaintContent(Graphics g, int width, int height) {

        currentDate = df.currentDate;
        mode  = df.mode;
        
        // draw background
        if (DateFieldSkin.IMAGE_BG != null) {
            CGraphicsUtil.draw9pcsBackground(g, 0, 0, width, height,
                DateFieldSkin.IMAGE_BG);
        } else {
            // draw widget instead of using images
            CGraphicsUtil.drawDropShadowBox(g, 0, 0, width, height,
                DateFieldSkin.COLOR_BORDER,
                DateFieldSkin.COLOR_BORDER_SHD, 
                DateFieldSkin.COLOR_BG);
        }
        
        if (hasFocus && !editor.isPopupOpen()) {
            // hilight the background
            g.setColor(ScreenSkin.COLOR_TRAVERSE_IND);
            g.fillRect(1, 2, width - 2, height - 3);
        } 
                    
        // draw icon
        int iconWidth = 0;
        switch (mode) {
            case DateField.DATE:
                if (DateFieldSkin.IMAGE_ICON_DATE != null) {
                    iconWidth = DateFieldSkin.IMAGE_ICON_DATE.getWidth();
                    int yOffset = height - 
                        DateFieldSkin.IMAGE_ICON_DATE.getHeight();
                    if (yOffset > 0) {
                        yOffset = (int)(yOffset / 2);
                    } else {
                        yOffset = 0;
                    }
                    drawButtonBG(g, width - iconWidth, 0, iconWidth, height);
                    g.drawImage(DateFieldSkin.IMAGE_ICON_DATE,
                                width - iconWidth, yOffset, 
                                Graphics.LEFT | Graphics.TOP);
                }
                break;
            case DateField.TIME:
                if (DateFieldSkin.IMAGE_ICON_TIME != null) {
                    iconWidth = DateFieldSkin.IMAGE_ICON_TIME.getWidth();
                    int yOffset = height - 
                        DateFieldSkin.IMAGE_ICON_DATE.getHeight();
                    if (yOffset > 0) {
                        yOffset = (int)(yOffset / 2);
                    } else {
                        yOffset = 0;
                    }
                    drawButtonBG(g, width - iconWidth, 0, iconWidth, height);
                    g.drawImage(DateFieldSkin.IMAGE_ICON_TIME,
                                width - iconWidth, yOffset, 
                                Graphics.LEFT | Graphics.TOP);
                }
                break;
            case DateField.DATE_TIME:
                if (DateFieldSkin.IMAGE_ICON_DATETIME != null) {
                    iconWidth = DateFieldSkin.IMAGE_ICON_DATETIME.getWidth();
                    int yOffset = height - 
                        DateFieldSkin.IMAGE_ICON_DATE.getHeight();
                    if (yOffset > 0) {
                        yOffset = (int)(yOffset / 2);
                    } else {
                        yOffset = 0;
                    }
                    drawButtonBG(g, width - iconWidth, 0, iconWidth, height);
                    g.drawImage(DateFieldSkin.IMAGE_ICON_DATETIME,
                                width - iconWidth, yOffset, 
                                Graphics.LEFT | Graphics.TOP);
                }
                break;
            default:
                // for safety/completeness.
                Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                    "DateFieldLFImpl: mode=" + mode);
                break;
        }

        // we clip in case our text is too long
        g.setClip(DateFieldSkin.PAD_H, DateFieldSkin.PAD_V,
            width - (2 * DateFieldSkin.PAD_H) - iconWidth,
            height - (2 * DateFieldSkin.PAD_V));

        g.translate(DateFieldSkin.PAD_H, DateFieldSkin.PAD_V);        
        
        // paint value
        g.setFont(DateFieldSkin.FONT);
        g.setColor(DateFieldSkin.COLOR_FG);
        g.drawString(toString(currentDate, mode, df.initialized), 
                     0, 0, Graphics.LEFT | Graphics.TOP);
        
        g.translate(-DateFieldSkin.PAD_H, -DateFieldSkin.PAD_V);
        if (editor.isPopupOpen() && editor.isSizeChanged()) {
            setLocation();
        }
    }

    /**
     * Draw background of button 
     * @param g Graphics
     * @param x x coordinate
     * @param y y coordinate
     * @param w width
     * @param h height
     */
    void drawButtonBG(Graphics g, int x, int y, int w, int h) {
        if (DateFieldSkin.IMAGE_BUTTON_BG != null) {
            CGraphicsUtil.draw9pcsBackground(g, x, y, w, h,
                                             DateFieldSkin.IMAGE_BUTTON_BG);
        }
    }
    
    /**
     * Called by the system to traverse this DateField.
     *
     * @param dir the direction of traversal
     * @param viewportWidth the width of the container's viewport
     * @param viewportHeight the height of the container's viewport
     * @param visRect passes the visible rectangle into the method, and
     * returns the updated traversal rectangle from the method
     * @return true if internal traversal had occurred, false if traversal
     * should proceed out
     */
    boolean lCallTraverse(int dir, int viewportWidth, int viewportHeight,
                          int[] visRect) 
    {

        super.lCallTraverse(dir, viewportWidth, viewportHeight, visRect);
        
        if (!editor.isPopupOpen()) {
            if (!traversedIn) {
                traversedIn = true;
                return true;
            }
        }
        return false;
    }

    /**
     * Called by the system to indicate traversal has left this Item.
     */
    void lCallTraverseOut() {
        super.lCallTraverseOut();
        traversedIn = false;
        if (editor.isPopupOpen()) {
            editor.hideAllPopups();
            getCurrentDisplay().hidePopup(editor);
        }
    }

    /**
     * Called by the system to signal a key press.
     *
     * @param keyCode the key code of the key that has been pressed
     */
    void uCallKeyPressed(int keyCode) {
        if (keyCode != Constants.KEYCODE_SELECT) {
            return;
        }
        
        synchronized (Display.LCDUILock) {
            if (!editor.isPopupOpen()) {
                setLocation();
                editor.show();
            } else {
                editor.hideAllPopups();
                getCurrentDisplay().hidePopup(editor);                
            }	    
        } // synchronized
        uRequestPaint();
    }

    /**
     * Set location of popup layer
     */
    private void setLocation() {
        ScreenLFImpl sLF = (ScreenLFImpl)df.owner.getLF();
        // decide where to show popup: above, below or in
        // the middle of the screen (if both above/below don't
        // work out)

        int[] avalibleBounds = sLF.lGetCurrentDisplay().getBodyLayerBounds();

        int x = bounds[X] + contentBounds[X] + DateFieldSkin.PAD_V - sLF.viewable[X];
        int y = bounds[Y] + contentBounds[Y] - sLF.viewable[Y];

        if (y - DateEditorSkin.HEIGHT >= 0) {
            // can fit above
            y -= DateEditorSkin.HEIGHT - DateFieldSkin.PAD_V - avalibleBounds[Y];
        } else if (y + contentBounds[HEIGHT] + DateEditorSkin.HEIGHT + 2 * DateFieldSkin.PAD_V < avalibleBounds[HEIGHT]) {
            // can fit below
            y += contentBounds[HEIGHT] + 2 * DateFieldSkin.PAD_V + avalibleBounds[Y];
        } else {
            // fit in the middle of screen
            y = avalibleBounds[Y] + (avalibleBounds[HEIGHT] / 2) -
                (DateEditorSkin.HEIGHT / 2);
        }
        editor.setLocation(x, y);
    }



    /**
     * Called by the system to indicate the size available to this Item
     * has changed
     *
     * @param w the new width of the item's content area
     * @param h the new height of the item's content area
     */
    void uCallSizeChanged(int w, int h) {
        super.uCallSizeChanged(w,h);
        synchronized (Display.LCDUILock) {
            editor.setSizeChanged();

        }
    }

    /**
     * Get the localized day of the week text given a Calendar.
     *
     * @param calendar The Calendar object to retrieve the date from
     * @return String The day of the week text based on the date in the
     *                  calendar
     */
    static String dayOfWeekString(Calendar calendar) {
        String str;
        switch (calendar.get(Calendar.DAY_OF_WEEK)) {
            case Calendar.SUNDAY:    
                str = Resource.getString(ResourceConstants.LCDUI_DF_SUN);
                break;
            case Calendar.MONDAY:
                str = Resource.getString(ResourceConstants.LCDUI_DF_MON); 
                break;
            case Calendar.TUESDAY:
                str = Resource.getString(ResourceConstants.LCDUI_DF_TUE); 
                break;
            case Calendar.WEDNESDAY: 
                str = Resource.getString(ResourceConstants.LCDUI_DF_WED); 
                break;
            case Calendar.THURSDAY:  
                str = Resource.getString(ResourceConstants.LCDUI_DF_THU); 
                break;
            case Calendar.FRIDAY:    
                str = Resource.getString(ResourceConstants.LCDUI_DF_FRI); 
                break;
            case Calendar.SATURDAY:  
                str = Resource.getString(ResourceConstants.LCDUI_DF_SAT); 
                break;
            default: 
                str = Integer.toString(calendar.get(Calendar.DAY_OF_WEEK));
            break;
        }
        return str;
    }

    /**
     * A utility method to return a numerical digit as two digits
     * if it is less than 10 (used to display time in 2 digits 
     * if required, eg. 6:09pm).
     *
     * @param n The number to convert
     * @return String The String representing the number in two digits
     */
    static String twoDigits(int n) {
        if (n == 0) {
            return "00";
        } else if (n < 10) {
            return "0" + n;
        } else {
            return "" + n;
        }
    }

    /**
     * Get the am/pm text given a Calendar.
     *
     * @param calendar The Calendar object to retrieve the time from
     * @return String The am/pm text based on the time in the calendar
     */
    static String ampmString(Calendar calendar) {
        if (!CLOCK_USES_AM_PM) {
            return null;
        }
        return (calendar.get(Calendar.AM_PM) == Calendar.AM) ?
            Resource.getString(ResourceConstants.LCDUI_DF_AM) :
            Resource.getString(ResourceConstants.LCDUI_DF_PM);
    }

    /**
     * Get the hour given a Calendar, based on a 12-hour or 24-hour clock.
     *
     * @param calendar The Calendar object to retrieve the hour from
     * @return String The hour based on whether it's a 12-hour/24-hour clock
     */
	static String getHourString(Calendar calendar) {
        if (CLOCK_USES_AM_PM) {
            int hour = calendar.get(Calendar.HOUR);
            return (hour == 0) ? "12" : "" + hour;
        } else {
            return Integer.toString(calendar.get(Calendar.HOUR_OF_DAY));
        }
	}


    /**
     * Translate the mode of a DateField into a readable string.
     *
     * @param currentDate The current date set in the DateField.
     * @param mode The mode to translate.
     * @param initialized <code>true</code> if the human-readable string has
     *            been initialized; <code>false</code>, otherwise.
     * @return String A human-readable string representing the mode of the
     *              DateField.
     */
    static String toString(Calendar currentDate, int mode, 
			               boolean initialized) 
    {
        String str = null;
        if (!initialized) {
            switch (mode) {
                case DateField.DATE:
                    str = Resource.getString(ResourceConstants.LCDUI_DF_DATE);
                    break;
                case DateField.TIME:
                    str = Resource.getString(ResourceConstants.LCDUI_DF_TIME);
                    break;
                case DateField.DATE_TIME:
                    str = Resource.getString(
                        ResourceConstants.LCDUI_DF_DATETIME);
                    break;
                default:
                    // for safety/completeness.
                    Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                        "DateFieldLFImpl: mode=" + mode);
                    break;
            }
        } else {
            switch (mode) {
                case DateField.DATE:
                    str = Resource.getDateString(
                        dayOfWeekString(currentDate), 
                        "" + currentDate.get(Calendar.DATE), 
                        MONTH_NAMES[currentDate.get(Calendar.MONTH)].
                        substring(0, 3), 
                        Integer.toString(currentDate.get(Calendar.YEAR)));
                    break;
                case DateField.TIME:
                    str = Resource.getTimeString(getHourString(currentDate), 
                        "" + twoDigits(currentDate.get(Calendar.MINUTE)),
                        "" + currentDate.get(Calendar.SECOND), 
                        ampmString(currentDate));
                    break;
                case DateField.DATE_TIME:
                    str = Resource.getDateTimeString(
                        dayOfWeekString(currentDate), 
                        Integer.toString(currentDate.get(Calendar.DATE)), 
                        MONTH_NAMES[currentDate.get(Calendar.MONTH)].
                        substring(0, 3), 
                        Integer.toString(currentDate.get(Calendar.YEAR)), 
                        getHourString(currentDate), 
                        twoDigits(currentDate.get(Calendar.MINUTE)), 
                        Integer.toString(currentDate.get(Calendar.SECOND)), 
                        ampmString(currentDate));
                    break;
                default:
                    // for safety/completeness.
                    Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI,
                        "DateFieldLFImpl: mode=" + mode);
                    break;
            }
        }
        return str;
    }

    /**
     * Static array holding the names of the 12 months.
     */
    static final String[] MONTH_NAMES = {
        Resource.getString(ResourceConstants.LCDUI_DF_JANUARY), 
        Resource.getString(ResourceConstants.LCDUI_DF_FEBRUARY),
        Resource.getString(ResourceConstants.LCDUI_DF_MARCH),
        Resource.getString(ResourceConstants.LCDUI_DF_APRIL),
        Resource.getString(ResourceConstants.LCDUI_DF_MAY),
        Resource.getString(ResourceConstants.LCDUI_DF_JUNE),
        Resource.getString(ResourceConstants.LCDUI_DF_JULY),
        Resource.getString(ResourceConstants.LCDUI_DF_AUGUST),
        Resource.getString(ResourceConstants.LCDUI_DF_SEPTEMBER),
        Resource.getString(ResourceConstants.LCDUI_DF_OCTOBER),
        Resource.getString(ResourceConstants.LCDUI_DF_NOVEMBER),
        Resource.getString(ResourceConstants.LCDUI_DF_DECEMBER)
    };

    /**
     * Flag to signal the clock representation uses AM and PM notation.
     * This is dummy right now, and always set to true since there is
     * not easy to find out from the system if the clock is using a 
     * 12-hour or 24-hour mode.
     */
    static final boolean CLOCK_USES_AM_PM = true;
    
    /**
     * DateField associated with this view.
     */
    DateField df;

    /**
     * Current date being handled by this datefield impl.
     */
    private Calendar currentDate;

    /**
     * Current input mode being handled by this datefield impl.
     */
    int mode;

    /**
     * A flag indicating a prior call to uCallTraverse()
     */
    private boolean traversedIn;
    
    /**
     * The editor for this DateField.
     */
    DateEditor editor = null;

}
