/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Spatial Navigation
 *
 * The Initial Developer of the Original Code is 
 * Douglas F. Turner II  <dougt@meer.net>
 * Portions created by the Initial Developer are Copyright (C) 2004-2005
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsSpatialNavigationPrivate.h"

#ifdef DEBUG_outputframes
#include "nsIFrameDebug.h"
#endif

static PRBool is_space(char c)
{
  return (c == ' '  || c == '\f' || c == '\n' ||
          c == '\r' || c == '\t' || c == '\v');
}

nscoord* lo_parse_coord_list(char *str, PRInt32* value_cnt)
{
  char *tptr;
  char *n_str;
  PRInt32 i, cnt;
  PRInt32 *value_list;
  
  /*
   * Nothing in an empty list
   */
  *value_cnt = 0;
  if (!str || *str == '\0')
  {
    return nsnull;
  }
  
  /*
   * Skip beginning whitespace, all whitespace is empty list.
   */
  n_str = str;
  while (is_space(*n_str))
  {
    n_str++;
  }
  if (*n_str == '\0')
  {
    return nsnull;
  }
  
  /*
   * Make a pass where any two numbers separated by just whitespace
   * are given a comma separator.  Count entries while passing.
   */
  cnt = 0;
  while (*n_str != '\0')
  {
    PRBool has_comma;
    
    /*
     * Skip to a separator
     */
    tptr = n_str;
    while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0')
    {
      tptr++;
    }
    n_str = tptr;
    
    /*
     * If no more entries, break out here
     */
    if (*n_str == '\0')
    {
      break;
    }
    
    /*
     * Skip to the end of the separator, noting if we have a
     * comma.
     */
    has_comma = PR_FALSE;
    while (is_space(*tptr) || *tptr == ',')
    {
      if (*tptr == ',')
      {
        if (has_comma == PR_FALSE)
        {
          has_comma = PR_TRUE;
        }
        else
        {
          break;
        }
      }
      tptr++;
    }
    /*
     * If this was trailing whitespace we skipped, we are done.
     */
    if ((*tptr == '\0')&&(has_comma == PR_FALSE))
    {
      break;
    }
    /*
     * Else if the separator is all whitespace, and this is not the
     * end of the string, add a comma to the separator.
     */
    else if (has_comma == PR_FALSE)
    {
      *n_str = ',';
    }
    
    /*
     * count the entry skipped.
     */
    cnt++;
    
    n_str = tptr;
  }
  /*
   * count the last entry in the list.
   */
  cnt++;
  
  /*
   * Allocate space for the coordinate array.
   */
  value_list = new nscoord[cnt];
  if (!value_list)
  {
    return nsnull;
  }
  
  /*
   * Second pass to copy integer values into list.
   */
  tptr = str;
  for (i=0; i<cnt; i++)
  {
    char *ptr;
    
    ptr = strchr(tptr, ',');
    if (ptr)
    {
      *ptr = '\0';
    }
    /*
     * Strip whitespace in front of number because I don't
     * trust atoi to do it on all platforms.
     */
    while (is_space(*tptr))
    {
      tptr++;
    }
    if (*tptr == '\0')
    {
      value_list[i] = 0;
    }
    else
    {
      value_list[i] = (nscoord) ::atoi(tptr);
    }
    if (ptr)
    {
      *ptr = ',';
      tptr = ptr + 1;
    }
  }
  
  *value_cnt = cnt;
  return value_list;
}

nsresult createFrameTraversal(nsPresContext* aPresContext,
                              PRInt32 aType,
                              PRBool aVisual,
                              PRBool aLockInScrollView,
                              PRBool aFollowOOFs,
                              nsIBidirectionalEnumerator** outTraversal)
{
  nsresult result;
  if (!aPresContext)
    return NS_ERROR_FAILURE;    
  
  nsIPresShell* presShell = aPresContext->PresShell();
  if (!presShell)
    return NS_ERROR_FAILURE;
  
  nsIFrame* frame = presShell->GetRootFrame();
  
  if (!frame)
    return NS_ERROR_FAILURE;

#ifdef DEBUG_outputframes
  nsIFrameDebug* fd;
  frame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**) &fd);
  if (fd)
    fd->List(frame->GetPresContext(), stdout, 0);
#endif

  nsCOMPtr<nsIBidirectionalEnumerator> frameTraversal;
  
  static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
  nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result));
  if (NS_FAILED(result))
    return result;
  
  result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
                                   aPresContext, frame,
                                   aType, aVisual, aLockInScrollView, aFollowOOFs);
  if (NS_FAILED(result))
    return result;
  
  NS_IF_ADDREF(*outTraversal = frameTraversal);
  return NS_OK;
}

