// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fxcrt/css/cfx_csssyntaxparser.h"

#include <algorithm>

#include "core/fxcrt/css/cfx_cssdata.h"
#include "core/fxcrt/css/cfx_cssdeclaration.h"
#include "core/fxcrt/fx_codepage.h"
#include "core/fxcrt/fx_extension.h"
#include "core/fxcrt/fx_fallthrough.h"
#include "third_party/base/logging.h"

namespace {

bool IsSelectorStart(wchar_t wch) {
  return wch == '.' || wch == '#' || wch == '*' ||
         (isascii(wch) && isalpha(wch));
}

}  // namespace

CFX_CSSSyntaxParser::CFX_CSSSyntaxParser(const wchar_t* pBuffer,
                                         int32_t iBufferSize)
    : CFX_CSSSyntaxParser(pBuffer, iBufferSize, 32, false) {}

CFX_CSSSyntaxParser::CFX_CSSSyntaxParser(const wchar_t* pBuffer,
                                         int32_t iBufferSize,
                                         int32_t iTextDatSize,
                                         bool bOnlyDeclaration)
    : m_iTextDataLen(0),
      m_dwCheck(0xFFFFFFFF),
      m_eStatus(CFX_CSSSyntaxStatus::None) {
  ASSERT(pBuffer && iBufferSize > 0 && iTextDatSize > 0);
  m_eMode = bOnlyDeclaration ? CFX_CSSSyntaxMode::PropertyName
                             : CFX_CSSSyntaxMode::RuleSet;
  m_TextData.InitWithSize(iTextDatSize);
  m_TextPlane.AttachBuffer(pBuffer, iBufferSize);
}

CFX_CSSSyntaxParser::~CFX_CSSSyntaxParser() {}

