// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2003  The 'ac++' developers (see aspectc.org)
//                                                                
// 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.            
//                                                                
// This program 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 General Public License for more details.                   
//                                                                
// You should have received a copy of the GNU General Public      
// License along with this program; if not, write to the Free     
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
// MA  02111-1307  USA                                            

#include <stdio.h>
#include <stdlib.h>

#include "Puma/ErrorSink.h"
#include "Puma/FileUnit.h"

#include "Repository.h"
#include "version.h"
#include "PointCut.h"
#include "JoinPointLoc.h"
#include "IntroductionInfo.h"
#include "OrderInfo.h"
#include "AdviceInfo.h"
#include "AspectInfo.h"
#include "RepoXMLDoc.h"

void Repository::open (const char *name, ErrorSink &err) {

  RepoXMLDoc doc;

  create (name);

  if (!doc.load (name) || !doc.root ().has_name ("ac-project")) {
    err << sev_error << "project repository '" << name << "' cannot be opened" 
      " or is invalid" << endMessage;
    return;
  }

  string version = doc.root ().get_str_prop ("version");
  if (version != ac_version ()) {
    err << sev_warning << "project file version '" 
        << version.c_str () << "' differs from ac++ version" << endMessage;
  }

  RepoXMLNode joinpoints, files, aspects, adv;
  
  // iterate the top-level project elements
  for (RepoXMLNode::iter curr = doc.root ().first_child ();
       curr != doc.root ().end_child (); ++curr) {
    if ((*curr).has_name ("joinpoint-list"))
      joinpoints = *curr;
    else if ((*curr).has_name ("file-list"))
      files = *curr;
    else if ((*curr).has_name ("aspect-list"))
      aspects = *curr;
    else if ((*curr).has_name ("advice-list"))
      adv = *curr;
  }
  
  if (!joinpoints || !files || !aspects || !adv) {
    err << sev_error << "invalid project file, missing list" << endMessage;
    return;
  }

  // now read all join points, files, and aspects
  _jprepo.get_xml (joinpoints);
  _advrepo.get_xml (adv);
  _asprepo.get_xml (aspects);
  _frepo.get_xml (files);
}

void Repository::create (const char *name) {
  _name           = name;
}

void Repository::save (ErrorSink &err) {

  // create an XML DOM
  RepoXMLDoc doc;
  doc.create ("ac-project");
  doc.root ().set_str_prop ("version", ac_version ());

  // handle join points, files, and aspects
  _jprepo.make_xml (doc.root ());
  _advrepo.make_xml (doc.root ());
  _asprepo.make_xml (doc.root ());
  _frepo.make_xml (doc.root ());

  // save the file
  if (!doc.save (_name)) {
    err << sev_error << "Saving repository '" << _name << "' failed"
	<< endMessage;
  }
}

void Repository::close () {
  _name = (const char *)0;
}

Repository::REPO_ID Repository::consider (const Unit &unit) {
  if (!unit.isFile ())
    return -1;

  return _frepo.insert (unit.name (),
			((Token*)unit.last ())->location ().line (), _primary);
}

Repository::REPO_ID Repository::consider (JoinPointLoc &jpl, int adv) {
  REPO_ID file_id = consider (*jpl.unit ());
  REPO_ID jp_id = _jprepo.insert (file_id, jpl.line (), jpl.signature (),
                                  jpl.type_str (), adv, jpl.lines ());
  return jp_id;
}

Repository::REPO_ID Repository::consider (AspectInfo &ai) {
  REPO_ID file_id = consider (ai.unit ());
  return _asprepo.insert (file_id, ai.line (), ai.name ());
}

void Repository::setup (Unit* prim_unit) {
  _frepo.noref ();
  _jprepo.noref ();
  _advrepo.noref ();
  _asprepo.noref ();
  _primary = -1;
  _primary = consider (*prim_unit);
}

void Repository::cleanup () {
  set<int> dep_files;
  _frepo.dependent (_primary, dep_files);
  _jprepo.cleanup (dep_files);
  _advrepo.cleanup (dep_files);
  _asprepo.cleanup (dep_files);
  _frepo.cleanup (_primary);
}

void Repository::update (IntroductionInfo &ii, PointCut &target) {
  // remember aspect information in the project repository
  REPO_ID aspect_id = consider (*ii.aspect ());
  REPO_ID file_id   = consider (ii.unit ());
  REPO_ID advice_id = _advrepo.insert (file_id, ii.line (), ii.type_str (), 
				       aspect_id, ii.lines ());
  for (PointCut::iterator iter = target.begin ();
       iter != target.end (); ++iter) {
    JoinPointLoc &jpl = *((*iter).location ());
    REPO_ID jp_id = consider (jpl, advice_id);
    jpl.id (jp_id);
  }
}

void Repository::update (AdviceInfo &ai, PointCut &target) {
  // remember aspect information in the project repository
  REPO_ID aspect_id = consider (*ai.aspect ());
  REPO_ID file_id   = consider (ai.code ().unit ());
  REPO_ID advice_id = _advrepo.insert (file_id, ai.code ().line (),
    ai.code ().type_str (), aspect_id, ai.code ().lines ());
  
  for (PointCut::iterator iter = target.begin ();
       iter != target.end (); ++iter) {
    JoinPointLoc &jpl = *((*iter).location ());
    if (!(jpl.type () == JoinPointLoc::Method &&
          !((JPL_Method&)jpl).func_info ()->isDefined ()) &&
        !(jpl.type () == JoinPointLoc::MethodCall &&
          ((JPL_Code&)jpl).is_pseudo ())) {
      REPO_ID jp_id = consider (jpl, advice_id);
      jpl.id (jp_id);
    }
  }
}

void Repository::update (OrderInfo &oi) {
  // remember order advice information in the project repository
  REPO_ID aspect_id = consider (*oi.aspect ());
  REPO_ID file_id   = consider (oi.unit ());
  /* REPO_ID advice_id = */ _advrepo.insert (file_id, oi.line (),
    "order", aspect_id, oi.lines ());
}