nsresult getEventTargetFromWindow(nsIDOMWindow* aWindow, nsIDOM3EventTarget** aEventTarget, nsIDOMEventGroup** aSystemGroup)
{
  *aEventTarget = nsnull;
  nsCOMPtr<nsPIDOMWindow> privateWindow = do_QueryInterface(aWindow);
  
  if (!privateWindow)
    return NS_ERROR_UNEXPECTED; // assert
  
  nsPIDOMEventTarget *chromeEventHandler = privateWindow->GetChromeEventHandler();

  if (!chromeEventHandler)
    return NS_ERROR_UNEXPECTED; // assert
  
  nsCOMPtr<nsIDOMEventGroup> systemGroup;
  chromeEventHandler->GetSystemEventGroup(getter_AddRefs(systemGroup));
  nsCOMPtr<nsIDOM3EventTarget> target(do_QueryInterface(chromeEventHandler));
  
  if (!target || !systemGroup)
    return NS_ERROR_FAILURE;
  
  NS_ADDREF(*aEventTarget = target);
  NS_ADDREF(*aSystemGroup = systemGroup);
  return NS_OK;
}

void getContentFromFrame(nsIContent* c, nsIContent** outContent)
{
  *outContent = nsnull;

  nsCOMPtr<nsIContent> result;
  nsCOMPtr<nsIDOMDocument> contentDocument;

  nsCOMPtr<nsIDOMHTMLFrameElement> domFrameElement = do_QueryInterface(c);

  if (domFrameElement) {
    domFrameElement->GetContentDocument(getter_AddRefs(contentDocument));
  }  
  else {
    nsCOMPtr<nsIDOMHTMLIFrameElement> domIFrameElement = do_QueryInterface(c);
	if (domIFrameElement)
      domIFrameElement->GetContentDocument(getter_AddRefs(contentDocument));
  }

  if (contentDocument) {
    nsCOMPtr<nsIDOMElement> documentElement;
    contentDocument->GetDocumentElement(getter_AddRefs(documentElement));
    result = do_QueryInterface(documentElement);
  }
  NS_IF_ADDREF(*outContent = result);
}

nsresult getFrameForContent(nsIContent* aContent, nsIFrame** aFrame)
{
  *aFrame = nsnull;

  if (!aContent)
    return NS_ERROR_FAILURE;
  
  nsIDocument* doc = aContent->GetDocument();
  if (!doc)
    return NS_ERROR_FAILURE;
  
  nsIPresShell *presShell = doc->GetPrimaryShell();
  nsIFrame* frame = presShell->GetPrimaryFrameFor(aContent);
  
  if (!frame)
    return NS_ERROR_FAILURE;
  
  *aFrame = frame;
  return NS_OK;
}

PRBool
isContentOfType(nsIContent* content, const char* type)
{
  if (!content)
	return PR_FALSE;

  if (content->IsNodeOfType(nsINode::eELEMENT))
  {
    nsIAtom* atom =  content->NodeInfo()->NameAtom();
    if (atom)
      return atom->EqualsUTF8(nsDependentCString(type));
  }
  return PR_FALSE;
}

PRBool
isArea(nsIContent* content)
{
  if (!content || !content->IsNodeOfType(nsINode::eHTML))
      return PR_FALSE;

  return isContentOfType(content, "area");
}

PRBool
isMap(nsIFrame* frame)
{
  nsIContent* content = frame->GetContent();

  if (!content || !content->IsNodeOfType(nsINode::eHTML))
      return PR_FALSE;

  return isContentOfType(content, "map");
}

