/**
 * This file is part of the DOM implementation for KDE.
 *
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2000 Simon Hausmann (hausmann@kde.org)
 *           (C) 2001 Dirk Mueller (mueller@kde.org)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
// -------------------------------------------------------------------------

#include "html/html_baseimpl.h"
#include "html/html_documentimpl.h"

#include "khtmlview.h"
#include "khtml_part.h"

#include "rendering/render_frames.h"
#include "rendering/render_html.h"
#include "rendering/render_body.h"
#include "css/cssstyleselector.h"
#include "css/css_stylesheetimpl.h"
#include "css/cssproperties.h"
#include "css/cssvalues.h"
#include "css/csshelper.h"
#include "misc/loader.h"
#include "misc/htmlhashes.h"
#include "dom/dom_string.h"
#include "dom/dom_doc.h"
#include "xml/dom2_eventsimpl.h"

#include <kurl.h>
#include <kdebug.h>

using namespace DOM;
using namespace khtml;

HTMLBodyElementImpl::HTMLBodyElementImpl(DocumentPtr *doc)
    : HTMLElementImpl(doc),
    m_bgSet( false ), m_fgSet( false )
{
    m_styleSheet = 0;
}

HTMLBodyElementImpl::~HTMLBodyElementImpl()
{
    if(m_styleSheet) m_styleSheet->deref();
}

NodeImpl::Id HTMLBodyElementImpl::id() const
{
    return ID_BODY;
}

void HTMLBodyElementImpl::parseAttribute(AttributeImpl *attr)
{
    switch(attr->id())
    {

    case ATTR_BACKGROUND:
    {
        QString url = khtml::parseURL( attr->value() ).string();
        if (!url.isEmpty()) {
            url = getDocument()->completeURL( url );
            addCSSProperty(CSS_PROP_BACKGROUND_IMAGE, "url('"+url+"')" );
            m_bgSet = true;
        }
        else
            m_bgSet = false;
        break;
    }
    case ATTR_MARGINWIDTH: {
	KHTMLView* w = getDocument()->view();
	w->setMarginWidth( -1 ); // unset this, so it doesn't override the setting here
        addCSSLength(CSS_PROP_MARGIN_RIGHT, attr->value() );
    }
        /* nobreak; */
    case ATTR_LEFTMARGIN:
        addCSSLength(CSS_PROP_MARGIN_LEFT, attr->value() );
        break;
    case ATTR_MARGINHEIGHT: {
	KHTMLView* w = getDocument()->view();
	w->setMarginHeight( -1 ); // unset this, so it doesn't override the setting here
        addCSSLength(CSS_PROP_MARGIN_BOTTOM, attr->value());
    }
        /* nobreak */
    case ATTR_TOPMARGIN:
        addCSSLength(CSS_PROP_MARGIN_TOP, attr->value());
        break;
    case ATTR_BGCOLOR:
        addCSSProperty(CSS_PROP_BACKGROUND_COLOR, attr->value());
        m_bgSet = !attr->value().isNull();
        break;
    case ATTR_TEXT:
        addCSSProperty(CSS_PROP_COLOR, attr->value());
        m_fgSet = !attr->value().isNull();
        break;
    case ATTR_BGPROPERTIES:
        if ( strcasecmp( attr->value(), "fixed" ) == 0)
            addCSSProperty(CSS_PROP_BACKGROUND_ATTACHMENT, CSS_VAL_FIXED);
        break;
    case ATTR_VLINK:
    case ATTR_ALINK:
    case ATTR_LINK:
    {
        if(!m_styleSheet) {
            m_styleSheet = new CSSStyleSheetImpl(this,DOMString(),true);
            m_styleSheet->ref();
        }
        QString aStr;
	if ( attr->id() == ATTR_LINK )
	    aStr = "a:link";
	else if ( attr->id() == ATTR_VLINK )
	    aStr = "a:visited";
	else if ( attr->id() == ATTR_ALINK )
	    aStr = "a:active";
	aStr += " { color: " + attr->value().string() + "; }";
        m_styleSheet->parseString(aStr);
        m_styleSheet->setNonCSSHints();
        break;
    }
    case ATTR_ONLOAD:
        getDocument()->setWindowEventListener(EventImpl::LOAD_EVENT,
	    getDocument()->createHTMLEventListener(attr->value().string()));
        break;
    case ATTR_ONUNLOAD:
        getDocument()->setWindowEventListener(EventImpl::UNLOAD_EVENT,
	    getDocument()->createHTMLEventListener(attr->value().string()));
        break;
    case ATTR_ONBLUR:
        getDocument()->setWindowEventListener(EventImpl::BLUR_EVENT,
	    getDocument()->createHTMLEventListener(attr->value().string()));
        break;
    case ATTR_ONFOCUS:
        getDocument()->setWindowEventListener(EventImpl::FOCUS_EVENT,
	    getDocument()->createHTMLEventListener(attr->value().string()));
        break;
    case ATTR_ONRESIZE:
        getDocument()->setWindowEventListener(EventImpl::RESIZE_EVENT,
	    getDocument()->createHTMLEventListener(attr->value().string()));
        break;
    case ATTR_NOSAVE:
	break;
    default:
        HTMLElementImpl::parseAttribute(attr);
    }
}

