/* Copyright (C) 2021-2025 Free Software Foundation, Inc.
   Contributed by Oracle.

   This file is part of GNU Binutils.

   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 3, 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, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "config.h"
#include "util.h"
#include "DbeSession.h"
#include "Experiment.h"
#include "DbeFile.h"
#include "ExpGroup.h"
#include "DbeJarFile.h"

DbeFile::DbeFile (const char *filename)
{
  filetype = 0;
  name = dbe_strdup (filename);
  name = canonical_path (name);
  orig_location = NULL;
  location = NULL;
  location_info = NULL;
  jarFile = NULL;
  container = NULL;
  need_refind = true;
  inArchive = false;
  sbuf.st_atim.tv_sec = 0;
  experiment = NULL;
}

DbeFile::~DbeFile ()
{
  free (name);
  free (location);
  free (orig_location);
  free (location_info);
}

void
DbeFile::set_need_refind (bool val)
{
  if (val != need_refind)
    {
      free (location_info);
      location_info = NULL;
      need_refind = val;
    }
}

void
DbeFile::set_location (const char *filename)
{
  free (location);
  location = NULL;
  if (filename)
    {
      if (strncmp (filename, NTXT ("./"), 2) == 0)
	filename += 2;
      location = canonical_path (dbe_strdup (filename));
    }
  free (location_info);
  location_info = NULL;
  set_need_refind (false);
}

char *
DbeFile::get_location_info ()
{
  if (location_info == NULL)
    {
      char *fnm = get_name ();
      char *loc = get_location ();
      Dprintf (DEBUG_DBE_FILE, NTXT ("DbeFile::get_location_info: %s %s\n"),
	       STR (fnm), STR (loc));
      if (loc == NULL)
	{
	  if (filetype & F_FICTION)
	    location_info = dbe_strdup (fnm);
	  else
	    location_info = dbe_sprintf (GTXT ("%s (not found)"),
					 get_relative_path (fnm));
	}
      else
	{
	  char *r_fnm = get_relative_path (fnm);
	  char *r_loc = get_relative_path (loc);
	  if (strcmp (r_fnm, r_loc) == 0)
	    location_info = dbe_strdup (r_fnm);
	  else
	    {
	      char *bname = get_basename (r_fnm);
	      if (strcmp (bname, r_loc) == 0)  // found in current directory
		location_info = dbe_strdup (bname);
	      else
		location_info = dbe_sprintf (GTXT ("%s (found as %s)"), bname, r_loc);
	    }
	}
    }
  return location_info;
}

char *
DbeFile::getResolvedPath ()
{
  if (get_location ())
    return location;
  return name;
}

DbeFile *
DbeFile::getJarDbeFile (char *fnm, int sym)
{
  Dprintf (DEBUG_DBE_FILE, NTXT ("DbeFile::getJarDbeFile: %s fnm='%s' sym=%d\n"),
	   STR (name), STR (fnm), sym);
  DbeFile *df = NULL;
  if (sym)
    {
      char *s = strchr (fnm, sym);
      if (s)
	{
	  s = dbe_strndup (fnm, s - fnm);
	  df = dbeSession->getDbeFile (s, F_JAR_FILE | F_FILE);
	  free (s);
	}
    }
  if (df == NULL)
    df = dbeSession->getDbeFile (fnm, F_JAR_FILE | F_FILE);
  if (df && (df->experiment == NULL))
    df->experiment = experiment;
  return df;
}

char *
DbeFile::get_location (bool find_needed)
{
  Dprintf (DEBUG_DBE_FILE, NTXT ("get_location 0x%x %s\n"), filetype, STR (name));
  if (!find_needed)
    return need_refind ? NULL : location;
  if (location || !need_refind)
    return location;
  set_need_refind (false);
  if ((filetype & F_FICTION) != 0)
    return NULL;
  if (filetype == F_DIR_OR_JAR)
    {
      find_in_archives (name);
      if (location)
	{
	  filetype |= F_JAR_FILE | F_FILE;
	  return location;
	}
      find_in_pathmap (name);
      if (location)
	return location;
      if (check_access (name) == F_DIRECTORY)
	{
	  filetype |= F_DIRECTORY;
	  set_location (name);
	  return location;
	}
    }

  if ((filetype & F_FILE) != 0)
    {
      if (experiment)
	{
	  char *fnm = experiment->checkFileInArchive (name, false);
	  if (fnm)
	    {
	      set_location (fnm);
	      inArchive = true;
	      sbuf.st_mtime = 0; // Don't check timestamps
	      free (fnm);
	      return location;
	    }
	  if ((filetype & F_JAVACLASS) != 0)
	    {
	      if (orig_location)
		{
		  Dprintf (DEBUG_DBE_FILE, NTXT ("DbeFile::get_location:%d name='%s' orig_location='%s'\n"),
			   (int) __LINE__, name, orig_location);
		  // Parse a fileName attribute. There are 4 possibilities:
		  //   file:<Class_Name>
		  //   file:<name_of_jar_or_zip_file>
		  //   jar:file:<name_of_jar_or_zip_file>!<Class_Name>
		  //   zip:<name_of_jar_or_zip_file>!<Class_Name>
		  DbeFile *jar_df = NULL;
		  if (strncmp (orig_location, NTXT ("zip:"), 4) == 0)
		    jar_df = getJarDbeFile (orig_location + 4, '!');
		  else if (strncmp (orig_location, NTXT ("jar:file:"), 9) == 0)
		    jar_df = getJarDbeFile (orig_location + 9, '!');
		  else if (strncmp (orig_location, NTXT ("file:"), 5) == 0
			   && isJarOrZip (orig_location + 5))
		    jar_df = getJarDbeFile (orig_location + 5, 0);
		  if (jar_df)
		    {
		      if (find_in_jar_file (name, jar_df->get_jar_file ()))
			{
			  Dprintf (DEBUG_DBE_FILE, NTXT ("DbeFile::get_location:%d FOUND name='%s' location='%s' jar='%s'\n"),
				   (int) __LINE__, name, STR (location), STR (jar_df->get_location ()));
			  inArchive = jar_df->inArchive;
			  container = jar_df;
			  return location;
			}
		    }
		  if (strncmp (orig_location, NTXT ("file:"), 5) == 0
		      && !isJarOrZip (orig_location + 5))
		    {
		      DbeFile *df = new DbeFile (orig_location + 5);
		      df->filetype = DbeFile::F_FILE;
		      df->experiment = experiment;
		      fnm = df->get_location ();
		      if (fnm)
			{
			  set_location (fnm);
			  inArchive = df->inArchive;
			  sbuf.st_mtime = df->sbuf.st_mtime;
			  Dprintf (DEBUG_DBE_FILE, NTXT ("DbeFile::get_location:%d FOUND name='%s' orig_location='%s' location='%s'\n"),
				   (int) __LINE__, name, orig_location, fnm);
			  delete df;
			  return location;
			}
		      delete df;
		    }
		}
	      fnm = dbe_sprintf (NTXT ("%s/%s/%s"), experiment->get_expt_name (), SP_DYNAMIC_CLASSES, name);
	      if (find_file (fnm))
		{
		  inArchive = true;
		  sbuf.st_mtime = 0; // Don't check timestamps
		  Dprintf (DEBUG_DBE_FILE, NTXT ("DbeFile::get_location:%d FOUND name='%s' location='%s'\n"),
			   (int) __LINE__, name, fnm);
		  free (fnm);
		  return location;
		}
	      free (fnm);
	    }
	}
    }

  if (dbeSession->archive_mode)
    {
      find_file (name);
      if (location)
	return location;
    }

  bool inPathMap = find_in_pathmap (name);
  if (location)
    return location;
  find_in_setpath (name, dbeSession->get_search_path ());
  if (location)
    return location;
  if ((filetype & (F_JAVACLASS | F_JAVA_SOURCE)) != 0)
    {
      find_in_classpath (name, dbeSession->get_classpath ());
      if (location)
	return location;
    }
  if (!inPathMap)
    find_file (name);
  Dprintf (DEBUG_DBE_FILE && (location == NULL),
	   "DbeFile::get_location:%d NOT FOUND name='%s'\n", __LINE__, name);
  return location;
}

int
DbeFile::check_access (const char *filename)
{
  if (filename == NULL)
    return F_NOT_FOUND;
  int st = dbe_stat (filename, &sbuf);
  Dprintf (DEBUG_DBE_FILE, NTXT ("check_access: %d 0x%x %s\n"), st, filetype, filename);
  if (st == 0)
    {
      if (S_ISDIR (sbuf.st_mode))
	return F_DIRECTORY;
      else if (S_ISREG (sbuf.st_mode))
	return F_FILE;
      return F_UNKNOWN; // Symbolic link or unknown type of file
    }
  sbuf.st_atim.tv_sec = 0;
  sbuf.st_mtime = 0; // Don't check timestamps
  return F_NOT_FOUND; // File not found
}

bool
DbeFile::isJarOrZip (const char *fnm)
{
  size_t len = strlen (fnm) - 4;
  return len > 0 && (strcmp (fnm + len, NTXT (".jar")) == 0
		     || strcmp (fnm + len, NTXT (".zip")) == 0);
}

char *
DbeFile::find_file (const char *filename)
{
  switch (check_access (filename))
    {
    case F_DIRECTORY:
      if (filetype == F_DIR_OR_JAR)
	filetype |= F_DIRECTORY;
      if ((filetype & F_DIRECTORY) != 0)
	set_location (filename);
      break;
    case F_FILE:
      if (filetype == F_DIR_OR_JAR)
	{
	  filetype |= F_FILE;
	  if (isJarOrZip (filename))
	    filetype |= F_JAR_FILE;
	}
      if ((filetype & F_DIRECTORY) == 0)
	set_location (filename);
      break;
    }
  return location;
}

DbeJarFile *
DbeFile::get_jar_file ()
{
  if (jarFile == NULL)
    {
      char *fnm = get_location ();
      if (fnm)
	jarFile = dbeSession->get_JarFile (fnm);
    }
  return jarFile;
}

char *
DbeFile::find_package_name (const char *filename, const char *dirname)
{
  char *nm = dbe_sprintf (NTXT ("%s/%s"), dirname, filename);
  if (!find_in_pathmap (nm))
    find_file (nm);
  free (nm);
  return location;
}

char *
DbeFile::find_in_directory (const char *filename, const char *dirname)
{
  if (filename && dirname)
    {
      char *nm = dbe_sprintf (NTXT ("%s/%s"), dirname, filename);
      find_file (nm);
      free (nm);
    }
  return location;
}

char *
DbeFile::find_in_jar_file (const char *filename, DbeJarFile *jfile)
{
  // Read .jar or .zip
  if (jfile == NULL)
    return NULL;
  int entry = jfile->get_entry (filename);
  if (entry >= 0)
    {
      char *fnm = dbeSession->get_tmp_file_name (filename, true);
      long long fsize = jfile->copy (fnm, entry);
      if (fsize >= 0)
	{
	  dbeSession->tmp_files->append (fnm);
	  set_location (fnm);
	  sbuf.st_size = fsize;
	  sbuf.st_mtime = 0; // Don't check timestamps
	  fnm = NULL;
	}
      free (fnm);
    }
  return location;
}

bool
DbeFile::find_in_pathmap (char *filename)
{
  Vector<pathmap_t*> *pathmaps = dbeSession->get_pathmaps ();
  bool inPathMap = false;
  if (strncmp (filename, NTXT ("./"), 2) == 0)
    filename += 2;
  for (int i = 0, sz = pathmaps ? pathmaps->size () : 0; i < sz; i++)
    {
      pathmap_t *pmp = pathmaps->fetch (i);
      size_t len = strlen (pmp->old_prefix);
      if (strncmp (pmp->old_prefix, filename, len) == 0
	  && (filename[len] == '/' || filename[len] == '\0'))
	{
	  inPathMap = true;
	  if (find_in_directory (filename + len, pmp->new_prefix))
	    {
	      return inPathMap;
	    }
	}
    }
  return inPathMap;
}

void
DbeFile::find_in_archives (char *filename)
{
  for (int i1 = 0, sz1 = dbeSession->expGroups->size (); i1 < sz1; i1++)
    {
      ExpGroup *gr = dbeSession->expGroups->fetch (i1);
      if (gr->founder)
	{
	  char *nm = gr->founder->checkFileInArchive (filename, false);
	  if (nm)
	    {
	      find_file (nm);
	      if (location)
		{
		  sbuf.st_mtime = 0; // Don't check timestamps
		  return;
		}
	    }
	}
    }
}

void
DbeFile::find_in_setpath (char *filename, Vector<char*> *searchPath)
{
  char *base = get_basename (filename);
  for (int i = 0, sz = searchPath ? searchPath->size () : 0; i < sz; i++)
    {
      char *spath = searchPath->fetch (i);
      // Check file in each experiment directory
      if (streq (spath, "$") || streq (spath, NTXT ("$expts")))
	{
	  // find only in founders and only LoadObj.
	  for (int i1 = 0, sz1 = dbeSession->expGroups->size (); i1 < sz1; i1++)
	    {
	      ExpGroup *gr = dbeSession->expGroups->fetch (i1);
	      char *exp_name = gr->founder->get_expt_name ();
	      if (gr->founder)
		{
		  if ((filetype & (F_JAVACLASS | F_JAVA_SOURCE)) != 0)
		    {
		      // Find with the package name
		      if (find_in_directory (filename, exp_name))
			 return;
		    }
		  if (find_in_directory (base, exp_name))
		    return;
		}
	    }
	  continue;
	}
      DbeFile *df = dbeSession->getDbeFile (spath, DbeFile::F_DIR_OR_JAR);
      if (df->get_location () == NULL)
	continue;
      if ((filetype & (F_JAVACLASS | F_JAVA_SOURCE)) != 0)
	{
	  if ((df->filetype & F_JAR_FILE) != 0)
	    {
	      if (find_in_jar_file (filename, df->get_jar_file ()))
		{
		  container = df;
		  return;
		}
	      continue;
	    }
	  else if ((df->filetype & F_DIRECTORY) != 0)
	    // Find with the package name
	    if (find_package_name (filename, spath))
	      return;
	}
      if ((df->filetype & F_DIRECTORY) != 0)
	if (find_in_directory (base, df->get_location ()))
	  return;
    }
}

void
DbeFile::find_in_classpath (char *filename, Vector<DbeFile*> *classPath)
{
  for (int i = 0, sz = classPath ? classPath->size () : 0; i < sz; i++)
    {
      DbeFile *df = classPath->fetch (i);
      if (df->get_location () == NULL)
	continue;
      if ((df->filetype & F_JAR_FILE) != 0)
	{
	  if (find_in_jar_file (filename, df->get_jar_file ()))
	    {
	      container = df;
	      return;
	    }
	}
      else if ((df->filetype & F_DIRECTORY) != 0)
	// Find with the package name
	if (find_package_name (filename, df->get_name ()))
	  return;
    }
}

dbe_stat_t *
DbeFile::get_stat ()
{
  if (sbuf.st_atim.tv_sec == 0)
    {
      int st = check_access (get_location (false));
      if (st == F_NOT_FOUND)
	return NULL;
    }
  return &sbuf;
}

bool
DbeFile::compare (DbeFile *df)
{
  if (df == NULL)
    return false;
  dbe_stat_t *st1 = get_stat ();
  dbe_stat_t *st2 = df->get_stat ();
  if (st1 == NULL || st2 == NULL)
    return false;
  if (st1->st_size != st2->st_size)
    return false;
  if (st1->st_mtim.tv_sec != st2->st_mtim.tv_sec)
    return false;
  return true;
}