PRBool 
isTargetable(PRBool focusDocuments, nsIFrame* frame)
{
  nsIContent* currentContent = frame->GetContent();

  if (!currentContent)
    return PR_FALSE;

  if (!currentContent->IsNodeOfType(nsINode::eHTML))
      return PR_FALSE;

  if (isContentOfType(currentContent, "map"))
    return PR_TRUE;

  if (isContentOfType(currentContent, "button"))
    return PR_TRUE;

  if (isContentOfType(currentContent, "a"))
  {
    // an anchor isn't targetable unless it has a non-null href.
    nsCOMPtr<nsIDOMHTMLAnchorElement> anchorElement = do_QueryInterface(currentContent);
    nsAutoString uri;
    anchorElement->GetHref(uri);
    if (uri.IsEmpty()) {
      return PR_FALSE; 
    }
    return PR_TRUE;
  }

  nsCOMPtr<nsIFrameFrame> frameFrame(do_QueryInterface(frame));
  if (frameFrame) 
    return PR_TRUE;

  nsCOMPtr<nsIDOMHTMLIFrameElement> iFrameElement = do_QueryInterface(currentContent);
  if (iFrameElement) 
    return PR_TRUE;

  if (focusDocuments) {
    nsCOMPtr<nsIDOMHTMLHtmlElement> hhElement(do_QueryInterface(currentContent));
    if (hhElement) 
      return PR_TRUE;
  }

  // need to figure out how to determine if a element is
  // either disabled, hidden, or it is inaccessible due to
  // its parent being one of these.

  PRBool disabled = PR_FALSE;
  
  nsCOMPtr<nsIDOMHTMLSelectElement> selectElement = do_QueryInterface(currentContent);
  if (selectElement && NS_SUCCEEDED(selectElement->GetDisabled(&disabled)))
    return !disabled;

  nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = do_QueryInterface(currentContent);
  if (optionElement && NS_SUCCEEDED(optionElement->GetDisabled(&disabled)))
    return !disabled;

  nsAutoString inputType;
  nsCOMPtr<nsIDOMHTMLInputElement> inputElement = do_QueryInterface(currentContent);
  if (inputElement && NS_SUCCEEDED(inputElement->GetDisabled(&disabled)) && NS_SUCCEEDED(inputElement->GetType(inputType)))
    return !disabled && (! inputType.EqualsIgnoreCase("hidden"));

  return PR_FALSE;
}

nsRect makeRectRelativeToGlobalView(nsIFrame *aFrame)
{
  nsRect result;
  result.SetRect(0,0,0,0);

  nsPoint offset;

  if (!aFrame)
    return result;

  result = aFrame->GetRect();

  nsIView* view;
  aFrame->GetOffsetFromView(offset, &view);
  
  nsIView* rootView = nsnull;
  if (view) {
    nsIViewManager* viewManager = view->GetViewManager();
    NS_ASSERTION(viewManager, "View must have a viewmanager");
    viewManager->GetRootView(rootView);
  }
  while (view) {
    offset  += view->GetPosition();
    if (view == rootView) {
      break;
    }
    view = view->GetParent();
  }
  
  result.MoveTo(offset);
  return result;
}


void poly2Rect(int sides, nscoord* coord, nsRect* rect)
{
  nscoord x1, x2, y1, y2, xtmp, ytmp;
  x1 = x2 = coord[0];
  y1 = y2 = coord[1];
  for (PRInt32 i = 2; i < sides; i += 2) 
  {
    xtmp = coord[i];
    ytmp = coord[i+1];
    x1 = x1 < xtmp ? x1 : xtmp;
    y1 = y1 < ytmp ? y1 : ytmp;
    x2 = x2 > xtmp ? x2 : xtmp;
    y2 = y2 > ytmp ? y2 : ytmp;
  }
  rect->SetRect(x1, y1, x2, y2);
}

void getRectOfAreaElement(nsIFrame* f, nsIDOMHTMLAreaElement* e, nsRect* r)
{
  if (!f || !e || !r)
    return;

  // RECT !!
  nsRect frameRect = makeRectRelativeToGlobalView(f);
  
  if (frameRect.width == 0)
    frameRect.width = 1;
  if (frameRect.height == 0)
    frameRect.height = 1;
  
  nsPoint offset;
  offset.x = frameRect.x;
  offset.y = frameRect.y;
  

  nsAutoString coordstr;
  e->GetCoords(coordstr);

  NS_ConvertUTF16toUTF8 cp (coordstr);
  
  PRInt32 count;
  nscoord* coords = lo_parse_coord_list((char*) cp.get(), &count);
  
  // FIX what about other shapes?
  
  if (count == 4) 
  {
    //                x          y          width                  height
    frameRect.SetRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]);
    
    frameRect.x += offset.x;
    frameRect.y += offset.y;
  }
  else if (count >=6)
    poly2Rect(count, coords, &frameRect);
#ifdef DEBUG_dougt
  else
    printf("area count not supported!! %d\n", count);
#endif

  // find the center of the rect.

  frameRect.x = frameRect.x + (frameRect.width / 2) - 1;
  frameRect.width = 2;
  frameRect.y = frameRect.y + (frameRect.height / 2) - 1;
  frameRect.height = 2;

  *r = frameRect;
}

// we want to determine if going in the |direction|
// direction from focusedRect will intercept the frameRect