void HTMLBodyElementImpl::insertedIntoDocument()
{
    HTMLElementImpl::insertedIntoDocument();

    KHTMLView* w = getDocument()->view();
    if(w->marginWidth() != -1) {
        QString s;
        s.sprintf( "%d", w->marginWidth() );
        addCSSLength(CSS_PROP_MARGIN_LEFT, s);
        addCSSLength(CSS_PROP_MARGIN_RIGHT, s);
    }
    if(w->marginHeight() != -1) {
        QString s;
        s.sprintf( "%d", w->marginHeight() );
        addCSSLength(CSS_PROP_MARGIN_TOP, s);
        addCSSLength(CSS_PROP_MARGIN_BOTTOM, s);
    }

    if ( m_bgSet && !m_fgSet )
        addCSSProperty(CSS_PROP_COLOR, "#000000");

    if (m_styleSheet)
        getDocument()->updateStyleSelector();
}

void HTMLBodyElementImpl::removedFromDocument()
{
    HTMLElementImpl::removedFromDocument();

    if (m_styleSheet)
        getDocument()->updateStyleSelector();
}

void HTMLBodyElementImpl::attach()
{
    assert(!m_render);
    assert(parentNode());
    assert(parentNode()->renderer());

    RenderStyle* style = getDocument()->styleSelector()->styleForElement(this);
    style->ref();
    if (style->display() != NONE) {
        m_render = new RenderBody(this);
        m_render->setStyle(style);
        parentNode()->renderer()->addChild(m_render, nextRenderer());
    }
    style->deref();

    NodeBaseImpl::attach();
}

// -------------------------------------------------------------------------

HTMLFrameElementImpl::HTMLFrameElementImpl(DocumentPtr *doc)
    : HTMLElementImpl(doc)
{
    frameBorder = true;
    frameBorderSet = false;
    marginWidth = -1;
    marginHeight = -1;
    scrolling = QScrollView::Auto;
    noresize = false;
    url = "about:blank";
}

HTMLFrameElementImpl::~HTMLFrameElementImpl()
{
}

NodeImpl::Id HTMLFrameElementImpl::id() const
{
    return ID_FRAME;
}