CFX_CSSSyntaxStatus CFX_CSSSyntaxParser::DoSyntaxParse() {
  while (m_eStatus >= CFX_CSSSyntaxStatus::None) {
    if (m_TextPlane.IsEOF()) {
      if (m_eMode == CFX_CSSSyntaxMode::PropertyValue &&
          m_TextData.GetLength() > 0) {
        SaveTextData();
        m_eStatus = CFX_CSSSyntaxStatus::PropertyValue;
        return m_eStatus;
      }
      m_eStatus = CFX_CSSSyntaxStatus::EOS;
      return m_eStatus;
    }
    wchar_t wch;
    while (!m_TextPlane.IsEOF()) {
      wch = m_TextPlane.GetChar();
      switch (m_eMode) {
        case CFX_CSSSyntaxMode::RuleSet:
          switch (wch) {
            case '}':
              m_TextPlane.MoveNext();
              if (RestoreMode())
                return CFX_CSSSyntaxStatus::DeclClose;

              m_eStatus = CFX_CSSSyntaxStatus::Error;
              return m_eStatus;
            case '/':
              if (m_TextPlane.GetNextChar() == '*') {
                m_ModeStack.push(m_eMode);
                SwitchMode(CFX_CSSSyntaxMode::Comment);
                break;
              }
              FX_FALLTHROUGH;
            default:
              if (wch <= ' ') {
                m_TextPlane.MoveNext();
              } else if (IsSelectorStart(wch)) {
                SwitchMode(CFX_CSSSyntaxMode::Selector);
                return CFX_CSSSyntaxStatus::StyleRule;
              } else {
                m_eStatus = CFX_CSSSyntaxStatus::Error;
                return m_eStatus;
              }
              break;
          }
          break;
        case CFX_CSSSyntaxMode::Selector:
          switch (wch) {
            case ',':
              m_TextPlane.MoveNext();
              SwitchMode(CFX_CSSSyntaxMode::Selector);
              if (m_iTextDataLen > 0)
                return CFX_CSSSyntaxStatus::Selector;
              break;
            case '{':
              if (m_TextData.GetLength() > 0) {
                SaveTextData();
                return CFX_CSSSyntaxStatus::Selector;
              }
              m_TextPlane.MoveNext();
              m_ModeStack.push(CFX_CSSSyntaxMode::RuleSet);
              SwitchMode(CFX_CSSSyntaxMode::PropertyName);
              return CFX_CSSSyntaxStatus::DeclOpen;
            case '/':
              if (m_TextPlane.GetNextChar() == '*') {
                if (SwitchToComment() > 0)
                  return CFX_CSSSyntaxStatus::Selector;
                break;
              }
              FX_FALLTHROUGH;
            default:
              AppendChar(wch);
              break;
          }
          break;
        case CFX_CSSSyntaxMode::PropertyName:
          switch (wch) {
            case ':':
              m_TextPlane.MoveNext();
              SwitchMode(CFX_CSSSyntaxMode::PropertyValue);
              return CFX_CSSSyntaxStatus::PropertyName;
            case '}':
              m_TextPlane.MoveNext();
              if (RestoreMode())
                return CFX_CSSSyntaxStatus::DeclClose;

              m_eStatus = CFX_CSSSyntaxStatus::Error;
              return m_eStatus;
            case '/':
              if (m_TextPlane.GetNextChar() == '*') {
                if (SwitchToComment() > 0)
                  return CFX_CSSSyntaxStatus::PropertyName;
                break;
              }
              FX_FALLTHROUGH;
            default:
              AppendChar(wch);
              break;
          }
          break;
        case CFX_CSSSyntaxMode::PropertyValue:
          switch (wch) {
            case ';':
              m_TextPlane.MoveNext();
              FX_FALLTHROUGH;
            case '}':
              SwitchMode(CFX_CSSSyntaxMode::PropertyName);
              return CFX_CSSSyntaxStatus::PropertyValue;
            case '/':
              if (m_TextPlane.GetNextChar() == '*') {
                if (SwitchToComment() > 0)
                  return CFX_CSSSyntaxStatus::PropertyValue;
                break;
              }
              FX_FALLTHROUGH;
            default:
              AppendChar(wch);
              break;
          }
          break;
        case CFX_CSSSyntaxMode::Comment:
          if (wch == '/' && m_TextData.GetLength() > 0 &&
              m_TextData.GetBuffer()[m_TextData.GetLength() - 1] == '*') {
            RestoreMode();
          } else {
            m_TextData.AppendChar(wch);
          }
          m_TextPlane.MoveNext();
          break;
        case CFX_CSSSyntaxMode::UnknownRule:
          if (wch == ';')
            SwitchMode(CFX_CSSSyntaxMode::RuleSet);
          m_TextPlane.MoveNext();
          break;
        default:
          NOTREACHED();
          break;
      }
    }
  }
  return m_eStatus;
}

bool CFX_CSSSyntaxParser::IsImportEnabled() const {
  if ((m_dwCheck & CFX_CSSSYNTAXCHECK_AllowImport) == 0)
    return false;
  if (m_ModeStack.size() > 1)
    return false;
  return true;
}

bool CFX_CSSSyntaxParser::AppendChar(wchar_t wch) {
  m_TextPlane.MoveNext();
  if (m_TextData.GetLength() > 0 || wch > ' ') {
    m_TextData.AppendChar(wch);
    return true;
  }
  return false;
}

int32_t CFX_CSSSyntaxParser::SaveTextData() {
  m_iTextDataLen = m_TextData.TrimEnd();
  m_TextData.Clear();
  return m_iTextDataLen;
}

void CFX_CSSSyntaxParser::SwitchMode(CFX_CSSSyntaxMode eMode) {
  m_eMode = eMode;
  SaveTextData();
}

int32_t CFX_CSSSyntaxParser::SwitchToComment() {
  int32_t iLength = m_TextData.GetLength();
  m_ModeStack.push(m_eMode);
  SwitchMode(CFX_CSSSyntaxMode::Comment);
  return iLength;
}

bool CFX_CSSSyntaxParser::RestoreMode() {
  if (m_ModeStack.empty())
    return false;

  SwitchMode(m_ModeStack.top());
  m_ModeStack.pop();
  return true;
}

WideStringView CFX_CSSSyntaxParser::GetCurrentString() const {
  return WideStringView(m_TextData.GetBuffer(), m_iTextDataLen);
}