/*
 * 
 * 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 com.sun.mmedia;

import java.util.*;
import java.lang.IllegalArgumentException;
import java.lang.IllegalStateException;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Item;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.CustomItem;
import javax.microedition.media.Control;
import javax.microedition.media.MediaException;
import javax.microedition.media.control.VideoControl;
import javax.microedition.media.PlayerListener;

import com.sun.midp.midlet.Scheduler;
import com.sun.midp.midlet.MIDletSuite;
import com.sun.midp.configurator.Constants;
import com.sun.midp.security.*;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;


/**
 * Video direct player
 * it implements VideoControl
 */
public class DirectVideo extends DirectPlayer implements 
    VideoControl, MIDPVideoPainter, ImplicitlyTrustedClass {

    private final int SCREEN_WIDTH = nGetScreenWidth();
    private final int SCREEN_HEIGHT = nGetScreenHeight();
    private final int DEFAULT_WIDTH = 80;
    private final int DEFAULT_HEIGHT = 80;
    private final int ALPHA_COLOR = 1;

    // NOTE: You have to calibrate this value carefully
    //       If you increase this value, fake preview quality goes down but, system overhead decrease
    //       If you decrease this value, fake preview quality goes up but, system overhead increase
    //       If you set this value 0 or negative value, fake preview support still image only
    private final static int FAKE_PREVIEW_INTERVAL = 250;
    
    // Canvas and item reference
    private Canvas canvas;
    private DVItem item;

    // original video size    
    private int sw;
    private int sh;

    // Display position and size
    private int dx;
    private int dy;
    private int dw;
    private int dh;

    // visible?
    private boolean visible = false;
    private boolean hidden = false;
    private boolean started = false;
    private boolean locationInited = false; // Is location initialized?
    
    // current display mode
    private int displayMode = -1;
    // MMHelper to communicate with Canvas
    private MMHelper mmh = null;
    // Lock
    private Object boundLock = new Object();

    /** This class has a different security domain than the MIDlet suite */
    private static SecurityToken classSecurityToken;
    
    // native functions /////////////////////////////////////////////

    // Get video width
    protected native int nGetWidth(int handle);
    // Get video height
    protected native int nGetHeight(int handle);
    // Set display location of video
    protected native boolean nSetLocation(int handle, int x, int y, int w, int h);
    // Get snapshot
    protected native byte[] nSnapShot(int handle, String imageType);
    // Set visible
    protected native boolean nSetVisible(int handle, boolean visible);
    // Get screen full width
    private native int nGetScreenWidth();
    // Get screen full height
    private native int nGetScreenHeight();
    // Turn on or off alpha channel
    private native int nSetAlpha(boolean on, int color);

    // member functions /////////////////////////////////////////////

    public DirectVideo() {
    }
    
    /**
     * Initializes the security token for this class, so it can
     * perform actions that a normal MIDlet Suite cannot.
     *
     * @param token security token for this class.
     */
    public final void initSecurityToken(SecurityToken token) {
        if (classSecurityToken != null) {
            return;
        }
        classSecurityToken = token;
    }

    /**
     * Check snapshot permission
     */
    protected void checkPermission() {
        try {
            Scheduler scheduler = Scheduler.getScheduler();
            MIDletSuite midletSuite = scheduler.getMIDletSuite();
            midletSuite.checkForPermission(Permissions.MM_IMAGE_CAPTURING, null);
        } catch (InterruptedException e) {
            throw new SecurityException(
                    "Interrupted while trying to ask the user permission");
        }
    }
    
    /**
     * Is in clipping area?
     */
    private boolean isInClippingArea(Graphics g, int x, int y, int w, int h) {
        int diffx = g.getTranslateX();
        int diffy = g.getTranslateY();
        int clipx = g.getClipX();
        int clipy = g.getClipY();
        int clipw = g.getClipWidth();
        int cliph = g.getClipHeight();

        x += diffx;
        y += diffy;
        clipx += diffx;
        clipy += diffy;

        if (x < clipx) return false;
        if (y < clipy) return false;
        if (x + w > clipx + clipw) return false;
        if (y + h > clipy + cliph) return false;

        if (debug) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                "isInClippingArea return true - No graphic outside of clipping area"); 
        }  

        return true;
    }

    /**
     *
     */
    private void setTranslatedVideoLocation(Graphics g, int x, int y, int w, int h) {
        int diffx = g.getTranslateX();
        int diffy = g.getTranslateY();
        int px, py, pw, ph;
 
        // Calcurate positions
        // And, do a physical clipping
        // Currently, Zoran chipset does not support negative position and exceed position
        px = x + diffx;
        py = y + diffy;
        pw = w;
        ph = h;

        if (px + pw <= 0) {
            return;
        }
        if (py + ph <= 0) {
            return;
        }
        if (px >= SCREEN_WIDTH) {
            return;
        }
        if (py >= SCREEN_HEIGHT) {
            return;
        }
        if (px < 0) {
            pw += px;
            px = 0;
        }
        if (py < 0) {
            ph += py;
            py = 0;
        }
        if (px + pw > SCREEN_WIDTH) {
            pw = SCREEN_WIDTH - px;
        }
        if (py + ph > SCREEN_HEIGHT) {
            ph = SCREEN_HEIGHT - py;
        }

        if (hNative != 0) {
            nSetLocation(hNative, px, py, pw, ph);
        }
    }

    /**
     * Prepare direct video rendering surface
     */
    private void prepareVideoSurface(Graphics g, int x, int y, int w, int h) {
        if (debug) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                "prepareVideoSurface " + x + "," + y + "," + w + "," + h); 
        }    
 
        // Turn off alpha channel
        nSetAlpha(false, ALPHA_COLOR);
        setTranslatedVideoLocation(g, x, y, w, h);

        // set location and size of display region
        if (hNative != 0) {
            nSetVisible(hNative, true);
        }
    }

    /**
     * Prepare clipped preview region by using alpha channel masking
     */
    private void prepareClippedPreview(Graphics g, int x, int y, int w, int h) {
        if (1 == nSetAlpha(true, ALPHA_COLOR)) {
            g.setColor(0, 0, 8);    // IMPL NOTE - Consider RGB565 conversion
            g.fillRect(x, y, w, h);
            setTranslatedVideoLocation(g, x, y, w, h);
            if (hNative != 0) {
                nSetVisible(hNative, true);
            }
        } else {
            if (hNative != 0) {
                nSetVisible(hNative, false);
            }
        }
    }

    /**
     * request to repaint
     */
    private void repaint() {
        if (canvas != null) {
            canvas.repaint();
        } else if (item != null) {
            item.forcePaint();
        }
    }

    /**
     * request to repaint canvas and wait until that processed
     */
    private void repaintAndWait() {
        if (canvas != null) {
            canvas.repaint();
            canvas.serviceRepaints();
        } else if (item != null) {
            item.forcePaint();
        }
    }

    /**
     * Check mode value
     */
    protected void checkState() {
        if (displayMode == -1) {
            throw new IllegalStateException("initDisplayMode not called yet");
        }
    }

    /**
     * Override doGetControl
     * return VideoControl and GUIControl
     */
    protected Control doGetControl(String type) {
        Control c = super.doGetControl(type);

        if (c == null) {
            String prefix = "javax.microedition.media.control.";
            if (type.equals(prefix + vicName)) {        // VideoControl
                return this;
            } else if (type.equals(prefix + guiName)) {  // GUIControl
                return this;
            }
        }
        return c;
    }

    /**
     * Override doRealize
     * Prepare soure video informations
     */ 
    protected void doRealize() throws MediaException {
        super.doRealize();
        sw = nGetWidth(hNative);
        sh = nGetHeight(hNative);
        // initialize default rendering width and height
        if (sw <= 0) dw = DEFAULT_WIDTH;
        else dw = sw;
        if (sh <= 0) dh = DEFAULT_HEIGHT;
        else dh = sh;
    }

    protected boolean doStart() {
        started = true;
        repaintAndWait();
        return super.doStart();
    }

    protected void doStop() throws MediaException {
        started = false;
        super.doStop();
    }
    
    /**
     * Init display mode
     */
    public Object initDisplayMode(int mode, Object container) {
        if (debug) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                "initDisplayMode mode=" + mode + ", container=" + container); 
        }     
        
        Object ret = null;
        
        if (displayMode != -1) {
            throw new IllegalStateException("mode already set");
        }
        if (mode != USE_DIRECT_VIDEO && mode != USE_GUI_PRIMITIVE) {
            throw new IllegalArgumentException("unsupported mode");
        }
        if (mode == USE_DIRECT_VIDEO && !(container instanceof Canvas)) {
            throw new IllegalArgumentException("container needs to be a Canvas");
        }
        if (mode == USE_GUI_PRIMITIVE && container != null) {
            if (!(container instanceof String)) {
                throw new IllegalArgumentException("container not valid");
            }
            if (!(container.equals("javax.microedition.lcdui.Item"))) {
                throw new IllegalArgumentException("container not valid");
            }
        }

        if (mode == USE_DIRECT_VIDEO) {
            canvas = (Canvas)container;
            if (mmh == null) {
                mmh = MIDPRendererCanvasBuddy.getMMHelper();
                if (mmh == null) {
                    throw new RuntimeException("initDisplayMode: unable to set the display mode");
                }
            }
            displayMode = mode;
            // register this direct video handler to MMH
            // MMH used to communicate with Canvas
            mmh.registerPlayer(canvas, this);
            setDisplayLocation(dx, dy);
        } else {
            displayMode = mode;
            item = new DVItem(null);
            ret = (Object)item;
            visible = true;
        }
        
        return ret;
    }

    /**
     * Set display location 
     */
    public void setDisplayLocation(int x, int y) {
        if (debug) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                "setDisplayLocation x=" + x + ",y=" + y); 
        }
        checkState();
        if (displayMode == USE_DIRECT_VIDEO) {
            synchronized(boundLock) {
                dx = x;
                dy = y;
            }
            if (dw != 0 && dh !=0) {
                repaintAndWait();
            }
        }
    }
    
    /**
     * Set display size
     */
    public void setDisplaySize(int width, int height) throws MediaException {
        if (debug) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                "setDisplaySize w=" + width + ",h=" + height); 
        }

        boolean sizeChanged = false;
        
        checkState();
        if (width < 1 || height < 1) {
            throw new IllegalArgumentException("invalid size");
        }

        synchronized(boundLock) {
            if (dw != width && dh != height) sizeChanged = true;
            dw = width;
            dh = height;
        }
        if (item != null) {
            // this will raise sizeChanged event
            // and sizeChanged shall raise paint event also
            item.setPreferredSize(width, height);
        }
        repaintAndWait();
        
        if (sizeChanged) {
            sendEvent(PlayerListener.SIZE_CHANGED, this);
        }
    }
    
    public int getDisplayX() {
        return dx;
    }
    
    public int getDisplayY() {
        return dy;
    }

    /**
     * Get actual width of rendering 
     */
    public int getDisplayWidth() {
        checkState();
        return dw;
    }
    
    /**
     * Get actual height of rendering
     */
    public int getDisplayHeight() {
        checkState();
        return dh;
    }

    /**
     * return source video width
     */
    public int getSourceWidth() {
        return sw;
    }
    
    /**
     * return source video height
     */
    public int getSourceHeight() {
        return sh;
    }

    /**
     * set visible or unvisible
     */
    public void setVisible(boolean visible) {
        boolean old = this.visible;
        checkState();
        this.visible = visible;

        if (old != visible) {
            repaintAndWait();
        }

        if (visible == false && hNative != 0) {
            nSetVisible(hNative, false);
        }
    }
    
    /**
     * There is no full screen mode now
     */
    public void setDisplayFullScreen(boolean fullScreenMode) throws MediaException {
        checkState();
        if (fullScreenMode) {
            throw new MediaException("No Fullscreen mode");
        }
    }
    
    /**
     * There is no snap shot support now
     */
    public byte[] getSnapshot(String imageType) throws MediaException {
        checkState();
        throw new MediaException("No snapshot support");
    }

    /**
     * called from Canvas.paint routine
     * We have to paint direct video region on Canvas
     * Notice: This have to be done before device painting action
     * Zoran ESDK use mask color to draw direct video
     */
    public void paintVideo(Graphics g) {
        int x, y, w, h;
        
        synchronized(boundLock) {
            x = dx;
            y = dy;
            w = dw;
            h = dh;
        }

        if (debug) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                "paintVideo x=" + x + ",y=" + y + ",w=" + w + ",h=" + h); 
        }

        if (hidden) {
            prepareClippedPreview(g, x, y, w, h);
        } else if (visible && started) {
            if (true == isInClippingArea(g, x, y, w, h)) {
                prepareVideoSurface(g, x, y, w, h);
            } else {
                int cx = g.getClipX();
                int cy = g.getClipY();
                int cw = g.getClipWidth();
                int ch = g.getClipHeight();
                g.setClip(x, y, w, h);
                prepareClippedPreview(g, x, y, w, h);
                g.setClip(cx, cy, cw, ch);
            }
        }
    }

    /**
     * Hide video preview (called from CanvasLFImpl)
     */
    public void hideVideo() {
        if (debug) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                "hideVideoPreview"); 
        }
        hidden = true;
        nSetAlpha(true, ALPHA_COLOR);
        repaint();
    }

    /**
     * Show video preview (called from CanvasLFImpl)
     */
    public void showVideo() {
        if (debug) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                "showVideoPreview"); 
        }
        hidden = false;
        nSetAlpha(false, ALPHA_COLOR);        
        repaint();
    }

    // Inner class ///////////////////////////////////////////////////////////

    /**
     * Support USE_GUI_PRIMITIVE mode
     */
    class DVItem extends CustomItem {

        DVItem(String label) {
            super(label);
        }
        
        void forcePaint() {
            repaint();
        }
        
        protected void paint(Graphics g, int w, int h) {
            if (debug) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, 
                    "DVItem.paint visible=" + visible); 
            }

            // Is in hidden state, then just draw fake preview
            if (hidden) {
                prepareClippedPreview(g, 0, 0, w, h);
            // Is out of hidden state, then check about clipping regions and
            // determind what to show
            } else if (visible) {
                if (true == isInClippingArea(g, 0, 0, w, h)) {
                    // Prepare video preview
                    prepareVideoSurface(g, 0, 0, w, h);
                } else {
                    prepareClippedPreview(g, 0, 0, w, h);
                }
            }
        }
        
        protected int getMinContentWidth() {
            return 1;
        }
        
        protected int getMinContentHeight() {
            return 1;
        }
        
        protected int getPrefContentWidth(int height) {
            return dw;
        }
        
        protected int getPrefContentHeight(int width) {
            return dh;
        }

        protected void sizeChanged(int w, int h) {
            synchronized(boundLock) {
                dw = w;
                dh = h;
            }
            repaint();
        }

        // Now this function used to control visible state of direct video preview
        // Called from MIDPWindow class
        protected void showNotify() {
            if (debug) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, "showNotify"); 
            }        
            hidden = false;
            repaint();
        }

        // Now this function used to control visible state of direct video preview
        // Called from MIDPWindow class
        protected void hideNotify() {
            if (debug) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_MMAPI, "hideNotify"); 
            }        
            hidden = true;
            repaint();
        }
    }

}

