// Copyright 2003 Brian Alliet, see the COPYING file for licensing [LGPL]

package org.xwt.plat;
import org.xwt.js.JS;

import gnu.gcj.RawData;
import org.xwt.util.*;
import org.xwt.*;
import java.util.*;

public class Cocoa extends POSIX {
    static Cocoa singleton;
    gnu.gcj.RawData rawWindowDelegate;
    
    private String[] fontList;
    private Hashtable fontWrapperMap = new Hashtable();
    private Hashtable fontFamilyMap = new Hashtable();
    
    // General Methods
    protected String _getAltKeyName() { return "Option"; }
    protected String[] _listFonts() { return fontList; }
    protected boolean _needsAutoClick() { return false; }
    protected boolean _needsAutoDoubleClick() { return false; }
    protected String getDescriptiveName() { return "GCJ Cocoa Binary"; }
    // Should be 13, but some stuff seems to assume 12
    protected String _getDefaultFont() { return "dialog12"; }
    protected boolean _isCaseSensitive() { return false; /* Well, not always, could be UFS */ }
    
    
    // Native Methods
    protected native int    _getScreenWidth();
    protected native int    _getScreenHeight();
    protected native String _getClipBoard();
    protected native void   _setClipBoard(String text);
    protected native void   _newBrowserWindow(String url);
    protected native String _fileDialog(String suggestedFileName, boolean write);
    protected native Proxy   natDetectProxy();
    private   native void    natInit();
    private   native void    initFonts();
    protected native void   _exit();
    
    private static native NSFontWrapper natFontWrapper(String family,int size,boolean bold,boolean italic,boolean underline);
    
    private static native void retain(gnu.gcj.RawData o);
    private static native void release(gnu.gcj.RawData o);
    
    // Called by main thread after initialization, this is the event handler
    protected native void _running();
    
    public void finalize() { release(rawWindowDelegate); }
    
    static void abort(String err) {
        throw new Error(err);
    }
    
    public Cocoa() {
        synchronized(Cocoa.class) {
            if(singleton != null) abort("Tried to instansiate Cocoa more than once");
            singleton = this;
        }
    }
    
    private static class NSFontWrapper {
        private gnu.gcj.RawData rawNSFont;
        private boolean underline;
        public NSFontWrapper(gnu.gcj.RawData f, boolean u) {
            rawNSFont = f;
            underline = u;
            retain(rawNSFont);
        }
        public gnu.gcj.RawData getNSFont() { return rawNSFont; }
        public boolean getUnderline() { return underline; }
        
        public native int stringWidth(String text);
        public native int maxDescent();
        public native int lineHeight();
        
        public int maxAscent() { return lineHeight() - maxDescent(); }
        
        public void finalize() {
            release(rawNSFont);
        }
    }
    
    NSFontWrapper fontWrapper(String font) {
        font = font.toLowerCase();
        NSFontWrapper fw = (NSFontWrapper) fontWrapperMap.get(font);
        if(fw != null) return fw;
        
        ParsedFont pf = new ParsedFont(font);
        String family = (String)fontFamilyMap.get(pf.name);
        if(family == null) family = "Lucida Grande";
        if(pf.size == 0) pf.size = 10;
        fw = natFontWrapper(family,pf.size,pf.bold,pf.italic,pf.underline);
        fontWrapperMap.put(font,fw);
        return fw;
    }
    
    protected int _stringWidth(String font, String text) { return fontWrapper(font).stringWidth(text); }
    protected int _getMaxAscent(String font) { return fontWrapper(font).maxAscent(); }
    protected int _getMaxDescent(String font) { return fontWrapper(font).maxDescent(); }
    protected int _getLineHeight(String font) { return fontWrapper(font).lineHeight(); }
    
    protected synchronized Proxy _detectProxy() {
        return natDetectProxy();
    }
        
    
    public void init() {
        super.init();
        natInit();
        final JS.Callable asfunc = new JS.Callable() {
            public Object call(JS.Array args) throws JS.Exn {
                if (args.length() != 1 || args.elementAt(0) == null) return null;
                if (!ThreadMessage.suspendThread()) return null;
                try {
                    return new AppleScript(args.elementAt(0).toString());
                } finally {
                    ThreadMessage.resumeThread();
                }
            }
        };
        jsObject.put("applescript",asfunc);
        
        // The mapped font name MUST be in proper case
        fontFamilyMap.put("serif","Times");
        fontFamilyMap.put("sandserif","Helvetica");
        fontFamilyMap.put("monospace","Andale Mono");
        fontFamilyMap.put("dialog","Lucida Grande");
        fontFamilyMap.put("tty","Courier");
        initFonts();
    }
    
    final public static class CocoaSurface extends Surface {
        private gnu.gcj.RawData rawWindow;
        private gnu.gcj.RawData rawView;
        boolean okToDraw = false;
        
        public native void setInvisible(boolean i);
        public native void _setMaximized(boolean b);
        public native void _setMinimized(boolean b);
        public native void setIcon(Picture p);
        public native void setTitleBarText(String s);
        public native void setSize(int w, int h);
        public native void setLocation(int x, int y);
        public native void toFront();
        public native void toBack();
        public native void syncCursor();
        public native void setLimits(int minWidth, int minHeight, int maxWidth, int maxHeight);

