/* GNU moe - My Own Editor
   Copyright (C) 2005-2025 Antonio Diaz Diaz.

   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, see <http://www.gnu.org/licenses/>.
*/

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/stat.h>

#include "buffer.h"
#include "buffer_handle.h"
#include "block.h"
#include "rc.h"
#include "screen.h"


namespace Bufhandle_vector {

int first_;				// index of first element
int last_visited_;			// absolute index for last_visited
std::vector< Buffer_handle > data;


// Save modified non-empty buffers if editor dies
//
void emergency_save()
  {
  if( !handles() ) return;
  int i = 0;
  for( ; i < handles(); ++i )
    if( data[i].buffer().precious() ) break;
  if( i >= handles() ) return;			// nothing to save

  const std::string dead_name( "DEADMOE" );
  RC::editor_options().backup = false;
  FILE * f = std::fopen( dead_name.c_str(), "a" );
  if( !f ) return;
  const time_t t = std::time( 0 );
  std::fprintf( f, "\n*** Modified files in Moe when it aborted on %s",
                std::ctime( &t ) );
  std::fflush( f );
  for( i = 0; i < handles(); ++i )
    if( data[i].buffer().precious() )
      {
      std::fprintf( f, "\n*** File '%s'\n", data[i].buffer().nename().c_str() );
      std::fflush( f );
      data[i].buffer().save( &dead_name, true, true );
      // save buffer only once even if it has multiple handles
      data[i].buffer().mark_as_unmodified();
      }
  std::fclose( f );
  }


/* 'namep' must be 0 or "-" for stdin to be read into buffer.
   A non-null but empty 'namep' ("") creates an empty unnamed buffer.
*/
int add_handle( const char * const namep, int i, const long line,
                const long col, const bool read_only = false )
  {
  Buffer * bufp = new Buffer( namep, read_only );
  RC::apply_all_file_options( *bufp );
  if( !bufp->options.auto_indent && !bufp->name().empty() && !bufp->empty() )
    {
    struct stat st;	// load executable files (scripts) with auto_indent on
    if( stat( bufp->name().c_str(), &st ) == 0 &&
        ( st.st_mode & ( S_IXUSR | S_IXGRP | S_IXOTH ) ) != 0 )
      bufp->options.auto_indent = true;
    }

  if( i < 0 ) i = handles();
  else
    {
    i = absolute_index( i );
    if( i == 0 ) i = handles();
    else { if( i <= first_ ) ++first_;
           if( i <= last_visited_ ) ++last_visited_; }
    }
  data.insert( data.begin() + i, Buffer_handle( *bufp, line, col ) );
  return circular_index( i );
  }

} // end namespace Bufhandle_vector


void Bufhandle_vector::init()
  { first_ = 0; last_visited_ = 0; std::atexit( emergency_save ); }


const char * Bufhandle_vector::save_all_named()
  {
  int count = 0;
  bool all = true;

  for( int i = 0; i < handles(); ++i )
    {
    const Buffer & buffer = data[i].buffer();
    if( buffer.modified() && buffer.name().size() )
      {
      try { if( buffer.save() ) ++count; else all = false; }
      catch( Buffer::Error & ) { all = false; }
      }
    }
  Screen::show_status_lines();
  int bufsize = 0;
  char * const buf = Screen::msg_line_buf( &bufsize );
  snprintf( buf, bufsize, "%s%s named buffer%s saved", all ? "" : "Only ",
            format_num3( count ), ( count == 1 ) ? "" : "s" );
  return buf;
  }


