/* OpenRemote, the Home of the Digital Home.
* Copyright 2008-2014, OpenRemote Inc.
*
* See the contributors.txt file in the distribution for a
* full listing of individual contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openremote.web.console.util;

import org.openremote.web.console.client.WebConsole;
import org.openremote.web.console.event.ConsoleUnitEventManager;
import org.openremote.web.console.event.rotate.RotationEvent;
import org.openremote.web.console.event.ui.WindowResizeEvent;
import org.openremote.web.console.panel.entity.PanelSizeInfo;
import org.openremote.web.console.panel.entity.WelcomeFlag;
import org.openremote.web.console.service.AsyncControllerCallback;
import org.openremote.web.console.service.AutoBeanService;
import org.openremote.web.console.service.EnumDataMap;
import org.openremote.web.console.service.LocalDataServiceImpl;
import org.openremote.web.console.unit.ConsoleUnit;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.TouchMoveEvent;
import com.google.gwt.event.dom.client.TouchMoveHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.web.bindery.autobean.shared.AutoBean;

/**
 * 
 *  
 * @author <a href="mailto:[email protected]">Richard Turner</a>
 */
	public class BrowserUtils {
		public static boolean isMobile;
		public static boolean isApple;
		public static boolean isCssDodgy;
		public static boolean isIE;
		private static String windowOrientation = "portrait";
		private static int windowHeight;
		private static int windowWidth;
		private static HTML probeElement;
		private static final String LOADING_IMAGE_ID = "console_loading_image";
		private static final String LOADING_MSG_ID = "console_loading_msg";
		private static String userAgent = Window.Navigator.getUserAgent();
		static final String[] MOBILE_SPECIFIC_SUBSTRING = {
	      "iphone","android","midp","opera mobi",
	      "opera mini","blackberry","hp ipaq","iemobile",
	      "msiemobile","windows phone","htc","lg",
	      "mot","nokia","symbian","fennec",
	      "maemo","tear","armv",
	      "windows ce","windowsce","smartphone","240x320",
	      "176x220","320x320","160x160","webos",
	      "palm","sagem","samsung","sgh",
	      "sonyericsson","mmp","ucweb","ipod", "ipad"};
		
	   // Scroll Window to hide address bar
		private static Timer addressBarScroller = new Timer() {
			public void run() {
				int currentWinHeight = getWindowHeight();
				if (currentWinHeight > windowWidth && currentWinHeight > windowHeight) {
					Window.scrollTo(0, 1);
					this.schedule(3000);
				}
			}
		};
		
		static {
			isMobile = isMobile();
			isApple = isApple();
			isIE = isIE();
			HTML panel = new HTML();
			Element elem = panel.getElement();
			elem.getStyle().setVisibility(Visibility.HIDDEN);
			BrowserUtils.setStyleAttributeAllBrowsers(panel.getElement(), "boxSizing", "border-box");
			probeElement = panel;
		}
		
		public static int getWindowHeight() {
			if (isMobile) {
				return getNativeWindowDim("height");
			} else {
				return Window.getClientHeight();
			}
		}
		
		public static int getWindowWidth() {
			if (isMobile) {
				return getNativeWindowDim("width");
			} else {
				return Window.getClientWidth();
			}
		}
		
		public static String getWindowOrientation() {
			return windowOrientation;
		}
		
//		public static AbsolutePanel getConsoleContainer() {
//			return consoleContainer;
//		}

		public static void initWindow() {
//			consoleContainer = new AbsolutePanel();
//			consoleContainer.setWidth("3000px");
//			consoleContainer.setHeight("3000px");
//			RootPanel.get().add(consoleContainer, 0, 0);
//			//consoleContainer.getElement().getStyle().setPosition(Position.FIXED);
			updateWindowInfo();
			
			if (isMobile) {
				initMobile();
			} else {
				// Prevent scrollbars from being shown
				Window.enableScrolling(false);
			}
			
			// Add window resize handler
			Window.addResizeHandler(new ResizeHandler() {
				@Override
				public void onResize(ResizeEvent event) {
					doResizeAndRotate();
				}
			});
		}
		
		private static void doResizeAndRotate() {
			String oldOrientation = windowOrientation;
			int oldWidth = windowWidth;
			int oldHeight = windowHeight;
			
			updateWindowInfo();

			// Only continue if the console unit is initialised
			ConsoleUnit console = WebConsole.getConsoleUnit();
			if (console == null)
			{
				return;
			}
			
			if ((oldOrientation.equalsIgnoreCase(windowOrientation) && (oldWidth != windowWidth || oldHeight != windowHeight)) || (!oldOrientation.equalsIgnoreCase(windowOrientation) && (oldWidth != windowHeight || oldHeight != windowWidth))) {
				ConsoleUnitEventManager.getInstance().getEventBus().fireEvent(new WindowResizeEvent(getWindowWidth(), getWindowHeight()));
			}
			
			if (console.getIsFullscreen() && !windowOrientation.equalsIgnoreCase(oldOrientation)) {
				ConsoleUnitEventManager.getInstance().getEventBus().fireEvent(new RotationEvent(getWindowOrientation(), getWindowWidth(), getWindowHeight()));
			}
		}
		
		private static void initMobile() {
			// Prevent window scrolling
			RootPanel.get().addDomHandler(new TouchMoveHandler() {
				public void onTouchMove(TouchMoveEvent e) {
						e.preventDefault();
				}
			}, TouchMoveEvent.getType());
			
			Window.scrollTo(0, 1);
			new Timer() {
				public void run() {
					Window.scrollTo(0, 1);
				}
			}.schedule(1000);
			
			// Determine current window orientation
			if (getWindowHeight() < getWindowWidth()) {
				windowOrientation = "landscape";
			}
		   
			// Create a native orientation change handler as resize handler isn't reliable on iOS 3.x
			addNativeOrientationHandler();
			
		   // If Apple device then check if loaded from bookmark
			if (!isBookmarked()) {
				Window.alert("Please add this page to your Home Screen to view in fullscreen!");
			}
		}
		
		private static void onRotate() {
			doResizeAndRotate();
			addressBarScroller.schedule(3000);
		}

		public static void updateWindowInfo() {
			int winHeight = getWindowHeight();
			int winWidth = getWindowWidth();
			
			if (isMobile() && windowWidth == winWidth && windowHeight > winHeight) {
				// Ignore this as it just means the soft keyboard has been shown
				return;
			}
			
			String winOrientation = "portrait";
			
			if (winHeight < winWidth) {
				winOrientation = "landscape";
			}
			
			windowWidth = winWidth;
			windowHeight = winHeight;
			windowOrientation = winOrientation;
		}
		
// -------------------------------------------------------------
//		UTILITIES BELOW HERE
// -------------------------------------------------------------
		
		// Check useragent and touch screen support
		private static boolean isMobile() {
		  if (!supportsTouch()) return false;
		  
			for (String mobile: MOBILE_SPECIFIC_SUBSTRING) {
				if (userAgent.toLowerCase().contains(mobile)) {
					return true;
	         }
			}
	     return false;
		}
		
		private static boolean isIE() {
			if (userAgent.toLowerCase().contains("msie")) {
				return true;
			}
			return false;
		}
		
		private static boolean isApple() {
			if (userAgent.toLowerCase().contains("ipod") || userAgent.toLowerCase().contains("iphone") || userAgent.toLowerCase().contains("ipad")) {
				return true;
			}
			return false;
		}
		
		public static String getSystemImageDir() {
//			String dir = "http://";
//			dir = Window.Location.getHost() + Window.Location.getPath();
//			dir = dir.replaceFirst("/+[a-z|A-Z|0-9]+\\.html*", "");
//			dir = dir.replaceFirst("^http://", "");
//			dir += "resources/images";
			return "resources/images/";
		}
		
		public static void setStyleAttributeAllBrowsers(Element elem, String attr, String value) {
			String[] browsers = {"Webkit", "Moz", "O", "Ms", "Khtml"};
			DOM.setStyleAttribute(elem, attr, value);
			attr = Character.toUpperCase(attr.charAt(0)) + attr.substring(1);
			for (String browser : browsers) {
				DOM.setStyleAttribute(elem, browser + attr, value);
			}
		}
		
		public static void hideLoadingMsg() {
			RootPanel.get(LOADING_IMAGE_ID).setVisible(false);
			RootPanel.get(LOADING_MSG_ID).setVisible(false);
		}
		
		public static void showLoadingMsg() {
			showLoadingMsg("");
		}
		
		public static void showLoadingMsg(String msg) {
			RootPanel.get(LOADING_MSG_ID).getElement().setInnerHTML("<span>" + msg  + "</span>");
			RootPanel.get(LOADING_IMAGE_ID).setVisible(true);
			RootPanel.get(LOADING_MSG_ID).setVisible(true);
		}
		
		public static void hideAlert() {
			//WebConsole.getConsoleUnit().getConsoleDisplay().setVisible(true);
			DOM.getElementById("alert_popup").getStyle().setVisibility(Visibility.HIDDEN);
		}
		
		public static void showAlert(String msg) {
			ConsoleUnit console = WebConsole.getConsoleUnit();
			Element elem = DOM.getElementById("alert_popup");
			DOM.getElementById("alert_popup_msg").setInnerHTML(msg);
			int halfHeight = (int) Math.round((double)elem.getClientHeight()/2);
			int halfWidth = (int) Math.round((double)elem.getClientWidth()/2);
			elem.getStyle().setMarginTop(-halfHeight, Unit.PX);
			elem.getStyle().setMarginLeft(-halfWidth, Unit.PX);
			DOM.getElementById("alert_popup").getStyle().setVisibility(Visibility.VISIBLE);
		}
		
		public static void isURLSameOrigin_old(String url, final AsyncControllerCallback<Boolean> callback) {
			RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
			
	    try {
	      Request request = builder.sendRequest(null, new RequestCallback() {
	        public void onError(Request request, Throwable exception) {
	        	callback.onSuccess(true);
	        }

	        public void onResponseReceived(Request request, Response response) {
	        	if (response.getStatusCode() == 0) {
	        		// We get here for modern browsers that will allow CORS
	        		callback.onSuccess(false);
	        	} else {
	        		callback.onSuccess(true);
	        	}
	        }
	      });
	    } catch (RequestException e) {
    		// Violates SOP
    		callback.onSuccess(false);
	    }
		}
		
		public static void isURLSameOrigin(String url, final AsyncControllerCallback<Boolean> callback) {
			RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url + "rest/panels/");
			builder.setHeader("Accept", "application/json");
			builder.setTimeoutMillis(2000);
			
	    try {
	      Request request = builder.sendRequest(null, new RequestCallback() {
	        public void onError(Request request, Throwable exception) {
	        	callback.onSuccess(true);
	        }

	        public void onResponseReceived(Request request, Response response) {
	        	if (response.getStatusCode() == 0) {
	        		callback.onSuccess(false);
	        	} else {
	        		callback.onSuccess(true);
	        	}
	        }
	      });
	    } catch (RequestException e) {
    		// Violates SOP
    		callback.onSuccess(false);
	    }
		}
		
		public static String getImageProxyURL(String username, String password, String url) {
			String imageUrl = url;
		
			if (username != null && password != null) {
				String authStr = username + ":" + password;
				authStr = BrowserUtils.base64Encode(authStr);
				
				imageUrl =  GWT.getModuleBaseURL() + "imageproxy?userpass=" + authStr + "&url=" + URL.encode(url);
			}	
			
			return imageUrl;
		}
		
		/**
		 * History management - currently this just disables the back button
		 * need full history support for screen history.
		 */
		public static void setupHistory() {
      final String initToken = History.getToken();
      if (initToken.length() == 0) {
          History.newItem("main");
      }

      // Add history listener
      HandlerRegistration historyHandlerRegistration = History.addValueChangeHandler(new ValueChangeHandler<String>() {
          @Override
          public void onValueChange(ValueChangeEvent<String> event) {
              String token = event.getValue();
              if (initToken.equals(token)) {
                  History.newItem(initToken);
              }
          }
      });

      // Now that we've setup our listener, fire the initial history state.
      History.fireCurrentHistoryState();

      Window.addWindowClosingHandler(new ClosingHandler() {
          boolean reloading = false;

          @Override
          public void onWindowClosing(ClosingEvent event) {
              if (!reloading) {
                  String userAgent = Window.Navigator.getUserAgent();
                  if (userAgent.contains("MSIE")) {
                      if (!Window.confirm("Do you really want to exit?")) {
                          reloading = true;
                          Window.Location.reload(); // For IE
                      }
                  }
                  else {
                      event.setMessage("Web Console"); // For other browser
                  }
              }
          }
      });
		}
		
		public static int[] getSizeFromStyle(String style) {
			return getSizeFromStyle(style, false);
		}
		
		public static int[] getSizeFromStyle(String style, boolean useText) {
			if (!probeElement.isAttached()) {
				RootPanel.get().add(probeElement);
			}
			
			if (useText) {
				probeElement.setHTML("M");
			} else {
				probeElement.setHTML("");
			}
			
			int[] values = new int[4];
			probeElement.getElement().addClassName(style);
			values[0] = probeElement.getElement().getOffsetWidth();
			values[1] = probeElement.getElement().getOffsetHeight();
			values[2] = probeElement.getElement().getClientWidth();
			values[3] = probeElement.getElement().getClientHeight();
			probeElement.getElement().removeClassName(style);
			return values;
		}
		
		/**
		 * Determines if the welcome message should be shown by checking: -
		 *  URL parameter
		 *  Javascript variable
		 *  Local storage
		 * @return
		 */
		public static boolean showWelcomeMessage() {
		  boolean showWelcome = true;
		  
		  String param = Window.Location.getParameter("showWelcome");
		  
		  if (param == null || (!param.equalsIgnoreCase("true") && !param.equalsIgnoreCase("false"))) {
		    param = getShowWelcomeString();
		  }

      if (param != null && (param.equalsIgnoreCase("true") || param.equalsIgnoreCase("false"))) {
        showWelcome = Boolean.parseBoolean(param);
      }

      if (showWelcome) {
		    // Check local storage
		    int version = getBuildVersion();
	      
	      String welcomeObj = LocalDataServiceImpl.getInstance().getObjectString(EnumDataMap.WELCOME_FLAG.getDataName());
	      AutoBean<?> bean = AutoBeanService.getInstance().fromJsonString(EnumDataMap.WELCOME_FLAG.getClazz(), welcomeObj);
	      Integer welcomeVersion = -1;
	      WelcomeFlag welcomeFlag = null;
	      
	      if (bean != null) {
	        welcomeFlag = (WelcomeFlag)bean.as();
	        welcomeVersion = welcomeFlag.getWelcomeVersion() == null ? welcomeVersion : welcomeFlag.getWelcomeVersion();
	      }
	  
	      showWelcome = welcomeVersion < version;
		  }
		  
		  return showWelcome;
		}
		
		public static String getOrientation()
		{
		  String orientation = Window.Location.getParameter("orientation");
		  
      if (orientation == null || (!orientation.equalsIgnoreCase("portrait") && !orientation.equalsIgnoreCase("landscape"))) {
        orientation = BrowserUtils.getOrientationString();
      }
      
      if (orientation == null || (!orientation.equalsIgnoreCase("portrait") && !orientation.equalsIgnoreCase("landscape"))) {
        // Check local storage
        orientation = LocalDataServiceImpl.getInstance().getObjectString("orientation");
      }

      return orientation;
		}
		
    /**
     * Determines if the toolbar should be shown by checking: -
     *  URL parameter
     *  Javascript variable
     *  Local storage
     * @return
     */
    public static boolean showToolbar() {
      boolean showToolbar = true;
      
      // Check URL parameter
      String param = Window.Location.getParameter("showToolbar");
      
      if (param == null || (!param.equalsIgnoreCase("true") && !param.equalsIgnoreCase("false"))) {
        param = getShowToolbarString();
      }

      if (param != null && (param.equalsIgnoreCase("true") || param.equalsIgnoreCase("false"))) {
        showToolbar = Boolean.parseBoolean(param);
      }

      return showToolbar;
    }
    
    /**
     * Determines if the failsafe method of getting back to the settings screen should work by checking: -
     *  URL parameter
     *  Javascript variable
     *  Local storage
     * @return
     */
    public static boolean disableFailsafe() {
      boolean disableFailsafe = false;
      
      // Check URL parameter
      String param = Window.Location.getParameter("dsiableFailsafe");
      
      if (param == null || (!param.equalsIgnoreCase("true") && !param.equalsIgnoreCase("false"))) {
        param = getDisableFailsafeString();
      }

      if (param != null && (param.equalsIgnoreCase("true") || param.equalsIgnoreCase("false"))) {
        disableFailsafe = Boolean.parseBoolean(param);
      }

      return disableFailsafe;
    }
		
		public static PanelSizeInfo getDefaultPanelSize() {
      PanelSizeInfo sizeInfo = AutoBeanService.getInstance().getFactory().create(PanelSizeInfo.class).as();
      sizeInfo.setPanelSizeWidth(ConsoleUnit.DEFAULT_DISPLAY_WIDTH);
      sizeInfo.setPanelSizeHeight(ConsoleUnit.DEFAULT_DISPLAY_HEIGHT);
      sizeInfo.setPanelSizeType("fixed");
      
      // Check URL parameters
      String fullscreenStr = Window.Location.getParameter("fullscreen");
      String widthStr = Window.Location.getParameter("consoleWidth");
      String heightStr = Window.Location.getParameter("consoleHeight");
      
      if (fullscreenStr != null && !fullscreenStr.isEmpty() || (widthStr != null && heightStr != null && !widthStr.isEmpty() && !heightStr.isEmpty())) {
        String sizeType = Boolean.parseBoolean(fullscreenStr) ? "fullscreen" : "fixed";
        sizeInfo.setPanelSizeType(sizeType);
        
        if (widthStr != null && heightStr != null) {
          try {
            int width = Integer.parseInt(widthStr);
            int height = Integer.parseInt(heightStr);
            sizeInfo.setPanelSizeWidth(width);
            sizeInfo.setPanelSizeHeight(height);
          } catch (Exception e) {}
        }
        return sizeInfo;
      }
      
      // Check Javascript variables
      fullscreenStr = getFullscreenString();
      widthStr = getWidthString();
      heightStr = getHeightString();
      
      if (fullscreenStr != null && !fullscreenStr.isEmpty() || (widthStr != null && heightStr != null && !widthStr.isEmpty() && !heightStr.isEmpty())) {
        String sizeType = Boolean.parseBoolean(fullscreenStr) ? "fullscreen" : "fixed";
        sizeInfo.setPanelSizeType("fixed");
        
        if (widthStr != null && heightStr != null) {
          try {
            int width = Integer.parseInt(widthStr);
            int height = Integer.parseInt(heightStr);
            sizeInfo.setPanelSizeWidth(width);
            sizeInfo.setPanelSizeHeight(height);
          } catch (Exception e) {}
        }
        return sizeInfo;
      }
      
      // Check local storage
      String panelSizeInfo = LocalDataServiceImpl.getInstance().getObjectString("panelSizeInfo");
      if (panelSizeInfo != null && !panelSizeInfo.isEmpty()) {
        sizeInfo = AutoBeanService.getInstance().fromJsonString(PanelSizeInfo.class, panelSizeInfo).as();
      }
      
      return sizeInfo;
		}
		
		public static int getBuildVersion() {
      String versionStr = BrowserUtils.getBuildVersionString();
      int version = 0;
      
      try {
        version = Integer.parseInt(versionStr);
      } catch (Exception e) {}
      
      return version;
		}
		