PRBool isRectInDirection(int direction, nsRect& focusedRect, nsRect& frameRect)
{
  if (direction == eNavLeft)
  {
    return (frameRect.x < focusedRect.x);
  }
  
  if (direction == eNavRight)
  {
    return (frameRect.x + frameRect.width > focusedRect.x + focusedRect.width);
  }
  
  if (direction == eNavUp)
  {
    return (frameRect.y < focusedRect.y);
  }
  
  if (direction == eNavDown)
  {
    return (frameRect.y + frameRect.height > focusedRect.y + focusedRect.height);
  }
  return PR_FALSE;
}

PRInt64 spatialDistance(int direction, nsRect& a, nsRect& b)
{
  PRBool inlineNavigation = PR_FALSE;
  nsPoint m, n;
  
  if (direction == eNavLeft)
  {
    //  |---|
    //  |---|
    //
    //  |---|  |---|
    //  |---|  |---|
    //
    //  |---|
    //  |---|
    //
    
    if (a.y > b.y + b.height) 
    {
      // the b rect is above a.
      m.x = a.x;
      m.y = a.y;
      n.x = b.x + b.width;
      n.y = b.y + b.height;
    }
    else if (a.y + a.height < b.y)
    {
      // the b rect is below a.
      m.x = a.x;
      m.y = a.y + a.height;
      n.x = b.x + b.width;
      n.y = b.y;       
    }
    else
    {
      m.x = a.x;
      m.y = 0;
      n.x = b.x + b.width; 
      n.y = 0;

      //      m.x = (a.x + (a.width / 2));
      //      m.y = (a.y + (a.height / 2));
      //      n.x = (b.x + (b.width / 2));
      //      n.y = (b.y + (b.height / 2));

    }
  }
  else if (direction == eNavRight)
  {
    
    //         |---|
    //         |---|
    //
    //  |---|  |---|
    //  |---|  |---|
    //
    //         |---|
    //         |---|
    //
    
    if (a.y > b.y + b.height) 
    {
      // the b rect is above a.
      m.x = a.x + a.width;
      m.y = a.y;
      n.x = b.x;
      n.y = b.y + b.height;
    }
    else if (a.y + a.height < b.y)
    {
      // the b rect is below a.
      m.x = a.x + a.width;
      m.y = a.y + a.height;
      n.x = b.x;
      n.y = b.y;       
    }
    else
    {
      m.x = a.x + a.width;
      m.y = 0;
      n.x = b.x; 
      n.y = 0;
      
      //      m.x = (a.x + (a.width / 2));
      //      m.y = (a.y + (a.height / 2));
      //      n.x = (b.x + (b.width / 2));
      //      n.y = (b.y + (b.height / 2));
    }
  }
  else if (direction == eNavUp)
  {
    
    //  |---|  |---|  |---|
    //  |---|  |---|  |---|
    //
    //         |---|
    //         |---|
    //
    
    if (a.x > b.x + b.width) 
    {
      // the b rect is to the left of a.
      m.x = a.x;
      m.y = a.y;
      n.x = b.x + b.width;
      n.y = b.y + b.height;
    }
    else if (a.x + a.width < b.x)
    {
      // the b rect is to the right of a
      m.x = a.x + a.width;
      m.y = a.y;
      n.x = b.x;
      n.y = b.y + b.height;       
    }
    else
    {
      // both b and a share some common x's.
      m.x = 0;
      m.y = a.y;
      n.x = 0;
      n.y = b.y + b.height;

      //      m.x = (a.x + (a.width / 2));
      //      m.y = (a.y + (a.height / 2));     
      //      n.x = (b.x + (b.width / 2));
      //      n.y = (b.y + (b.height / 2));
    }
  }
  else if (direction == eNavDown)
  {
    //         |---|
    //         |---|
    //
    //  |---|  |---|  |---|
    //  |---|  |---|  |---|
    //
    
    if (a.x > b.x + b.width) 
    {
      // the b rect is to the left of a.
      m.x = a.x;
      m.y = a.y + a.height;
      n.x = b.x + b.width;
      n.y = b.y;
    }
    else if (a.x + a.width < b.x)
    {
      // the b rect is to the right of a
      m.x = a.x + a.width;
      m.y = a.y + a.height;
      n.x = b.x;
      n.y = b.y;       
    }
    else
    {
      // both b and a share some common x's.
      m.x = 0;
      m.y = a.y + a.height;
      n.x = 0;
      n.y = b.y;

      //      m.x = (a.x + (a.width / 2));
      //      m.y = (a.y + (a.height / 2));     
      //      n.x = (b.x + (b.width / 2));
      //      n.y = (b.y + (b.height / 2));
    }
  }
  
  // a is always the currently focused rect.
  
  nsRect scopedRect = a;
  scopedRect.Inflate(gRectFudge, gRectFudge);
  
  if (direction == eNavLeft)
  {
    scopedRect.x = 0;
    scopedRect.width = nscoord_MAX;
    if (scopedRect.Intersects(b))
      inlineNavigation = PR_TRUE;
  }
  else if (direction == eNavRight)
  {
    scopedRect.width = nscoord_MAX;
    if (scopedRect.Intersects(b))
      inlineNavigation = PR_TRUE;
  }
  else if (direction == eNavUp)
  {
    scopedRect.y = 0;
    scopedRect.height = nscoord_MAX;
    if (scopedRect.Intersects(b))
      inlineNavigation = PR_TRUE;
  }
  else if (direction == eNavDown)
  {
    scopedRect.height = nscoord_MAX;
    if (scopedRect.Intersects(b))
      inlineNavigation = PR_TRUE;
  }
  
  PRInt64 d = ((m.x-n.x)*(m.x-n.x)) + ((m.y-n.y)*(m.y-n.y));
  
  if(d<0)
    d=d*(-1);
  
  if (inlineNavigation)
	d /= gDirectionalBias;
  
  return d;
}

