// 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 "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 "third_party/base/compiler_specific.h"
#include "third_party/base/notreached.h"

namespace {

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

}  // namespace

CFX_CSSSyntaxParser::CFX_CSSSyntaxParser(WideStringView str) : m_Input(str) {}

CFX_CSSSyntaxParser::~CFX_CSSSyntaxParser() = default;

void CFX_CSSSyntaxParser::SetParseOnlyDeclarations() {
  m_eMode = SyntaxMode::kPropertyName;
}

CFX_CSSSyntaxStatus CFX_CSSSyntaxParser::DoSyntaxParse() {
  m_Output.Clear();
  if (m_bHasError)
    return CFX_CSSSyntaxStatus::kError;

  while (!m_Input.IsEOF()) {
    wchar_t wch = m_Input.GetChar();
    switch (m_eMode) {
      case SyntaxMode::kRuleSet:
        switch (wch) {
          case '}':
            m_bHasError = true;
            return CFX_CSSSyntaxStatus::kError;
          case '/':
            if (m_Input.GetNextChar() == '*') {
              SaveMode(SyntaxMode::kRuleSet);
              m_eMode = SyntaxMode::kComment;
              break;
            }
            FALLTHROUGH;
          default:
            if (wch <= ' ') {
              m_Input.MoveNext();
            } else if (IsSelectorStart(wch)) {
              m_eMode = SyntaxMode::kSelector;
              return CFX_CSSSyntaxStatus::kStyleRule;
            } else {
              m_bHasError = true;
              return CFX_CSSSyntaxStatus::kError;
            }
            break;
        }
        break;
      case SyntaxMode::kSelector:
        switch (wch) {
          case ',':
            m_Input.MoveNext();
            if (!m_Output.IsEmpty())
              return CFX_CSSSyntaxStatus::kSelector;
            break;
          case '{':
            if (!m_Output.IsEmpty())
              return CFX_CSSSyntaxStatus::kSelector;
            m_Input.MoveNext();
            SaveMode(SyntaxMode::kRuleSet);  // Back to validate ruleset again.
            m_eMode = SyntaxMode::kPropertyName;
            return CFX_CSSSyntaxStatus::kDeclOpen;
          case '/':
            if (m_Input.GetNextChar() == '*') {
              SaveMode(SyntaxMode::kSelector);
              m_eMode = SyntaxMode::kComment;
              if (!m_Output.IsEmpty())
                return CFX_CSSSyntaxStatus::kSelector;
              break;
            }
            FALLTHROUGH;
          default:
            m_Output.AppendCharIfNotLeadingBlank(wch);
            m_Input.MoveNext();
            break;
        }
        break;
      case SyntaxMode::kPropertyName:
        switch (wch) {
          case ':':
            m_Input.MoveNext();
            m_eMode = SyntaxMode::kPropertyValue;
            return CFX_CSSSyntaxStatus::kPropertyName;
          case '}':
            m_Input.MoveNext();
            if (!RestoreMode())
              return CFX_CSSSyntaxStatus::kError;

            return CFX_CSSSyntaxStatus::kDeclClose;
          case '/':
            if (m_Input.GetNextChar() == '*') {
              SaveMode(SyntaxMode::kPropertyName);
              m_eMode = SyntaxMode::kComment;
              if (!m_Output.IsEmpty())
                return CFX_CSSSyntaxStatus::kPropertyName;
              break;
            }
            FALLTHROUGH;
          default:
            m_Output.AppendCharIfNotLeadingBlank(wch);
            m_Input.MoveNext();
            break;
        }
        break;
      case SyntaxMode::kPropertyValue:
        switch (wch) {
          case ';':
            m_Input.MoveNext();
            FALLTHROUGH;
          case '}':
            m_eMode = SyntaxMode::kPropertyName;
            return CFX_CSSSyntaxStatus::kPropertyValue;
          case '/':
            if (m_Input.GetNextChar() == '*') {
              SaveMode(SyntaxMode::kPropertyValue);
              m_eMode = SyntaxMode::kComment;
              if (!m_Output.IsEmpty())
                return CFX_CSSSyntaxStatus::kPropertyValue;
              break;
            }
            FALLTHROUGH;
          default:
            m_Output.AppendCharIfNotLeadingBlank(wch);
            m_Input.MoveNext();
            break;
        }
        break;
      case SyntaxMode::kComment:
        if (wch == '*' && m_Input.GetNextChar() == '/') {
          if (!RestoreMode())
            return CFX_CSSSyntaxStatus::kError;
          m_Input.MoveNext();
        }
        m_Input.MoveNext();
        break;
      default:
        NOTREACHED();
        break;
    }
  }
  if (m_eMode == SyntaxMode::kPropertyValue && !m_Output.IsEmpty())
    return CFX_CSSSyntaxStatus::kPropertyValue;

  return CFX_CSSSyntaxStatus::kEOS;
}

void CFX_CSSSyntaxParser::SaveMode(SyntaxMode mode) {
  m_ModeStack.push(mode);
}

bool CFX_CSSSyntaxParser::RestoreMode() {
  if (m_ModeStack.empty()) {
    m_bHasError = true;
    return false;
  }
  m_eMode = m_ModeStack.top();
  m_ModeStack.pop();
  return true;
}

WideStringView CFX_CSSSyntaxParser::GetCurrentString() const {
  return m_Output.GetTrailingBlankTrimmedString();
}