void HTMLFrameElementImpl::parseAttribute(AttributeImpl *attr)
{
    switch(attr->id())
    {
    case ATTR_SRC:
        url = khtml::parseURL(attr->val());
        // FIXME_APPLE: load new url in the iframe
        setChanged();
        break;
    case ATTR_ID:
    case ATTR_NAME:
        // FIXME: if already attached, doesn't change the frame name
        // FIXME: frame name conflicts, no unique frame name anymore
        name = attr->value();
        break;
    case ATTR_FRAMEBORDER:
    {
        frameBorder = attr->value().toInt();
        frameBorderSet = ( attr->val() != 0 );
        // FIXME: when attached, has no effect
    }
    break;
    case ATTR_MARGINWIDTH:
        marginWidth = attr->val()->toInt();
        // FIXME: when attached, has no effect
        break;
    case ATTR_MARGINHEIGHT:
        marginHeight = attr->val()->toInt();
        // FIXME: when attached, has no effect
        break;
    case ATTR_NORESIZE:
        noresize = true;
        // FIXME: when attached, has no effect
        break;
    case ATTR_SCROLLING:
        if( strcasecmp( attr->value(), "auto" ) == 0 )
            scrolling = QScrollView::Auto;
        else if( strcasecmp( attr->value(), "yes" ) == 0 )
            scrolling = QScrollView::AlwaysOn;
        else if( strcasecmp( attr->value(), "no" ) == 0 )
            scrolling = QScrollView::AlwaysOff;
        // when attached, has no effect
        break;
    case ATTR_ONLOAD:
        static_cast<HTMLDocumentImpl*>( getDocument() )->body()
              ->setHTMLEventListener(EventImpl::LOAD_EVENT,
            getDocument()->createHTMLEventListener(attr->value().string()));
        break;
    case ATTR_ONUNLOAD:
        static_cast<HTMLDocumentImpl*>( getDocument() )->body()
              ->setHTMLEventListener(EventImpl::UNLOAD_EVENT,
            getDocument()->createHTMLEventListener(attr->value().string()));
        break;

    default:
        HTMLElementImpl::parseAttribute(attr);
    }
}

void HTMLFrameElementImpl::attach()
{
    assert(!attached());
    assert(parentNode());
    assert(parentNode()->renderer());

    // we should first look up via id, then via name.
    // this shortterm hack fixes the ugly case. ### rewrite needed for next release
    name = getAttribute(ATTR_NAME);
    if (name.isNull())
        name = getAttribute(ATTR_ID);

    // inherit default settings from parent frameset
    HTMLElementImpl* node = static_cast<HTMLElementImpl*>(parentNode());
    while(node)
    {
        if(node->id() == ID_FRAMESET)
        {
            HTMLFrameSetElementImpl* frameset = static_cast<HTMLFrameSetElementImpl*>(node);
            if(!frameBorderSet)  frameBorder = frameset->frameBorder();
            if(!noresize)  noresize = frameset->noResize();
            break;
        }
        node = static_cast<HTMLElementImpl*>(node->parentNode());
    }

    // ignore display: none for this element!
    KHTMLView* w = getDocument()->view();

    if (isURLAllowed(url.string()))  {
        m_render = new RenderFrame(this);
        m_render->setStyle(getDocument()->styleSelector()->styleForElement(this));
        parentNode()->renderer()->addChild(m_render, nextRenderer());
    }

    NodeBaseImpl::attach();

    if (!m_render)
        return;

    // we need a unique name for every frame in the frameset. Hope that's unique enough.
    if(name.isEmpty() || w->part()->frameExists( name.string() ) )
      name = DOMString(w->part()->requestFrameName());

    // load the frame contents
    w->part()->requestFrame( static_cast<RenderFrame*>(m_render), url.string(), name.string() );
}

void HTMLFrameElementImpl::setLocation( const DOMString& str )
{
    url = str;
    KHTMLView* w = getDocument()->view();
    if ( m_render && w && w->part() )
        // don't call this for an iframe
        w->part()->requestFrame( static_cast<khtml::RenderFrame *>(m_render), url.string(), name.string() );
}

bool HTMLFrameElementImpl::isSelectable() const
{
    return m_render!=0;
}

