#include "lang/all.h"

#include "io/randomaccessfile.h"
#include "io/file.h"
#include "io/ioexception.h"

// Tail.cpp Copyright (C) Christian von Krogh 

void out(const String & s)
{
    System.out.println(s);
}

void showUsageAndExit()
{
    out("");
    out("Tail 1.01 Copyright (C) 2002 - 2012 Christian von Krogh.  FREEWARE!  Use at your own risk.");
    out("");
    out("Usage:");
    out("");
    out("Tail [[+][-]number[l|c[f]] file");
    out("   l: (line) unit is lines (default)");
    out("   c: (char) unit is character");
    out("   number: how many units to skip from end or start before starting to dump file contents");
    out("   +: skip given number units from the start");
    out("   -: go back given number of units from the end");
    out("   f: (follow) keeps displaying units from the file until killed");
    out("   file: filename");
    out("");
    out("Default values: skip direction: -, unit:l, number:10");
    out("");
    out("Standard in is not yet supported, so file must be supplied.");
    out("");
    out("Examples:");
    out("");
    out("Show the last 15 lines of the file log.txt, then exit");
    out("   Tail -15l log.txt");
    out("");
    out("Show the last 15 lines of the file log.txt, then continue to watch the file for changes.");
    out("   Tail -15lf log.txt");
    out("");
    out("Show the last 10 lines of the file log.txt, then exit.");
    out("   Tail log.txt");
    out("");
    exit(1);
}

enum StartingPoint
{
    StartFromTheBeginning,
    StartFromTheEnd
};

enum SkipUnit
{
    SkipLines,
    SkipChars
};

struct Options
{
    Options() 
        : fileName()
        , startingPoint(StartFromTheEnd)
        , skipAmount(10)
        , skipUnit(SkipLines)
        , follow(false)
        , errorMessage()
    {}
    String fileName;
    StartingPoint startingPoint;
    jlong skipAmount; 
    SkipUnit skipUnit;
    bool follow;
    String errorMessage;
};

Options parseCommandLine(int argc, char ** args)
{
    Options options;
    String switches;

    switch(argc)
    {
    case 2:
        switches = "-10l";
        options.fileName = args[1];
        break;
    case 3:
        switches = args[1];
        options.fileName = args[2];
        break;
    default:
        options.errorMessage = "Please consult the documentation.";
        break;        
    }
    if (!options.errorMessage.isEmpty())
        return options;

    int pos = 0;
    if (switches.startsWith("+") || switches.startsWith("-"))
    {
        options.startingPoint = switches.startsWith("+") ? StartFromTheBeginning : StartFromTheEnd;
        pos++;
    }
    
    const int startPos = pos;
    while (pos < switches.length() 
        && switches.charAt(pos) <= '9' 
        && switches.charAt(pos) >= '0')
    {
        pos++;
    }
    if (pos > startPos)
        options.skipAmount = Integer::parseInt(switches.substring(startPos, pos - startPos));
    
    for (int i = pos; i < switches.length(); i++)
    {
        char c = switches.charAt(i);
        switch (c)
        {
        case 'f':
            options.follow = true;
            break;
        case 'l':
            options.skipUnit = SkipLines;
            break;
        case 'c':
            options.skipUnit = SkipChars;
            break;
        default:
            options.errorMessage = String("Unknown switch: ") + c;
            break;
        }
    }
    return options;
}

void skipToInitialPosition(RandomAccessFile & f, const Options & options)
{
    if (options.skipUnit == SkipChars)
    {
        if (options.startingPoint == StartFromTheBeginning)
            f.seek(Math.min(f.length() - 1, options.skipAmount));
        else
            f.seek(Math.max(jlong(0), f.length() - options.skipAmount));
    }
    else
    {
        int numBytesRead;
        int skippedLines = 0;
        if (options.startingPoint == StartFromTheBeginning)
        {
            f.seek(0);
            while (skippedLines < options.skipAmount)
            {
                f.readLine(numBytesRead);
                skippedLines++;
            }
        }
        else
        {
            f.seek(f.length() - 1);
            while (skippedLines < options.skipAmount)
            {
                jlong filePos = f.currentPosition();
                // Seek backwards until start of file, or next \n
                byte c = 0;
                while (filePos > 0 && (c = f.readByte()) != '\n')
                {
                    --filePos;
                    f.seek(filePos);
                }
                if (c == '\n')
                {
                    --filePos;
                    f.seek(filePos);
                }
                skippedLines++;
            }
        }
    }
}

void printContentsUntilEOF(RandomAccessFile & f, const Options & options)
{
    String line;
    int numBytesRead;
    while (!(line = f.readLine(numBytesRead)).isEmpty() && numBytesRead > 0)
        System.out.print(line);
}

void monitorChanges(RandomAccessFile & f)
{
    jlong pos = f.currentPosition();
    String soFar, line;
    int numBytesRead;
    
    while (true)
    {
        Thread::sleep(500);
        const jlong fileSize = f.length();
        
        if (fileSize > pos)
        {
            // File has been augmented since last time
            while (!(line = f.readLine(numBytesRead)).isEmpty() && numBytesRead > 0)
            {
                soFar.append(line);
                if (soFar.endsWith("\n"))
                {
                    System.out.print(soFar);
                    soFar.truncate();
                }
            }
            pos = fileSize;
        }
    }
    
}

int main(int argc, char ** args)
{
    const Options options = parseCommandLine(argc, args);

    if (!options.errorMessage.isEmpty())
    {
        out(options.errorMessage);
        showUsageAndExit();
    }

    if (!File::exists(options.fileName))
    {
        out(String("File not found: ") + options.fileName);
        exit(1);
    }
    
    try
    {
        RandomAccessFile f(options.fileName, "r");
        skipToInitialPosition(f, options);
        printContentsUntilEOF(f, options);
        if (options.follow)
            monitorChanges(f);        
        return 0;
    }
    catch (const IOException & e)
    {
        out(e.message());
        exit(1);
    }
}


/**
*
* Unix variant: usr/xpg4/bin/tail [ ± number [ l | b | c ] [ f ]] [ file ] 
* -b 
*   Units of blocks. 
* -c 
*   Units of bytes. 
* -f 
*   Follow. If the input-file is not a pipe, the program will not terminate after the line of the input-file has been copied, but will enter an endless loop, wherein it sleeps for a second and then attempts to read and copy further records from the input-file. Thus it may be used to monitor the growth of a file that is being written by some other process. 
* -l 
*   Units of lines. 
* -r 
*   Reverse. Copies lines from the specified starting point in the file in reverse order. The default for r is to print the entire file in reverse order. 
*/