        /* Drawing stuff */
        public native void setClip(int x, int y, int x2, int y2);
        public native void resetClip();
        public native void fillRect(int x, int y, int x2, int y2, int color);
        public native void clearRect(int x, int y, int x2, int y2);
        public native void drawString(String font, String text, int x, int y, int color);
        public native void drawPicture(Picture source, int x, int y);
        public native void drawPicture(Picture source, int dx1, int dy1, int dx2, int dy2,
            int sx1, int sy1, int sx2, int sy2);
        public native void flush();
        protected native boolean render2();
        
        public void flush(int x, int y, int w, int h) {
            // FIXME: We can't tell Quartz to only flush part of the screen
        }
        
        public void dirtyScreen(int x,int y, int w, int h) {
            // NOTHING. Quartz handles this for us
        }
        
        public native void setMoveWithMouse(boolean on);
        public native void _setAlwaysOnTop(boolean on);
        
        public  native void _dispose();
        private native void natInit(int type);
                        
        public void finalize() {
            if(rawWindow != null)
                release(rawWindow);
        }
        
        public CocoaSurface(Box root, int type) {
            super(root);
            natInit(type);
        }
    }
    
    public static class CocoaPicture extends Picture {
        /* These have package level access so we can access them from drawPicture() */
        int width;
        int height;
        gnu.gcj.RawData rawCGImageRef;
        
        public int getWidth() { return width; }
        public int getHeight() { return height; }
        
        private native void natInit(int[] data);
        public native void finalize();
        
        public CocoaPicture(int[] data,int w, int h) {
            this.width = w;
            this.height = h;
            natInit(data);
        }
    }

    protected Surface _createSurface(Box b, int type) {
        Surface s = new CocoaSurface(b,type);
        return s;
    }
        
    protected Picture _createPicture(int[] data, int w, int h) {
        return new CocoaPicture(data,w,h);
    }

    // TODO: This could be a lot faster 
    public static class Retainer {
        private static Hashtable table = new Hashtable();
        private final static class Entry {
            private Object o;
            public int refCount=1;
        }
        
        private static native long addr(Object o);
        public static synchronized void retain(Object o) {
            Long key = new Long(addr(o));
            Entry e = (Entry) table.get(key);
            if(e == null) {
                e = new Entry();
                e.o = o;
                table.put(key,e);
                //Log.log(Retainer.class,"Retained " + key);
            } else {
                e.refCount++;
            }
        }
        public static synchronized void release(Object o) {
           	Long key = new Long(addr(o));
            Entry e = (Entry) table.get(key);
            if(e == null) Cocoa.abort("Retainer::Release on unknown object");
            if(--e.refCount==0) { Log.log(Retainer.class,"Released " + key); table.remove(key); }
        }
    }
    
    private static class AppleScript extends JS.Obj {
        private RawData rawMyASData;
        private String script;
        
        public AppleScript(String script) throws JS.Exn {
            this.script = script;
            
            final JS.Callable execfn = new JS.Callable() {
                public Object call(JS.Array args) throws JS.Exn {
                    if(args.length() != 0) throw new JS.Exn("No args expected to exec()");
                    if (!ThreadMessage.suspendThread()) return null;
                    try {
                        return exec();
                    } catch(ASException e) {
                        throw new JS.Exn(e.getMessage());
                    } finally {
                        ThreadMessage.resumeThread();
                    }
                }
            };
            final JS.Callable callfn =new JS.Callable() {
                public Object call(JS.Array args) throws JS.Exn {
                    if(args.length() == 0) throw new JS.Exn("At least one arg expected to call()");
                    if (!ThreadMessage.suspendThread()) return null;
                    try {
                        return AppleScript.this.call(args);
                    } catch(ASException e) {
                        throw new JS.Exn(e.getMessage());
                    } finally {
                        ThreadMessage.resumeThread();
                    }
                }
            };
            final JS.Callable compilefn = new JS.Callable() {
                public Object call(JS.Array args) throws JS.Exn {
                    if(args.length() != 0) throw new JS.Exn("No args expected to compile()");
                    if (!ThreadMessage.suspendThread()) return null;
                    try {
                        compile();
                    } catch(ASException e) {
                        throw new JS.Exn(e.getMessage());
                    } finally {
                        ThreadMessage.resumeThread();
                    }
                    return null;
                }
            };
            put("exec",execfn);
            put("call",callfn);
            put("compile",compilefn);
            setSeal(true);
        }
        public native void compile() throws ASException;
        public native Object exec() throws ASException;
        public native Object call(JS.Array args) throws ASException;
        public native void finalize();
        
        public static Object jsArrayForObjects(Object[] objs) {
            JS arr = new JS.Array();
            for(int i=0;i<objs.length;i++)
                arr.put(new Integer(i),objs[i]);
            return arr;
        }
        
        public static Object jsHashtableForObjects(Object[] objs) {
            JS hash = new JS.Obj();
            for(int i=0;i<objs.length-1;i+=2)
                hash.put(objs[i].toString(),objs[i+1]);
            return hash;
        }
        
        public static class ASException extends Exception {
            public ASException(String msg) { super(msg); }
        }
        public static Integer castToInteger(Object o) {
            if(o instanceof Integer) return (Integer) o;
            return null;
        }
        public static Number castToNumber(Object o) {
            if(o instanceof Number) return (Number) o;
            return null;
        }
        public static Boolean castToBoolean(Object o) {
            if(o instanceof Boolean) return (Boolean) o;
            return null;
        }
        public static JS castToJS(Object o) {
            if(o instanceof JS) return (JS) o;
            return null;
        }
        public static JS.Array castToJSArray(Object o) {
            if(o instanceof JS.Array) return (JS.Array) o;
            return null;
        }
    }
}
