/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <iostream>
#include <cstdlib>
#include "libdecomp.hh"

namespace ghidra {

using std::cin;
using std::cout;
using std::cerr;

class IfcLoadFile : public IfaceDecompCommand {
public:
  virtual void execute(istream &s);
};

class IfcAddpath : public IfaceDecompCommand {
public:
  virtual void execute(istream &s);
};

class IfcSave : public IfaceDecompCommand {
public:
  virtual void execute(istream &s);
};

class IfcRestore : public IfaceDecompCommand {
public:
  virtual void execute(istream &s);
};

void IfcLoadFile::execute(istream &s)

{
  string filename,target;

  if (dcp->conf != (Architecture *)0) 
    throw IfaceExecutionError("Load image already present");
  s >> filename;
  if (!s.eof()) {		// If there are two parameters
    target = filename;		// Second is filename, first is target
    s >> filename;
  }
  else
    target = "default";

  ArchitectureCapability *capa = ArchitectureCapability::findCapability(filename);
  if (capa == (ArchitectureCapability *)0)
    throw IfaceExecutionError("Unable to recognize imagefile "+filename);
  dcp->conf = capa->buildArchitecture(filename,target,status->optr);
				// Attempt to open file and discern
				// the processor architecture  
  DocumentStorage store;	// temporary storage for xml docs

#ifdef CPUI_RULECOMPILE
  if (dcp->experimental_file.size() != 0) {
    *status->optr << "Trying to parse " << dcp->experimental_file << " for experimental rules" << endl;
    try {
      Element *root = store.openDocument(dcp->experimental_file)->getRoot();
      if (root->getName() == "experimental_rules")
	store.registerTag(root);
      else
	*status->optr << "Wrong tag type for experimental rules: "+root->getName() << endl;
    }
    catch(DecoderError &err) {
      *status->optr << err.explain << endl;
      *status->optr << "Skipping experimental rules" << endl;
    }
  }
#endif
  string errmsg;
  bool iserror = false;
  try {
    dcp->conf->init(store);
  } catch(DecoderError &err) {
    errmsg = err.explain;
    iserror = true;
  } catch(LowlevelError &err) {
    errmsg = err.explain;
    iserror = true;
  }
  if (iserror) {
    *status->optr << errmsg << endl;
    *status->optr << "Could not create architecture" << endl;
    delete dcp->conf;
    dcp->conf = (Architecture *)0;
    return;
  }
  if (capa->getName() == "xml")		// If file is xml
    dcp->conf->readLoaderSymbols(); // Read in loader symbols
#ifdef OPACTION_DEBUG
  dcp->conf->setDebugStream(status->fileoptr);
#endif
  *status->optr << filename << " successfully loaded: " << dcp->conf->getDescription() << endl;
}

void IfcAddpath::execute(istream &s)

{
  string newpath;

  s >> newpath;
  if (newpath.empty())
    throw IfaceParseError("Missing path name");
  SleighArchitecture::specpaths.addDir2Path(newpath);
}

static string savefile;

void IfcSave::execute(istream &s)

{				// Save state to filename specified on command line
  ofstream fs;

  s >> ws;
  if (!s.eof())
    s >> savefile;

  if (savefile.size()==0)
    throw IfaceParseError("Missing savefile name");

  fs.open( savefile.c_str() );
  if (!fs)
    throw IfaceExecutionError("Unable to open file: "+savefile);

  XmlEncode encoder(fs);
  dcp->conf->encode(encoder);
  fs.close();
}

void IfcRestore::execute(istream &s)

{				// Restore saved state
  s >> savefile;
  if (savefile.size()==0)
    throw IfaceParseError("Missing file name");

  DocumentStorage store;
  Document *doc = store.openDocument(savefile);
  store.registerTag(doc->getRoot());
  dcp->clearArchitecture();	// Clear any old architecture
  ArchitectureCapability *capa = ArchitectureCapability::findCapability(doc);
  if (capa == (ArchitectureCapability *)0)
    throw IfaceExecutionError("Could not find savefile tag");
  dcp->conf = capa->buildArchitecture("","",status->optr);
  try {
    dcp->conf->restoreXml(store);
  } catch(LowlevelError &err) {
    throw IfaceExecutionError(err.explain);
  } catch(DecoderError &err) {
    throw IfaceExecutionError(err.explain);
  }
  
#ifdef OPACTION_DEBUG
  dcp->conf->setDebugStream(status->fileoptr);
#endif
  *status->optr << savefile << " successfully loaded: " << dcp->conf->getDescription() << endl;
}

} // End namespace ghidra

int main(int argc,char **argv)

{
  using namespace ghidra;

  const char *initscript = (const char *)0;

  {
    vector<string> extrapaths;
    int4 i = 1;
    while ((i < argc) && (argv[i][0] == '-')) {
      if (argv[i][1] == 'i')
	initscript = argv[++i];
      else if (argv[i][1] == 's')
	extrapaths.push_back(argv[++i]);
      i += 1;
    }

    string ghidraroot = FileManage::discoverGhidraRoot(argv[0]);
    if (ghidraroot.size() == 0) {
      const char *sleighhomepath = getenv("SLEIGHHOME");
      if (sleighhomepath == (const char*) 0) {
	if (extrapaths.empty()) {
	  cerr << "Could not discover root of Ghidra installation" << endl;
	  exit(1);
	}
      }
      else
	ghidraroot = sleighhomepath;
    }
    startDecompilerLibrary(ghidraroot.c_str(), extrapaths);
  }

  int4 retval = 2;
  try {
    unique_ptr<IfaceStatus> status(new IfaceTerm("[decomp]> ",cin,cout)); // Set up interface
    IfaceCapability::registerAllCommands(status.get());	// Register commands for decompiler and all modules

    // Extra commands specific to the console application
    status->registerCom(new IfcLoadFile(),"load","file");
    status->registerCom(new IfcAddpath(),"addpath");
    status->registerCom(new IfcSave(),"save");
    status->registerCom(new IfcRestore(),"restore");

    if (initscript != (const char *)0) {
      try {
	status->pushScript(initscript,"init> ");
      } catch(IfaceParseError &err) {
	*status->optr << err.explain << endl;
	status->done = true;
      }
    }

    if (!status->done)
      mainloop(status.get());
    retval = status->isInError() ? 1 : 0;

#ifdef CPUI_STATISTICS
    IfaceDecompData *decompdata = (IfaceDecompData *)status->getData("decompile");
    decompdata->conf->stats->printResults(cout);
#endif

  } catch(IfaceError &err) {
    cerr << "Interface error: " << err.explain << endl;
    retval = 2;
  }

  shutdownDecompilerLibrary();

  exit(retval);
}