void HTMLFrameElementImpl::setFocus(bool received)
{
    HTMLElementImpl::setFocus(received);
    khtml::RenderFrame *renderFrame = static_cast<khtml::RenderFrame *>(m_render);
    if (!renderFrame || !renderFrame->widget())
	return;
    if (received)
	renderFrame->widget()->setFocus();
    else
	renderFrame->widget()->clearFocus();
}

DocumentImpl* HTMLFrameElementImpl::contentDocument() const
{
    if ( !m_render ) return 0;

    RenderPart* render = static_cast<RenderPart*>( m_render );

    if(render->widget() && render->widget()->inherits("KHTMLView"))
        return static_cast<KHTMLView*>( render->widget() )->part()->xmlDocImpl();

    return 0;
}

// -------------------------------------------------------------------------

HTMLFrameSetElementImpl::HTMLFrameSetElementImpl(DocumentPtr *doc)
    : HTMLElementImpl(doc)
{
    // default value for rows and cols...
    m_totalRows = 1;
    m_totalCols = 1;

    m_rows = m_cols = 0;

    frameborder = true;
    frameBorderSet = false;
    m_border = 4;
    noresize = false;

    m_resizing = false;
}

HTMLFrameSetElementImpl::~HTMLFrameSetElementImpl()
{
    if (m_rows)
        delete [] m_rows;
    if (m_cols)
        delete [] m_cols;
}

NodeImpl::Id HTMLFrameSetElementImpl::id() const
{
    return ID_FRAMESET;
}

void HTMLFrameSetElementImpl::parseAttribute(AttributeImpl *attr)
{
    switch(attr->id())
    {
    case ATTR_ROWS:
        if (!attr->val()) break;
        if (m_rows) delete [] m_rows;
        m_rows = attr->val()->toLengthArray(m_totalRows);
        setChanged();
    break;
    case ATTR_COLS:
        if (!attr->val()) break;
        delete [] m_cols;
        m_cols = attr->val()->toLengthArray(m_totalCols);
        setChanged();
    break;
    case ATTR_FRAMEBORDER:
        // false or "no" or "0"..
        if ( attr->value().toInt() == 0 ) {
            frameborder = false;
            m_border = 0;
        }
        frameBorderSet = true;
        break;
    case ATTR_NORESIZE:
        noresize = true;
        break;
    case ATTR_BORDER:
        m_border = attr->val()->toInt();
        if(!m_border)
            frameborder = false;
        break;
    case ATTR_ONLOAD:
        getDocument()->setHTMLEventListener(EventImpl::LOAD_EVENT,
	    getDocument()->createHTMLEventListener(attr->value().string()));
        break;
    case ATTR_ONUNLOAD:
        getDocument()->setHTMLEventListener(EventImpl::UNLOAD_EVENT,
	    getDocument()->createHTMLEventListener(attr->value().string()));
        break;
    default:
        HTMLElementImpl::parseAttribute(attr);
    }
}

void HTMLFrameSetElementImpl::attach()
{
    assert(!m_render);
    assert(parentNode());
    assert(parentNode()->renderer());

    // inherit default settings from parent frameset
    HTMLElementImpl* node = static_cast<HTMLElementImpl*>(parentNode());
    while(node)
    {
        if(node->id() == ID_FRAMESET)
        {
            HTMLFrameSetElementImpl* frameset = static_cast<HTMLFrameSetElementImpl*>(node);
            if(!frameBorderSet)  frameborder = frameset->frameBorder();
            if(!noresize)  noresize = frameset->noResize();
            break;
        }
        node = static_cast<HTMLElementImpl*>(node->parentNode());
    }

    // ignore display: none
    m_render = new RenderFrameSet(this);
    m_render->setStyle(getDocument()->styleSelector()->styleForElement(this));
    parentNode()->renderer()->addChild(m_render, nextRenderer());

    NodeBaseImpl::attach();
}