const char * Bufhandle_vector::show_status()
  {
  int bcount = 0, mcount = 0, ucount = 0;

  for( int i = 0; i < handles(); ++i ) data[i].buffer().handnum = i;
  for( int i = 0; i < handles(); ++i )
    if( data[i].buffer().handnum == i )	// last handle for this buffer
      {
      ++bcount;				// buffer counter
      if( data[i].buffer().modified() ) ++mcount;
      if( data[i].buffer().name().empty() ) ++ucount;
      }

  Screen::show_status_lines();
  if( bcount == 1 ) return "** This is the only buffer being edited **";
  int bufsize = 0;
  char * const buf = Screen::msg_line_buf( &bufsize );
  snprintf( buf, bufsize,
            "**  %s modified and  %s unnamed out of  %s total buffers **",
            format_num3( mcount ), format_num3( ucount ), format_num3( bcount ) );
  return buf;
  }


int Bufhandle_vector::absolute_index( const int i )
  { return ( i + first_ ) % handles(); }


int Bufhandle_vector::circular_index( const int ai )
  { return ( ai + handles() - first_ ) % handles(); }


Buffer_handle & Bufhandle_vector::handle( const int i )
  { return data[absolute_index(i)]; }


int Bufhandle_vector::handles() { return data.size(); }


int Bufhandle_vector::handles( const Buffer & buffer )
  {
  int c = 0;
  for( int i = 0; i < handles(); ++i ) if( &buffer == &data[i].buffer() ) ++c;
  return c;
  }


int Bufhandle_vector::last_visited( const int i )
  {
  const int ci = circular_index( last_visited_ );
  last_visited_ = absolute_index( i );
  return ci;
  }


void Bufhandle_vector::next()
  { ++first_; first_ %= handles(); }


void Bufhandle_vector::prev()
  { --first_; first_ += handles(); first_ %= handles(); }


void Bufhandle_vector::set_first( const int ai )
  { first_ = ai % handles(); }


/* An empty 'name' ("") creates an empty unnamed buffer.
   'name' is std::string because it compares faster than c-style strings.
   'prev_handles' limits comparisons to previously existing handles when
   adding handles recursively, which decreases startup time.
*/
int Bufhandle_vector::find_or_add_handle( const std::string & name,
              int i, const long line, const long col, const bool read_only,
              const int prev_handles )
  {
  if( name.size() )
    {
    const int n = ( prev_handles >= 0 && prev_handles <= handles() ) ?
                  prev_handles : handles();
    for( int j = 0; j < n; ++j )
      if( name == data[j].buffer().name() )
        return circular_index( j );
    }
  return add_handle( name.c_str(), i, line, col, read_only );
  }


// Add unnamed buffer if no files given or stdin is not a tty
void Bufhandle_vector::add_handle_if_pending_input_or_empty( const long line,
                                                             const long col )
  {
  if( !handles() || !isatty( fileno( stdin ) ) )
    add_handle( 0, -1, line, col );
  }


void Bufhandle_vector::duplicate_handle( int i )
  {
  i = absolute_index( i );
  if( i < first_ ) ++first_;
  if( i < last_visited_ ) ++last_visited_;
  data.insert( data.begin() + i + 1, data[i] );
  }


bool Bufhandle_vector::delete_handle( int i )
  {
  if( i < 0 || i >= handles() ) return false;
  else i = absolute_index( i );
  Buffer & buffer = data[i].buffer();
  int j;
  for( j = 0; j < handles(); ++j )
    if( j != i && &buffer == &data[j].buffer() ) break;
  if( j >= handles() )			// last handle for this buffer
    {
    if( Block::bufferp() == &buffer ) Block::reset();
    delete &buffer;
    }
  data.erase( data.begin() + i );
  if( i <= first_ && handles() ) prev();
  if( i <= last_visited_ && handles() )
    { --last_visited_; last_visited_ += handles(); last_visited_ %= handles(); }
  return true;
  }


void Bufhandle_vector::delete_all_handles()
  {
  for( int i = 0; i < handles(); ++i ) data[i].buffer().handnum = i;
  for( int i = handles() - 1; i >= 0; --i )
    if( data[i].buffer().handnum == i )	// last handle for this buffer
      delete &data[i].buffer();
  data.clear(); first_ = 0; last_visited_ = 0; Block::reset();
  }
