/***************************************************************************
*   Copyright (C) 2006 by Andreas Pakulat                                 *
*   apaku@gmx.de                                                          *
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************/

#ifndef _SCOPE_H_
#define _SCOPE_H_

#include <tqstring.h>
#include <tqstringlist.h>
#include <tqmap.h>
#include <set>

#include "qmakeast.h"
#include "qmakedefaultopts.h"

#ifdef DEBUG
#include "qmakeastvisitor.h"
#endif

class Scope;
class TrollProjectPart;

class Scope
{
public:

    enum ScopeType {
        ProjectScope,
        FunctionScope,
        SimpleScope,
        IncludeScope,
        InvalidScope
    };
    static const TQStringList KnownVariables;
    static const TQStringList KnownConfigValues;

    Scope( const TQMap<TQString, TQString>& env, const TQString &filename, TrollProjectPart* part );
    ~Scope();

    void saveToFile() const;

    // Changing variable values
    void addToPlusOp( const TQString& variable, const TQStringList& values );
    void removeFromPlusOp( const TQString& variable, const TQStringList& values );
    void addToMinusOp( const TQString& variable, const TQStringList& values );
    void removeFromMinusOp( const TQString& variable, const TQStringList& values );
    void addToEqualOp( const TQString& variable, const TQStringList& values );
    void removeFromEqualOp( const TQString& variable, const TQStringList& values );
    void setPlusOp( const TQString& variable, const TQStringList& values );
    void setEqualOp( const TQString& variable, const TQStringList& values );
    void setMinusOp( const TQString& variable, const TQStringList& values );

    // Checks wether a line like VAR = exists in this subscope
    bool isVariableReset( const TQString& var );

    // Fetch the valuelist for the variable op combination inside this scope
    TQStringList variableValuesForOp( const TQString& variable, const TQString& op ) const;

    // Fetch the variable values by running over the statements and adding/removing/setting
    // as the encountered op's say, begin with the parent projects variableValues list
    TQStringList variableValues( const TQString& variable, bool checkIncParent = true, bool fetchFromParent = true, bool evaluateSubScopes = false );

    // Remove a variable+Op combination from the scope, if existant
    void removeVariable( const TQString& var, const TQString& op );

    // Getting to know what type of scope this is
    ScopeType scopeType() const;

    // This returns the function+args, the scopename or the pro/pri file
    // depending on the type of scope
    TQString scopeName() const;

    // Returns the projectName for this scope, this is equal to the last part of the projectDir()
    TQString projectName() const;

    // Returns just the filename of this project's .pro file
    TQString fileName() const;

    // Returns the absolute path of the dir containing the .pro file
    TQString projectDir() const;

    // get the parent Scope
    Scope* parent() const { return m_parent; }

    // Fetching sub-scopes
    const TQValueList<Scope*> scopesInOrder() const { return m_scopes.values(); }
    // Working on SubScopes
    /*
     * creates a new function scope at the end of this (Sub-)AST and returns the Scope wrapping it
     */
    Scope* createFunctionScope( const TQString& funcName, const TQString& args );
    /*
     * creates a new simple scope at the end of this (Sub-)AST and returns the Scope wrapping it
     */
    Scope* createSimpleScope( const TQString& scopename );

    /*
     * creates a new function scope at the end of this (Sub-)AST
     * and a new include scope inside the new function scope.
     * It returns the Scope wrapping the include-AST, the function scope AST
     * can be accessed easily using the parent() method.
     */
    Scope* createIncludeScope( const TQString& includeFile, bool negate = false );

    /*
     * creates a new subproject in dir (create's dir if necessary)
     * If this scope is not a project scope the subproject will be added to this
     * Scope only, i.e. it is not seen in the project-files list of subdirs
     */
    Scope* createSubProject( const TQString& dir );

    /* delete the given function scope */
    bool deleteFunctionScope( unsigned int );
    /* delete the given simple scope */
    bool deleteSimpleScope( unsigned int );
    /* delete the given include scope */
    bool deleteIncludeScope( unsigned int );
    /* deletes the subproject (including the subdir if deleteSubdir is true) */
    bool deleteSubProject( unsigned int, bool deleteSubdir );

    /* Provide a Map of Custom variables */
    const TQMap<unsigned int, TQMap<TQString, TQString> > customVariables() const;

    unsigned int addCustomVariable( const TQString& var, const TQString& op, const TQString& values );

    /* Removes the variable with the given id if it exists */
    void removeCustomVariable( unsigned int );

    /* Update the values of the variable/operation combo var+op to values */
    void updateCustomVariable( unsigned int, const TQString&, const TQString& , const TQString& );

    // Checks wether a TQStringList contains any values that are not whitespace or \\n
    static bool listIsEmpty( const TQStringList& values );

    /* returns wether this is an enabled subproject or a disabled one */
    bool isEnabled() { return m_isEnabled; }

    TQStringList cleanStringList(const TQStringList& list) const;

    /* Reload a project scope */
    void reloadProject();

    /* creates a new disabled Scope child and add SUBDIRS -= dir to this scope */
    Scope* disableSubproject( const TQString& );

    /* return the "position" of this scope in the list of scopes */
    unsigned int getNum() { return m_num; }

    TQStringList allFiles( const TQString& );

    TQString resolveVariables( const TQString& ) const;

    TQString findCustomVarForPath( const TQString& );

#ifdef DEBUG
    void printTree();
#endif

private:

