/*
 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 in the LICENSE file that
 * accompanied this code).
 *
 * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.glass.ui.win;

import com.sun.glass.ui.Application;
import com.sun.glass.ui.Pixels;
import com.sun.glass.ui.SystemClipboard;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

class WinSystemClipboard extends SystemClipboard {

    private static native void initIDs();
    static {
        initIDs();
    }

    private long ptr = 0L; //native pointer

    protected WinSystemClipboard(String name) {
        super(name);
        create();
    }

    protected final long getPtr() {
        return ptr;
    }

    protected native boolean isOwner();

    protected native void create();
    protected native void dispose();

    /*
     * public mime types to system clipboard
     */
    protected native void push(Object[] keys, int supportedActions);

    /*
     * extract clipboard snap-shot
     */
    protected native boolean pop();

    static final byte[] terminator = new byte[] { 0, 0 };
    static final String defaultCharset = "UTF-16LE";
    static final String RTFCharset = "US-ASCII";

    // Called from native code
    private byte[] fosSerialize(String mime, long index) {
        Object data = getLocalData(mime);
        if (data instanceof ByteBuffer) {
            byte[] b = ((ByteBuffer)data).array();
            if (HTML_TYPE.equals(mime)) {
                b = WinHTMLCodec.encode(b);
            }
            return b;
        } else if (data instanceof String) {
            String st = ((String) data).replaceAll("(\r\n|\r|\n)", "\r\n");
            if (HTML_TYPE.equals(mime)) {
                try {
                    // NOTE: Transfer of HTML data on Windows uses UTF-8 encoding!
                    byte[] bytes = st.getBytes(WinHTMLCodec.defaultCharset);
                    ByteBuffer ba = ByteBuffer.allocate(bytes.length + 1);
                    ba.put(bytes);
                    ba.put((byte)0);

                    return WinHTMLCodec.encode(ba.array());
                } catch (UnsupportedEncodingException ex) {
                    // never happen
                    return null;
                }
            } else if (RTF_TYPE.equals(mime)) {
                try {
                    // NOTE: Transfer of RTF data on Windows uses US-ASCII encoding!
                    byte[] bytes = st.getBytes(RTFCharset);
                    ByteBuffer ba = ByteBuffer.allocate(bytes.length + 1);
                    ba.put(bytes);
                    ba.put((byte)0);
                    return ba.array();
                } catch (UnsupportedEncodingException ex) {
                    // could happen on user error
                    return null;
                }
            } else {
                ByteBuffer ba = ByteBuffer.allocate((st.length() + 1) * 2);
                try {
                    ba.put(st.getBytes(defaultCharset));
                } catch (UnsupportedEncodingException ex) {
                    //never happen
                }
                ba.put(terminator);
                return ba.array();
            }
        } else if (FILE_LIST_TYPE.equals(mime)) {
            String[] ast = ((String[]) data);
            if (ast != null && ast.length > 0) {
                int size = 0;
                for (String st : ast) {
                    size += (st.length() + 1) * 2;
                }
                size += 2;
                try {
                    ByteBuffer ba = ByteBuffer.allocate(size);
                    for (String st : ast) {
                        ba.put(st.getBytes(defaultCharset));
                        ba.put(terminator);
                    }
                    ba.put(terminator);
                    return ba.array();
                } catch (UnsupportedEncodingException ex) {
                    //never happen
                }
            }
        } else if (RAW_IMAGE_TYPE.equals(mime)) {
            Pixels pxls = (Pixels)data;
            if (pxls != null) {
                ByteBuffer ba = ByteBuffer.allocate(
                        pxls.getWidth() * pxls.getHeight() * 4 + 8);
                ba.putInt(pxls.getWidth());
                ba.putInt(pxls.getHeight());
                ba.put(pxls.asByteBuffer());
                return ba.array();
            }
        }
        //TODO: customizes for OS specific cases
        return null;
    }

    private static final class MimeTypeParser {
        protected static final String externalBodyMime = "message/external-body";
        protected String mime;
        protected boolean bInMemoryFile;
        protected int index;

        public MimeTypeParser() {
            parse("");
        }

        public MimeTypeParser(String mimeFull) {
            parse(mimeFull);
        }

        public void parse(String mimeFull) {
            mime = mimeFull;
            bInMemoryFile = false;
            index = -1;
            //we are limited here by [message/external-body] mime type with clipboard acess-type,
            //because NetBeans has a clipboard format that includes [;]
            if (mimeFull.startsWith(externalBodyMime)) {
                String mimeParts[] = mimeFull.split(";");
                String accessType = "";
                int indexValue = -1;
                //RFC 1521 extension for [message/external-body] mime
                for (int i = 1; i < mimeParts.length; ++i) {
                    String params[] = mimeParts[i].split("=");
                    if (params.length == 2) {
                        if( params[0].trim().equalsIgnoreCase("index") ) {
                            //that is OK to have the runtime-exception here
                            //we already have a chance to have an exception in WinHTMLCodec
                            indexValue = Integer.parseInt(params[1].trim());
                        } else if( params[0].trim().equalsIgnoreCase("access-type") ) {
                            accessType = params[1].trim();
                        }
                    }
                    if (indexValue != -1 && !accessType.isEmpty()) {
                        //Better to stop here to avoid problem with "index=100.url" filename
                        //it is not a security problem - we can request any index without
                        //buffer overflow or null pointer exception
                        break;
                    }
                }
                //we are responsible only for FX synthetic access type!
                if (accessType.equalsIgnoreCase("clipboard")) {
                    bInMemoryFile = true;
                    mime = mimeParts[0];
                    index = indexValue;
                }
            }
        }

        public String getMime() {
            return mime;
        }

        public int getIndex() {
            return index;
        }

        public boolean isInMemoryFile() {
            return bInMemoryFile;
        }
    }

    protected final void pushToSystem(HashMap<String, Object> cacheData, int supportedActions) {
        Set<String> mimes = cacheData.keySet();
        Set<String> mimesForSystem = new HashSet<String>();
        MimeTypeParser parser = new MimeTypeParser();
        for (String mime : mimes) {
            parser.parse(mime);
            if ( !parser.isInMemoryFile() ) {
                //[message/external-body] mime with [access-type=clipboard]
                //could not be exported to the system due to synthetic nature (Win-API subst),
                //but it could be used for applcation-wide communication
                mimesForSystem.add(mime);
            }
        }
        push(mimesForSystem.toArray(), supportedActions);
    }

    private native byte[] popBytes(String mime, long index);
    protected final Object popFromSystem(String mimeFull) {
        //we have to syncronize with system ones per
        //a popFromSystem function call, because
        //mime type data could be a collection of
        //sub-mimes likes "ms-stuff/XXXX"
        if ( !pop() ) {
            return null;
        }

        MimeTypeParser parser = new MimeTypeParser(mimeFull);
        String mime = parser.getMime();
        byte[] data = popBytes(mime, parser.getIndex());
        if (data != null) {
            if (TEXT_TYPE.equals(mime) || URI_TYPE.equals(mime)) {
                try {
                    // RT-16199 - internal Windows data null terminated
                    return new String(data, 0, data.length - 2, defaultCharset);
                } catch (UnsupportedEncodingException ex) {
                    //never happen
                }
            } else if (HTML_TYPE.equals(mime)) {
                try {
                    data = WinHTMLCodec.decode(data);
                    return new String(data, 0, data.length, WinHTMLCodec.defaultCharset);
                } catch (UnsupportedEncodingException ex) {
                    //never happen
                }
            } else if (RTF_TYPE.equals(mime)) {
                try {
                    return new String(data, 0, data.length, RTFCharset);
                } catch (UnsupportedEncodingException ex) {
                    //can happen for bad system data
                }
            } else if (FILE_LIST_TYPE.equals(mime)) {
                try {
                    String st = new String(data, 0, data.length, defaultCharset);
                    return st.split("\0");
                } catch (UnsupportedEncodingException ex) {
                    //never happen
                }
            } else if (RAW_IMAGE_TYPE.equals(mime)) {
                ByteBuffer size = ByteBuffer.wrap(data, 0, 8);
                return Application.GetApplication().createPixels(size.getInt(), size.getInt(),  ByteBuffer.wrap(data, 8, data.length - 8) );
            } else {
                return ByteBuffer.wrap(data);
            }
        } else {
            //alternative extraction if any
            if (URI_TYPE.equals(mime) || TEXT_TYPE.equals(mime)) {
                //try 8bit version
                data = popBytes(mime + ";locale", parser.getIndex());
                if (data != null) {
                    try {
                        // RT-16199 - internal Windows data null terminated
                        // Here we can request the "ms-stuff/locale" mime data
                        // from GlassClipbord for codepage detection, but
                        // for the most of cases [UTF-8] is ok.
                        return new String(data, 0, data.length - 1, "UTF-8");
                    } catch (UnsupportedEncodingException ex) {
                        //could happen, but not a problem
                    }
                }
            }
            if (URI_TYPE.equals(mime)) {
                //we are here if [text/uri-list;locale] mime is absent or
                //URL could not be decoded from the [data] as String
                String[] ret = (String[])popFromSystem(FILE_LIST_TYPE);
                if (ret != null) {
                    StringBuilder out = new StringBuilder();
                    //"text/uri-list" spec: http://www.ietf.org/rfc/rfc2483.txt
                    for (int i = 0; i < ret.length; i++) {
                        String fileName = ret[i];
                        fileName = fileName.replace("\\", "/");
                        //fileName = fileName.replace(" ", "%20");
                        if (out.length() > 0) {
                            out.append("\r\n");
                        }
                        out.append("file:/").append(fileName);
                    }
                    return out.toString();
                }
            }
        }
        return null;
    }

    private native String[] popMimesFromSystem();
    protected final String[] mimesFromSystem() {
        //we have to syncronize with system
        //if we heed to do it. DnD clipboard need not.
        if (!pop()) {
            return null;
        }
        return popMimesFromSystem();
    }

    @Override public String toString() {
        return "Windows System Clipboard";
    }

    @Override protected final void close() {
        dispose();
        ptr = 0L;
    }

    @Override protected native void pushTargetActionToSystem(int actionDone);

    private native int popSupportedSourceActions();
    @Override protected int supportedSourceActionsFromSystem() {
        if (!pop()) {
            return ACTION_NONE;
        }
        return popSupportedSourceActions();
   }
}