// -------------------------------------------------------------
//			NATIVE METHODS BELOW HERE
// -------------------------------------------------------------		
		
		// Gets display density as an integer (1.0 = 10)
		public native static int getDisplayDensityValue() /*-{
			var displayDensity = 10;
			if (typeof $wnd.devicePixelRatio != 'undefined') {
				displayDensity = $wnd.devicePixelRatio * 10;
			}
			return (displayDensity < 13 ? 10 : displayDensity < 18 ? 15 : 20);
		}-*/;
		
		// Seem to have issue with getting height using GWT on ipod so resort to native JS
		public native static int getNativeWindowDim(String dim) /*-{
			if (typeof $wnd.innerWidth != 'undefined') {
				var height = $wnd.innerHeight;
				var width = $wnd.innerWidth;
			} else {
				var height = document.documentElement.clientHeight;
				var width = document.documentElement.clientWidth;
			}
			return (dim=="width" ? width : height);
		}-*/;
		
		public native static void addNativeOrientationHandler() /*-{
	   	if (typeof window.onorientationchange != 'undefined') {
		   	function eventHandler(e) {
					@org.openremote.web.console.util.BrowserUtils::onRotate()();
		   	}
		   	$wnd.addEventListener("orientationchange", eventHandler, false);
	   	}
		}-*/;
		
		public native static boolean isBookmarked() /*-{
			bookmarked = true;
    		if (("standalone" in window.navigator) && !window.navigator.standalone) {
				bookmarked = false;
    		}
    		return bookmarked;
		}-*/;
		
		public native static String randomUUID() /*-{
		  var s = [];
		  for (var i = 0; i <36; i++) s.push(Math.floor(Math.random()*10));
		  return s.join('');
		}-*/;
		
		public static native void exportStaticMethod() /*-{
			$wnd.hideAlert = $entry(@org.openremote.web.console.util.BrowserUtils::hideAlert());
		}-*/;
		
		public static native String base64Encode(String str) /*-{
			return $wnd.btoa(str);
		}-*/;
		
		public static native String getBuildVersionString() /*-{
		  return $wnd.buildVersionStr;
	  }-*/;

		public static native String getShowWelcomeString() /*-{
      return $wnd.showWelcome;
    }-*/;
    
    public static native String getShowToolbarString() /*-{
      return $wnd.showToolbar;
    }-*/;
    
    public static native String getDisableFailsafeString() /*-{
      return $wnd.disableFailsafe;
    }-*/;
  
    public static native String getFullscreenString() /*-{
      return $wnd.fullscreen;
    }-*/;
    
    public static native String getWidthString() /*-{
      return $wnd.consoleWidth;
    }-*/;
    
    public static native String getHeightString() /*-{
      return $wnd.consoleHeight;
    }-*/;
    
    public static native String getOrientationString() /*-{
      return $wnd.orientation;
    }-*/;
    
    public static native String getShowFrameString() /*-{
      return $wnd.showConsoleFrame;
    }-*/;
    
    public static native String getControllerUrlString() /*-{
      return $wnd.controllerURL;
    }-*/;

    public static native String getPanelNameString() /*-{
      return $wnd.panelName;
    }-*/;
		
    public static native boolean supportsTouch() /*-{
      var supportsTouch = false;
      if ('ontouchstart' in window) {
          //iOS & android
          supportsTouch = true;
      } else if(window.navigator.msPointerEnabled) {
          //Win8
          supportsTouch = true;
      }
      return supportsTouch;
  }-*/;
}