//
// This file is part of the aMule Project.
//
// Copyright (c) 2006-2011 aMule Team ( admin@amule.org / http://www.amule.org )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
//

#include "TextFile.h"
#include "Path.h"

#include <wx/textbuf.h>


//! The maximum number of chars read at once in GetNextLine
const size_t TXTBUF_SIZE = 1024;


CTextFile::CTextFile()
{
}


bool CTextFile::Open(const wxString& path, EOpenMode mode)
{
        return Open(CPath(path), mode);
}


bool CTextFile::Open(const CPath& path, EOpenMode mode)
{
        // wxFFile doesn't call close itself, but asserts instead.
        Close();

        m_mode = mode;

        if (mode == read) {
                if (path.FileExists()) {
                        m_file.Open(path.GetRaw(), wxT("r"));
                }
        } else if (mode == write) {
                m_file.Open(path.GetRaw(), wxT("w"));
        } else {
                wxFAIL;
        }

        return IsOpened();
}


CTextFile::~CTextFile()
{
}


bool CTextFile::IsOpened() const
{
        return m_file.IsOpened();
}


bool CTextFile::Eof() const
{
        // This is needed because feof will crash if the
        // underlying FILE pointer is NULL, as is the
        // case when the file is closed.
        return m_file.IsOpened() ? m_file.Eof() : true;
}


bool CTextFile::Close()
{
        return m_file.Close();
}


wxString CTextFile::GetNextLine(EReadTextFile flags, const wxMBConv& conv, bool* result)
{
        wxCHECK_MSG(m_file.IsOpened(), wxEmptyString, wxT("Trying to read from closed file."));
        wxCHECK_MSG(!m_file.Eof(), wxEmptyString, wxT("Trying to read past EOF"));
        wxCHECK_MSG((m_mode == read), wxEmptyString, wxT("Trying to read from non-readable file."));

        bool is_filtered = false;

        wxString line;
        char buffer[TXTBUF_SIZE];

        // Loop until EOF (fgets will then return NULL) or a newline is read.
        while (fgets(buffer, TXTBUF_SIZE, m_file.fp())) {
                // Filters must be first applied here to avoid unnecessary CPU usage.

                if (line.IsEmpty()) {
                        if (buffer[0] == '\0') {
                                // Empty line.
                                break;
                        } else if (flags & txtIgnoreComments) {
                                int i = 0;
                                char t = buffer[i];
                                while (t) {
                                        if ((t == ' ') || (t == '\t')) {
                                                ++i;
                                                t = buffer[i];
                                        } else {
                                                is_filtered = (buffer[i] == '#');
                                                break;
                                        }
                                }
                        }
                }

                if (!is_filtered) {
                // NB: The majority of the time spent by this function is
                //     spent converting the multibyte string to wide-char.
                line += conv.cMB2WC(buffer);

                // Remove any newlines, carriage returns, etc.
                if (line.Length() && (line.Last() == wxT('\n'))) {
                        if ((line.Length() > 1)) {
                                if (line[line.Length() - 2] == wxT('\r')) {
                                        // Carriage return + newline
                                        line.RemoveLast(2);
                                } else {
                                        // Only a newline.
                                        line.RemoveLast(1);
                                }
                        } else {
                                // Empty line
                                line.Clear();
                        }

                        // We've read an entire line.
                        break;
                }
                } else {
                        // Filtered line.
                        break;
                }
        }

        if (!is_filtered) {
                if (flags & txtStripWhitespace) {
                        line = line.Strip(wxString::both);
                }

                if ((flags & txtIgnoreEmptyLines) && line.IsEmpty()) {
                        is_filtered = true;
                }
        }

        if (result) {
                *result = !is_filtered;
        }

        return line;
}


bool CTextFile::WriteLine(const wxString& line, const wxMBConv& conv)
{
        wxCHECK_MSG(m_file.IsOpened(), false, wxT("Trying to read from closed file."));
        wxCHECK_MSG((m_mode == write), false, wxT("Trying to read from non-readable file."));

        // Ensures that use of newlines/carriage-returns matches the OS
        wxString result = wxTextBuffer::Translate(line);

        // Only add line-breaks between lines, as otherwise the number of
        // lines would grow as the result of the addition of an empty line,
        // at the end of the file.
        if (m_file.Tell() > 0) {
                result = wxTextBuffer::GetEOL() + result;
        }

        wxCharBuffer strBuffer = conv.cWC2MB(result);
        if (strBuffer) {
                const size_t length = strlen(strBuffer);

                return (m_file.Write(strBuffer, length) == length);
        }

        return false;
}


wxArrayString CTextFile::ReadLines(EReadTextFile flags, const wxMBConv& conv)
{
        wxArrayString lines;

        while (!Eof()) {
                bool result = true;

                wxString line = GetNextLine(flags, conv, &result);

                if (result) {
                        lines.Add(line);
                        }
                }


        return lines;
}


bool CTextFile::WriteLines(const wxArrayString& lines, const wxMBConv& conv)
{
        bool result = true;

        for (size_t i = 0; i < lines.GetCount(); ++i) {
                result &= WriteLine(lines[i], conv);
        }

        return result;
}