/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.netbeans.html.boot.fx;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.prefs.Preferences;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Separator;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.Window;

final class FXToolbar extends ToolBar {
    private final ArrayList<ResizeBtn> resizeButtons;
    private final WebView webView;
    private final BorderPane container;
    private final ToggleGroup resizeGroup = new ToggleGroup();
    private final ComboBox<String> comboZoom = new ComboBox<String>();
    private WatchDir watcher;
    
    FXToolbar(WebView wv, BorderPane container, boolean enableFirebug) {
        this.webView = wv;
        this.container = container;
        
        List<ResizeOption> options = ResizeOption.loadAll();
        options.add( 0, ResizeOption.SIZE_TO_FIT );
        resizeButtons = new ArrayList<ResizeBtn>( options.size() );

        for( ResizeOption ro : options ) {
            ResizeBtn button = new ResizeBtn(ro);
            resizeButtons.add( button );
            resizeGroup.getToggles().add( button );
            getItems().add( button );
        }
        resizeButtons.get( 0 ).setSelected( true );
        resizeGroup.selectedToggleProperty().addListener( new InvalidationListener() {

            @Override
            public void invalidated( Observable o ) {
                resize();
            }
        });
        
        getItems().add( new Separator() );

        getItems().add( comboZoom );
        ArrayList<String> zoomModel = new ArrayList<String>( 6 );
        zoomModel.add( "200%" ); //NOI18N
        zoomModel.add( "150%" ); //NOI18N
        zoomModel.add( "100%" ); //NOI18N
        zoomModel.add( "75%" ); //NOI18N
        zoomModel.add( "50%" ); //NOI18N
        comboZoom.setItems( FXCollections.observableList( zoomModel ) );
        comboZoom.setEditable( true );
        comboZoom.setValue( "100%" ); //NOI18N
        comboZoom.valueProperty().addListener( new ChangeListener<String>() {

            @Override
            public void changed( ObservableValue<? extends String> ov, String t, String t1 ) {
                String newZoom = zoom( t1 );
                comboZoom.setValue( newZoom );
            }
        });
        
        getItems().add(new Separator());
        final CheckBox automatic = new CheckBox("Automatic");
        final Preferences prefs = Preferences.userNodeForPackage(FXToolbar.class);
        final String ar = "automaticReload"; // NOI18N
        automatic.setSelected(prefs.getBoolean(ar, true));
        getItems().add(automatic);
        final Button reload = new Button("Reload");
        getItems().add(reload);
        reload.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                webView.getEngine().reload();
            }
        });
        automatic.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                prefs.putBoolean(ar, automatic.isSelected());
                listenOnChanges(automatic.isSelected());
            }
        });
        webView.getEngine().locationProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                listenOnChanges(automatic.isSelected());
            }
        });
        if (enableFirebug){
        final Button firebug = new Button("Firebug");
        getItems().add(firebug);
        firebug.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                enableFirebug(webView.getEngine());
                firebug.setDisable(true);
            }
        });}
    }

    private String zoom( String zoomFactor ) {
        if( zoomFactor.trim().isEmpty() )
            return null;

        try {
            zoomFactor = zoomFactor.replaceAll( "\\%", ""); //NOI18N
            zoomFactor = zoomFactor.trim();
            double zoom = Double.parseDouble( zoomFactor );
            zoom = Math.abs( zoom )/100;
            if( zoom <= 0.0 )
                return null;
            webView.setScaleX(zoom);
            webView.setScaleY(zoom);
            webView.setScaleZ(zoom);
            return (int)(100*zoom) + "%"; //NOI18N
        } catch( NumberFormatException nfe ) {
            //ignore
        }
        return null;
    }

    private void resize() {
        Toggle selection = resizeGroup.getSelectedToggle();
        if( selection instanceof ResizeBtn ) {
            ResizeOption ro = ((ResizeBtn)selection).getResizeOption();
            if( ro == ResizeOption.SIZE_TO_FIT ) {
                _autofit();
            } else {
                _resize( ro.getWidth(), ro.getHeight() );
            }
        }

    }

    private void _resize(final double width, final double height) {
        Window window = container.getScene().getWindow();
        // size difference between root node and window depends on OS and Decorations
        double diffY = window.getHeight() - container.getHeight();
        double diffX = window.getWidth() - container.getWidth();

        webView.setMaxWidth(width);
        webView.setMaxHeight(height);
        webView.setMinWidth(width);
        webView.setMinHeight(height);
        javafx.geometry.Rectangle2D screenBounds = Screen.getPrimary().getBounds();
        double scaleX = screenBounds.getWidth() / ( width + diffX );
        double scaleY = screenBounds.getHeight() / ( height + diffY );
        // calculate scale factor if too big for device, the .1 adds some padding
        double scale = Math.min(Math.min(scaleX, scaleY), 1.1) - .1;
        webView.setScaleX(scale);
        webView.setScaleY(scale);
        container.getScene().setRoot(new Group());
        ((Stage)window).setScene(new Scene(container, width * scale, height * scale));
    }

    private void _autofit() {
        if (container.getCenter() != webView) {
            container.setCenter(webView);
        }
        webView.setMaxWidth( Integer.MAX_VALUE );
        webView.setMaxHeight( Integer.MAX_VALUE );
        webView.setMinWidth( -1 );
        webView.setMinHeight( -1 );
        webView.autosize();
    }

    /**
     * Button to resize the browser window.
     * Taken from NetBeans. Kept GPLwithCPEx license.
     * Portions Copyright 2012 Oracle.
     *
     * @author S. Aubrecht
     */
    static final class ResizeBtn extends ToggleButton {

        private final ResizeOption resizeOption;

        ResizeBtn(ResizeOption resizeOption) {
            super(null, new ImageView(toImage(resizeOption)));
            this.resizeOption = resizeOption;
            setTooltip(new Tooltip(resizeOption.getToolTip()));
        }

        ResizeOption getResizeOption() {
            return resizeOption;
        }

        static Image toImage(ResizeOption ro) {
            if (ro == ResizeOption.SIZE_TO_FIT) {
                return ResizeOption.Type.CUSTOM.getImage();
            }
            return ro.getType().getImage();
        }
    }

    /**
     * Immutable value class describing a single button to resize web browser window.
     * Taken from NetBeans. Kept GPLwithCPEx license.
     * Portions Copyrighted 2012 Sun Microsystems, Inc.
     *
     * @author S. Aubrecht
     */
    static final class ResizeOption {

        private final Type type;
        private final String displayName;
        private final int width;
        private final int height;
        private final boolean isDefault;

        enum Type {
            DESKTOP("desktop.png"), 
            TABLET_PORTRAIT("tabletPortrait.png"), 
            TABLET_LANDSCAPE("tabletLandscape.png"), 
            SMARTPHONE_PORTRAIT("handheldPortrait.png"), 
            SMARTPHONE_LANDSCAPE("handheldLandscape.png"), 
            WIDESCREEN("widescreen.png"), 
            NETBOOK("netbook.png"), 
            CUSTOM("sizeToFit.png");
            
            
            private final String resource;

            private Type(String r) {
                resource = r;
            }

            public Image getImage() {
                return new Image(Type.class.getResourceAsStream(resource));
            }
        }

        private ResizeOption(Type type, String displayName, int width, int height, boolean showInToolbar, boolean isDefault) {
            super();
            this.type = type;
            this.displayName = displayName;
            this.width = width;
            this.height = height;
            this.isDefault = isDefault;
        }

        static List<ResizeOption> loadAll() {
            List<ResizeOption> res = new ArrayList<ResizeOption>(10);
            res.add(ResizeOption.create(ResizeOption.Type.DESKTOP, "Desktop", 1280, 1024, true, true));
            res.add(ResizeOption.create(ResizeOption.Type.TABLET_LANDSCAPE, "Tablet Landscape", 1024, 768, true, true));
            res.add(ResizeOption.create(ResizeOption.Type.TABLET_PORTRAIT, "Tablet Portrait", 768, 1024, true, true));
            res.add(ResizeOption.create(ResizeOption.Type.SMARTPHONE_LANDSCAPE, "Smartphone Landscape", 480, 320, true, true));
            res.add(ResizeOption.create(ResizeOption.Type.SMARTPHONE_PORTRAIT, "Smartphone Portrait", 320, 480, true, true));
            res.add(ResizeOption.create(ResizeOption.Type.WIDESCREEN, "Widescreen", 1680, 1050, false, true));
            res.add(ResizeOption.create(ResizeOption.Type.NETBOOK, "Netbook", 1024, 600, false, true));
            return res;
        }
        
        /**
         * Creates a new instance.
         * @param type
         * @param displayName Display name to show in tooltip, cannot be empty.
         * @param width Screen width
         * @param height Screen height
         * @param showInToolbar True to show in web developer toolbar.
         * @param isDefault True if this is a predefined option that cannot be removed.
         * @return New instance.
         */
        public static ResizeOption create(Type type, String displayName, int width, int height, boolean showInToolbar, boolean isDefault) {
            if (width <= 0 || height <= 0) {
                throw new IllegalArgumentException("Invalid screen dimensions: " + width + " x " + height); //NOI18N
            }
            return new ResizeOption(type, displayName, width, height, showInToolbar, isDefault);
        }
        /**
         * An extra option to size the browser content to fit its window.
         */
        public static final ResizeOption SIZE_TO_FIT = new ResizeOption(Type.CUSTOM, "Size To Fit", -1, -1, true, true);

        public String getDisplayName() {
            return displayName;
        }

        public Type getType() {
            return type;
        }

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }

        public boolean isDefault() {
            return isDefault;
        }

        @Override
        public String toString() {
            return displayName;
        }

        public String getToolTip() {
            if (width < 0 || height < 0) {
                return displayName;
            }
            StringBuilder sb = new StringBuilder();
            sb.append(width);
            sb.append(" x "); //NOI18N
            sb.append(height);
            sb.append(" ("); //NOI18N
            sb.append(displayName);
            sb.append(')'); //NOI18N
            return sb.toString();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ResizeOption other = (ResizeOption) obj;
            if (this.type != other.type) {
                return false;
            }
            if ((this.displayName == null) ? (other.displayName != null) : !this.displayName.equals(other.displayName)) {
                return false;
            }
            if (this.width != other.width) {
                return false;
            }
            if (this.height != other.height) {
                return false;
            }
            if (this.isDefault != other.isDefault) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 11 * hash + (this.type != null ? this.type.hashCode() : 0);
            hash = 11 * hash + (this.displayName != null ? this.displayName.hashCode() : 0);
            hash = 11 * hash + this.width;
            hash = 11 * hash + this.height;
            hash = 11 * hash + (this.isDefault ? 1 : 0);
            return hash;
        }
    }
    
    private void listenOnChanges(boolean turnOn) {
        try {
            if (watcher != null) {
                watcher.close();
                watcher = null;
            }
            final WebEngine eng = webView.getEngine();
            if (turnOn && eng.getLocation().startsWith("file:")) { // NOI18N
                watcher = new WatchDir(eng);
            }
        } catch (Exception ex) {
            FXInspect.LOG.log(Level.SEVERE, null, ex);
        }
    }
       private static void enableFirebug(final WebEngine engine) {
        engine.executeScript("if (!document.getElementById('FirebugLite')){E = document['createElement' + 'NS'] && document.documentElement.namespaceURI;E = E ? document['createElement' + 'NS'](E, 'script') : document['createElement']('script');E['setAttribute']('id', 'FirebugLite');E['setAttribute']('src', 'https://getfirebug.com/' + 'firebug-lite.js' + '#startOpened');E['setAttribute']('FirebugLite', '4');(document['getElementsByTagName']('head')[0] || document['getElementsByTagName']('body')[0]).appendChild(E);E = new Image;E['setAttribute']('src', 'https://getfirebug.com/' + '#startOpened');}"); 
    }
}