void GetWindowFromDocument(nsIDOMDocument* aDocument, nsIDOMWindowInternal** aWindow)
{
  nsCOMPtr<nsIDOMDocumentView> docview = do_QueryInterface(aDocument);

  nsCOMPtr<nsIDOMAbstractView> view;
  docview->GetDefaultView(getter_AddRefs(view));
  if (!view) return;
  
  nsCOMPtr<nsIDOMWindowInternal> window = do_QueryInterface(view);
  NS_IF_ADDREF(*aWindow = window);
}



PRBool IsPartiallyVisible(nsIPresShell* shell, nsIFrame* frame) 
{
   // We need to know if at least a kMinPixels around the object is visible
   // Otherwise it will be marked STATE_OFFSCREEN and STATE_INVISIBLE
   
   const PRUint16 kMinPixels  = 12;
    // Set up the variables we need, return false if we can't get at them all
 
   nsIViewManager* viewManager = shell->GetViewManager();
   if (!viewManager)
     return PR_FALSE;
 
 
   // If visibility:hidden or visibility:collapsed then mark with STATE_INVISIBLE
   if (!frame->GetStyleVisibility()->IsVisible())
   {
       return PR_FALSE;
   }
 
   nsPresContext *presContext = shell->GetPresContext();
   if (!presContext)
     return PR_FALSE;
 
   // Get the bounds of the current frame, relative to the current view.
   // We don't use the more accurate GetBoundsRect, because that is more expensive 
   // and the STATE_OFFSCREEN flag that this is used for only needs to be a rough indicator
 
   nsRect relFrameRect = frame->GetRect();
   nsPoint frameOffset;
   nsIView *containingView = frame->GetViewExternal();
   if (!containingView) {
     frame->GetOffsetFromView(frameOffset, &containingView);
     if (!containingView)
       return PR_FALSE;  // no view -- not visible
     relFrameRect.x = frameOffset.x;
     relFrameRect.y = frameOffset.y;
   }
 
   PRUint16 minPixels = PRUint16(nsPresContext::CSSPixelsToAppUnits(kMinPixels));
   nsRectVisibility rectVisibility;
   viewManager->GetRectVisibility(containingView, relFrameRect, 
                                  minPixels, 
                                  &rectVisibility);
 
   if (rectVisibility == nsRectVisibility_kVisible ||
       (rectVisibility == nsRectVisibility_kZeroAreaRect && frame->GetNextInFlow())) {
     // This view says it is visible, but we need to check the parent view chain :(
     // Note: zero area rects can occur in the first frame of a multi-frame text flow,
     //       in which case the next frame exists because the text flow is visible
     while ((containingView = containingView->GetParent()) != nsnull) {
       if (containingView->GetVisibility() == nsViewVisibility_kHide) {
         return PR_FALSE;
       }
     }
     return PR_TRUE;
   }
 
   return PR_FALSE;
 }


const static PRInt32 gScrollOffset = (26*3);


void ScrollWindow(int direction, nsIDOMWindow* contentWindow)
{
  if (!contentWindow)
    return;
  
  if (direction == eNavLeft)
    contentWindow->ScrollBy(-1* gScrollOffset, 0);
  else if (direction == eNavRight)
    contentWindow->ScrollBy(gScrollOffset, 0);
  else if (direction == eNavUp)
    contentWindow->ScrollBy(0, -1 * gScrollOffset);
  else
    contentWindow->ScrollBy(0, gScrollOffset);
}