    // Builds the scope-lists and the customVariables list
    void init();

    /*
     * Updates the given Variable+op with the values, if removeFromOp is true it removes the values, else it adds them
     * this works it's way back through the current scope and changes the last occurence of op to
     * include all new values.
     *
     * Depending on "op" it might end the search earlier (if op is += it also stops at =)
     *
     * This also removes the values from other assignments if the operation is not op, i.e.
     * if op is += removes values from any occurence of -=
     * if op is -= removes values from any occurence of = and +=
     * if op is = removes values frmo any occurence of -=
     */
    void updateVariable( const TQString& variable, const TQString& op, const TQStringList& values, bool removeFromOp );

    /*
     * Helper Function to change the origValues list with the values from newValues
     * depending on the state of "remove" either adds or removes all entries from newValues
     * to origValues if they didn't exist there yet
     */
    void updateValues( TQStringList& origValues, const TQStringList& newValues, bool remove = false, TQString indent = "  " );

    /*
     * Finds an existing variable, returns the end() of the statemenst if it is not found
     */
    TQValueList<TQMake::AST*>::iterator findExistingVariable( const TQString& variable );

    // Private constructors for easier subscope creation
    /*
     * just initializes the lists from the scope
     */
    Scope( const TQMap<TQString, TQString>& env, unsigned int num, Scope* parent, TQMake::ProjectAST* root, TQMakeDefaultOpts*, TrollProjectPart* part );
    /*
     * reads the given filename and parses it. If it doesn't exist creates an empty
     * ProjectAST with the given filename
     */
    Scope( const TQMap<TQString, TQString>& env, unsigned int num, Scope* parent, const TQString& filename, TrollProjectPart* part, bool isEnabled = true );
    /*
     * Creates a scope for an include statement, parses the file and initializes the Scope
     * Create an empty ProjectAST if the file cannot be found or parsed.
     */
    Scope( const TQMap<TQString, TQString>& env, unsigned int num, Scope* parent, TQMake::IncludeAST* incast, const TQString& path, const TQString& incfile, TQMakeDefaultOpts*, TrollProjectPart* part );


    // runs through the statements until stopHere is found (or the end is reached, if stopHere is 0),
    // using the given list as startvalue
    // Changes the list using the +=, -=, = operations accordingly
    void calcValuesFromStatements( const TQString& variable, TQStringList& result, bool, TQMake::AST* stopHere = 0, bool fetchFromParent = true, bool setDefault = true, bool evaluateSubScopes = false ) const;

    // Check wether the two operators are compatible
    static bool isCompatible( const TQString& op1, const TQString& op2);

    // Check wether the 2 lists are equal, regardless of element order.
    static bool listsEqual(const TQStringList& , const TQStringList& );

    // Load and Save project files, these only work on ProjectScope's
    bool loadFromFile( const TQString& filename );

    TQString funcScopeKey( TQMake::ProjectAST* funcast ) const { return funcast->scopedID + "(" + funcast->args + ")"; }

    unsigned int getNextScopeNum() { if( m_scopes.isEmpty() ) return 0; else return (m_scopes.keys().last()+1); }

    TQStringList lookupVariable( const TQString& var );

    TQStringList resolveVariables( const TQStringList&, TQMake::AST* = 0 ) const;
    TQStringList variableValues( const TQString& variable, TQMake::AST*, bool fetchFromParent = true ) const;
    TQString resolveVariables( const TQString& , TQMake::AST* ) const;

    // This function determines the currently used String for fileending, it can be \n, \r or \r\n
    TQString getLineEndingString() const;
    bool isComment( const TQString& ) const;
    bool containsContinue( const TQString& ) const;
    void allFiles( const TQString&, std::set<TQString>& );

    void loadDefaultOpts();

    TQMake::ProjectAST* m_root;
    TQMake::IncludeAST* m_incast;
    TQMap<unsigned int, TQMake::AssignmentAST*> m_customVariables;
    TQMap<unsigned int, Scope*> m_scopes;
    Scope* m_parent;
    unsigned int m_maxCustomVarNum;

    TQString replaceWs(TQString);


    // The "position" inside the parent scope that this scope starts at
    unsigned int m_num;
    bool m_isEnabled;
    TrollProjectPart* m_part;
    TQMakeDefaultOpts* m_defaultopts;
    TQMap<TQString, TQStringList> m_varCache;
    TQMap<TQString,TQString> m_environment;

#ifdef DEBUG
    class PrintAST : TQMake::ASTVisitor
    {

    public:
        PrintAST();
        virtual void processProject( TQMake::ProjectAST* p );
        virtual void enterRealProject( TQMake::ProjectAST* p );

        virtual void leaveRealProject( TQMake::ProjectAST* p );

        virtual void enterScope( TQMake::ProjectAST* p );

        virtual void leaveScope( TQMake::ProjectAST* p );

        virtual void enterFunctionScope( TQMake::ProjectAST* p );

        virtual void leaveFunctionScope( TQMake::ProjectAST* p );

        virtual void processAssignment( TQMake::AssignmentAST* a);

        virtual void processNewLine( TQMake::NewLineAST* n);

        virtual void processComment( TQMake::CommentAST* a);

        virtual void processInclude( TQMake::IncludeAST* a);

        TQString replaceWs(TQString);

    private:
        TQString getIndent();
        int indent;

    };
#endif

};

#endif
