/****************************************************************************
**
** Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Script Generator project on Qt Labs.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "typeparser.h"

#include <qdebug.h>
#include <QStack>

class Scanner
{
public:
  enum Token {
    StarToken,
    AmpersandToken,
    LessThanToken,
    ColonToken,
    CommaToken,
    EllipsisToken,
    OpenParenToken,
    CloseParenToken,
    SquareBegin,
    SquareEnd,
    GreaterThanToken,

    ConstToken,
    Identifier,
    NoToken
  };

  Scanner(const QString& s)
    : m_pos(0)
    , m_length(s.length())
    , m_chars(s.constData())
  {
  }

  Token nextToken();
  QString identifier() const;

private:
  int m_pos;
  int m_length;
  int m_token_start;
  const QChar* m_chars;
};

QString Scanner::identifier() const
{
  return QString(m_chars + m_token_start, m_pos - m_token_start);
}

Scanner::Token Scanner::nextToken()
{
  Token tok = NoToken;

  // remove whitespace
  while (m_pos < m_length && m_chars[m_pos] == ' ') {
    ++m_pos;
  }

  m_token_start = m_pos;

  while (m_pos < m_length) {

    const QChar& c = m_chars[m_pos];

    if (tok == NoToken) {
      switch (c.toLatin1()) {
      case '*':
        tok = StarToken;
        break;
      case '&':
        tok = AmpersandToken;
        break;
      case '<':
        tok = LessThanToken;
        break;
      case '>':
        tok = GreaterThanToken;
        break;
      case ',':
        tok = CommaToken;
        break;
      case '(':
        tok = OpenParenToken;
        break;
      case ')':
        tok = CloseParenToken;
        break;
      case '[':
        tok = SquareBegin;
        break;
      case ']':
        tok = SquareEnd;
        break;
      case ':':
        tok = ColonToken;
        Q_ASSERT(m_pos + 1 < m_length);
        ++m_pos;
        break;
      case '.':
        if (m_pos + 2 < m_length && c == m_chars[m_pos + 1] && c == m_chars[m_pos + 2]) {
          tok = EllipsisToken;
          m_pos += 2;
          break;
        }
        Q_FALLTHROUGH();
      default:
        if (c.isLetterOrNumber() || c == '_')
          tok = Identifier;
        else
          qFatal("Unrecognized character in lexer: %c", c.toLatin1());
        break;
      }
    }

    if (tok <= GreaterThanToken) {
      ++m_pos;
      break;
    }

    if (tok == Identifier) {
      if (c.isLetterOrNumber() || c == '_')
        ++m_pos;
      else
        break;
    }
  }

  if (tok == Identifier && m_pos - m_token_start == 5) {
    if (m_chars[m_token_start] == 'c' && m_chars[m_token_start + 1] == 'o' && m_chars[m_token_start + 2] == 'n'
        && m_chars[m_token_start + 3] == 's' && m_chars[m_token_start + 4] == 't')
      tok = ConstToken;
  }

  return tok;
}

TypeParser::Info TypeParser::parse(const QString& str)
{
  Scanner scanner(str);

  Info info;
  QStack<Info*> stack;
  stack.push(&info);

  bool colon_prefix = false;
  bool in_array = false;
  QString array;

  Scanner::Token tok = scanner.nextToken();
  while (tok != Scanner::NoToken) {

    //         switch (tok) {
    //         case Scanner::StarToken: printf(" - *\n"); break;
    //         case Scanner::AmpersandToken: printf(" - &\n"); break;
    //         case Scanner::EllipsisToken: printf(" - ...\n"); break;
    //         case Scanner::LessThanToken: printf(" - <\n"); break;
    //         case Scanner::GreaterThanToken: printf(" - >\n"); break;
    //         case Scanner::ColonToken: printf(" - ::\n"); break;
    //         case Scanner::CommaToken: printf(" - ,\n"); break;
    //         case Scanner::ConstToken: printf(" - const\n"); break;
    //         case Scanner::SquareBegin: printf(" - [\n"); break;
    //         case Scanner::SquareEnd: printf(" - ]\n"); break;
    //         case Scanner::Identifier: printf(" - '%s'\n", qPrintable(scanner.identifier())); break;
    //         default:
    //             break;
    //         }

    switch (tok) {

    case Scanner::StarToken:
      ++stack.top()->indirections;
      break;

    case Scanner::AmpersandToken:
      stack.top()->is_reference = true;
      break;

    case Scanner::LessThanToken:
      stack.top()->template_instantiations << Info();
      stack.push(&stack.top()->template_instantiations.last());
      break;

    case Scanner::CommaToken:
      stack.pop();
      stack.top()->template_instantiations << Info();
      stack.push(&stack.top()->template_instantiations.last());
      break;

    case Scanner::GreaterThanToken:
      stack.pop();
      break;

    case Scanner::ColonToken:
      colon_prefix = true;
      break;

    case Scanner::ConstToken:
      stack.top()->is_constant = true;
      break;

    case Scanner::OpenParenToken: // function pointers not supported
    case Scanner::CloseParenToken:
    case Scanner::EllipsisToken: // variadic templates not supported
    {
      Info i;
      i.is_busted = true;
      return i;
    }

    case Scanner::Identifier:
      if (in_array) {
        array = scanner.identifier();
      } else if (colon_prefix || stack.top()->qualified_name.isEmpty()) {
        stack.top()->qualified_name << scanner.identifier();
        colon_prefix = false;
      } else {
        stack.top()->qualified_name.last().append(" " + scanner.identifier());
      }
      break;

    case Scanner::SquareBegin:
      in_array = true;
      break;

    case Scanner::SquareEnd:
      in_array = false;
      stack.top()->arrays += array;
      break;

    default:
      break;
    }

    tok = scanner.nextToken();
  }

  return info;
}

QString TypeParser::Info::instantiationName() const
{
  QString s(qualified_name.join("::"));
  if (!template_instantiations.isEmpty()) {
    s += '<';
    for (int i = 0; i < template_instantiations.size(); ++i) {
      if (i != 0)
        s += ",";
      s += template_instantiations.at(i).toString();
    }
    s += '>';
  }

  return s;
}

QString TypeParser::Info::toString() const
{
  QString s;

  if (is_constant)
    s += "const ";
  s += instantiationName();
  for (int i = 0; i < arrays.size(); ++i)
    s += "[" + arrays.at(i) + "]";
  s += QString(indirections, '*');
  if (is_reference)
    s += '&';

  return s;
}