void HTMLFrameSetElementImpl::defaultEventHandler(EventImpl *evt)
{
    if (evt->isMouseEvent() && !noresize && m_render)
        static_cast<khtml::RenderFrameSet *>(m_render)->userResize(static_cast<MouseEventImpl*>(evt));

    evt->setDefaultHandled();
    HTMLElementImpl::defaultEventHandler(evt);
}

void HTMLFrameSetElementImpl::detach()
{
    if(attached())
        // ### send the event when we actually get removed from the doc instead of here
        getDocument()->dispatchHTMLEvent(EventImpl::UNLOAD_EVENT,false,false);

    HTMLElementImpl::detach();
}

void HTMLFrameSetElementImpl::recalcStyle( StyleChange ch )
{
    if (changed() && m_render) {
        m_render->setLayouted(false);
//         m_render->layout();
        setChanged(false);
    }
    HTMLElementImpl::recalcStyle( ch );
}


// -------------------------------------------------------------------------

NodeImpl::Id HTMLHeadElementImpl::id() const
{
    return ID_HEAD;
}

void HTMLHtmlElementImpl::attach()
{
    assert(!m_render);
    assert(parentNode());
    assert(parentNode()->renderer());

    m_render = new RenderHtml(this);
    m_render->setStyle(getDocument()->styleSelector()->styleForElement(this));
    parentNode()->renderer()->addChild(m_render, nextRenderer());

    NodeBaseImpl::attach();
}

// -------------------------------------------------------------------------

NodeImpl::Id HTMLHtmlElementImpl::id() const
{
    return ID_HTML;
}


// -------------------------------------------------------------------------

HTMLIFrameElementImpl::HTMLIFrameElementImpl(DocumentPtr *doc) : HTMLFrameElementImpl(doc)
{
    frameBorder = false;
    marginWidth = 0;
    marginHeight = 0;
    needWidgetUpdate = false;
}

HTMLIFrameElementImpl::~HTMLIFrameElementImpl()
{
}

NodeImpl::Id HTMLIFrameElementImpl::id() const
{
    return ID_IFRAME;
}

void HTMLIFrameElementImpl::parseAttribute(AttributeImpl *attr )
{
  switch (  attr->id() )
  {
    case ATTR_WIDTH:
      addCSSLength( CSS_PROP_WIDTH, attr->value());
      break;
    case ATTR_HEIGHT:
      addCSSLength( CSS_PROP_HEIGHT, attr->value() );
      break;
    case ATTR_SRC:
      needWidgetUpdate = true; // ### do this for scrolling, margins etc?
      HTMLFrameElementImpl::parseAttribute( attr );
      break;
    default:
      HTMLFrameElementImpl::parseAttribute( attr );
  }
}

void HTMLIFrameElementImpl::attach()
{
    assert(!attached());
    assert(!m_render);
    assert(parentNode());

    RenderStyle* _style = getDocument()->styleSelector()->styleForElement(this);
    _style->ref();
    if (isURLAllowed(url.string()) &&
        parentNode()->renderer() && _style->display() != NONE) {
        m_render = new RenderPartObject(this);
        m_render->setStyle(_style);
        parentNode()->renderer()->addChild(m_render, nextRenderer());
    }
    _style->deref();

    NodeBaseImpl::attach();

    if (m_render) {
        // we need a unique name for every frame in the frameset. Hope that's unique enough.
        KHTMLView* w = getDocument()->view();
        if(name.isEmpty() || w->part()->frameExists( name.string() ))
            name = DOMString(w->part()->requestFrameName());

        static_cast<RenderPartObject*>(m_render)->updateWidget();
        needWidgetUpdate = false;
    }
}

void HTMLIFrameElementImpl::recalcStyle( StyleChange ch )
{
    if (needWidgetUpdate) {
        if(m_render)  static_cast<RenderPartObject*>(m_render)->updateWidget();
        needWidgetUpdate = false;
    }
    HTMLElementImpl::recalcStyle( ch );
}

