// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 Eduardo Aguiar
//
// 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, 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 <mobius/unittest.h>
#include <mobius/io/resource.h>
#include <mobius/io/file.h>
#include <mobius/io/folder.h>
#include <mobius/io/reader_impl_local.h>
#include <mobius/io/writer_impl_local.h>
#include <fcntl.h>

static void
testcase_resource ()
{
  mobius::unittest test ("mobius::io::resource");

  const std::string url1 = "file:///etc/hosts";
  const std::string url2 = "file:///not/found";
  const std::string url3 = "file:///etc/hosts/abc";
  const std::string url4 = "file:///etc";
  const std::string url5 = "invalid:///invalid/uri";

  mobius::io::resource r0;
  mobius::io::resource r1 (url1);
  mobius::io::resource r2 (url2);
  mobius::io::resource r3 (url3);
  mobius::io::resource r4 (url4);
  mobius::io::resource r5 (url5);

  // is_valid
  test.ASSERT_FALSE (r0);
  test.ASSERT_TRUE (r1);
  test.ASSERT_TRUE (r2);
  test.ASSERT_TRUE (r3);
  test.ASSERT_TRUE (r4);
  test.ASSERT_FALSE (r5);

  // is_file
  ASSERT_EXCEPTION (test, r0.is_file (), std::runtime_error);
  test.ASSERT_TRUE (r1.is_file ());
  ASSERT_EXCEPTION (test, r2.is_file (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.is_file (), std::runtime_error);
  test.ASSERT_FALSE (r4.is_file ());
  ASSERT_EXCEPTION (test, r5.is_file (), std::runtime_error);

  // is_folder
  ASSERT_EXCEPTION (test, r0.is_folder (), std::runtime_error);
  test.ASSERT_FALSE (r1.is_folder ());
  ASSERT_EXCEPTION (test, r2.is_folder (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.is_folder (), std::runtime_error);
  test.ASSERT_TRUE (r4.is_folder ());
  ASSERT_EXCEPTION (test, r5.is_folder (), std::runtime_error);

  // exists
  ASSERT_EXCEPTION (test, r0.exists (), std::runtime_error);
  test.ASSERT_TRUE (r1.exists ());
  test.ASSERT_FALSE (r2.exists ());
  test.ASSERT_FALSE (r3.exists ());
  test.ASSERT_TRUE (r4.exists ());
  ASSERT_EXCEPTION (test, r5.exists (), std::runtime_error);

  // URL
  test.ASSERT_EQUAL (r0.get_url (), std::string ());
  test.ASSERT_EQUAL (r1.get_url (), url1);
  test.ASSERT_EQUAL (r2.get_url (), url2);
  test.ASSERT_EQUAL (r3.get_url (), url3);
  test.ASSERT_EQUAL (r4.get_url (), url4);
  test.ASSERT_EQUAL (r5.get_url (), url5);

  // get_size
  ASSERT_EXCEPTION (test, r0.get_size (), std::runtime_error);
  test.ASSERT_NOT_EQUAL (r1.get_size (), 0);
  ASSERT_EXCEPTION (test, r2.get_size (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.get_size (), std::runtime_error);
  test.ASSERT_NOT_EQUAL (r4.get_size (), 0);
  ASSERT_EXCEPTION (test, r5.get_size (), std::runtime_error);

  // user_id
  ASSERT_EXCEPTION (test, r0.get_user_id (), std::runtime_error);
  test.ASSERT_EQUAL (r1.get_user_id (), 0);
  ASSERT_EXCEPTION (test, r2.get_user_id (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.get_user_id (), std::runtime_error);
  test.ASSERT_EQUAL (r4.get_user_id (), 0);
  ASSERT_EXCEPTION (test, r5.get_user_id (), std::runtime_error);

  // user_name
  ASSERT_EXCEPTION (test, r0.get_user_name (), std::runtime_error);
  test.ASSERT_EQUAL (r1.get_user_name (), "root");
  ASSERT_EXCEPTION (test, r2.get_user_name (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.get_user_name (), std::runtime_error);
  test.ASSERT_EQUAL (r4.get_user_name (), "root");
  ASSERT_EXCEPTION (test, r5.get_user_name (), std::runtime_error);

  // group_id
  ASSERT_EXCEPTION (test, r0.get_group_id (), std::runtime_error);
  test.ASSERT_EQUAL (r1.get_group_id (), 0);
  ASSERT_EXCEPTION (test, r2.get_group_id (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.get_group_id (), std::runtime_error);
  test.ASSERT_EQUAL (r4.get_group_id (), 0);
  ASSERT_EXCEPTION (test, r5.get_group_id (), std::runtime_error);

  // group_name
  ASSERT_EXCEPTION (test, r0.get_group_name (), std::runtime_error);
  test.ASSERT_EQUAL (r1.get_group_name (), "root");
  ASSERT_EXCEPTION (test, r2.get_group_name (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.get_group_name (), std::runtime_error);
  test.ASSERT_EQUAL (r4.get_group_name (), "root");
  ASSERT_EXCEPTION (test, r5.get_group_name (), std::runtime_error);

  // last access time
  ASSERT_EXCEPTION (test, r0.get_last_access_time (), std::runtime_error);
  test.ASSERT_TRUE (r1.get_last_access_time ());
  ASSERT_EXCEPTION (test, r2.get_last_access_time (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.get_last_access_time (), std::runtime_error);
  test.ASSERT_TRUE (r4.get_last_access_time ());
  ASSERT_EXCEPTION (test, r5.get_last_access_time (), std::runtime_error);

  // last modification time
  ASSERT_EXCEPTION (test, r0.get_last_modification_time (), std::runtime_error);
  test.ASSERT_TRUE (r1.get_last_modification_time ());
  ASSERT_EXCEPTION (test, r2.get_last_modification_time (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.get_last_modification_time (), std::runtime_error);
  test.ASSERT_TRUE (r4.get_last_modification_time ());
  ASSERT_EXCEPTION (test, r5.get_last_modification_time (), std::runtime_error);

  // last metadata time
  ASSERT_EXCEPTION (test, r0.get_last_metadata_time (), std::runtime_error);
  test.ASSERT_TRUE (r1.get_last_metadata_time ());
  ASSERT_EXCEPTION (test, r2.get_last_metadata_time (), std::runtime_error);
  ASSERT_EXCEPTION (test, r3.get_last_metadata_time (), std::runtime_error);
  test.ASSERT_TRUE (r4.get_last_metadata_time ());
  ASSERT_EXCEPTION (test, r5.get_last_metadata_time (), std::runtime_error);

  test.end ();
}

static void
testcase_file ()
{
  mobius::unittest test ("mobius::io::file");
  mobius::io::file f0;
  mobius::io::file f1 ("file:///tmp/unittest.dat");
  mobius::io::file f2 ("invalid:///invalid/uri");

  // write and read back
  ASSERT_EXCEPTION (test, f0.new_writer (), std::runtime_error);
  ASSERT_EXCEPTION (test, f2.new_writer (), std::runtime_error);

  {
    auto writer = f1.new_writer ();
    writer.write ("abc");
    writer.write (mobius::bytearray {0x0d,0x0a});
  }

  ASSERT_EXCEPTION (test, f0.new_reader (), std::runtime_error);
  ASSERT_EXCEPTION (test, f2.new_reader (), std::runtime_error);

  {
    auto reader = f1.new_reader ();
    mobius::bytearray b1 = reader.read (3);
    mobius::bytearray b2 = reader.read (2);

    test.ASSERT_EQUAL (b1, "abc");
    test.ASSERT_EQUAL (b2, "\r\n");
  }

  // append to the end and read back
  {
    auto writer = f1.new_writer (false);
    writer.write ("def");
    writer.write (mobius::bytearray {0x0d,0x0a});
  }

  {
    auto reader = f1.new_reader ();

    test.ASSERT_EQUAL (reader.read (3), "abc");
    test.ASSERT_EQUAL (reader.read (2), "\r\n");
    test.ASSERT_EQUAL (reader.read (3), "def");
    test.ASSERT_EQUAL (reader.read (2), "\r\n");
  }

  // built from resource
  mobius::io::resource r0;
  mobius::io::resource r1 ("file:///etc/hosts");
  mobius::io::resource r2 ("invalid:///invalid/xxx");

  mobius::io::file fr0 (r0);
  mobius::io::file fr1 (r1);
  mobius::io::file fr2 (r2);

  ASSERT_EXCEPTION (test, fr0.new_reader (), std::runtime_error);
  ASSERT_EXCEPTION (test, fr2.new_reader (), std::runtime_error);
  auto reader = fr1.new_reader ();

  test.end ();
}

static void
testcase_folder ()
{
  mobius::unittest test ("mobius::io::folder");
  mobius::io::folder f0;
  mobius::io::folder f1 ("file:///etc");
  mobius::io::folder f2 ("invalid:///invalid/uri");

  ASSERT_EXCEPTION (test, f0.begin (), std::runtime_error);
  ASSERT_EXCEPTION (test, f2.begin (), std::runtime_error);

  // built from resource
  mobius::io::resource r0;
  mobius::io::resource r1 ("file:///etc");
  mobius::io::resource r2 ("invalid:///invalid/xxx");

  mobius::io::folder fr0 (r0);
  mobius::io::folder fr1 (r1);
  mobius::io::folder fr2 (r2);

  ASSERT_EXCEPTION (test, fr0.begin (), std::runtime_error);
  ASSERT_EXCEPTION (test, fr2.begin (), std::runtime_error);
  auto it1 = fr1.begin ();

  // test iterator
  auto iter = f1.begin ();
  auto r = *iter;

  test.ASSERT_TRUE (r);
  test.ASSERT_NOT_EQUAL (r.get_url (), std::string ());
  test.ASSERT_TRUE (r.exists ());

  test.end ();
}

static void
testcase_reader ()
{
  mobius::unittest test ("mobius::io::reader");
  mobius::io::file f ("file:///dev/zero");
  auto reader = f.new_reader ();

  // capabilities
  test.ASSERT_TRUE (reader.is_seekable ());
  test.ASSERT_TRUE (reader.is_rewindable ());
  test.ASSERT_TRUE (reader.is_sizeable ());

  // initial values
  test.ASSERT_EQUAL (reader.get_size (), 0);
  test.ASSERT_EQUAL (reader.eof (), false);
  test.ASSERT_EQUAL (reader.tell (), 0);

  // read bytes
  mobius::bytearray data = reader.read (16);
  test.ASSERT_EQUAL (data.size (), 16);
  test.ASSERT_EQUAL (data[0], 0);
  test.ASSERT_EQUAL (data[15], 0);

  // values
  test.ASSERT_EQUAL (reader.eof (), false);
  test.ASSERT_EQUAL (reader.tell (), 16);

  // seek
  reader.seek (100);
  test.ASSERT_EQUAL (reader.tell (), 100);

  // read more bytes
  data = reader.read (16);
  test.ASSERT_EQUAL (data.size (), 16);
  test.ASSERT_EQUAL (data[0], 0);
  test.ASSERT_EQUAL (data[15], 0);

  // values
  test.ASSERT_EQUAL (reader.eof (), false);
  test.ASSERT_EQUAL (reader.tell (), 116);

  test.end ();
}

static void
testcase_writer ()
{
  mobius::unittest test ("mobius::io::writer");
  mobius::io::file f ("file:///tmp/unittest.dat");

  {
    auto writer = f.new_writer ();
    writer.write ("abc");
    writer.write (mobius::bytearray {0x0d,0x0a});
  }

  auto reader = f.new_reader ();
  mobius::bytearray b1 = reader.read (3);
  mobius::bytearray b2 = reader.read (2);

  test.ASSERT_EQUAL (b1, "abc");
  test.ASSERT_EQUAL (b2, "\r\n");

  test.end ();
}

void
unittest_io ()
{
  testcase_resource ();
  testcase_file ();
  testcase_folder ();
  testcase_reader ();
  testcase_writer ();
}
