module otya.smilebasic.program;
import otya.smilebasic.error;
import std.range;
import std.container;
import std.algorithm;
import std.algorithm;

unittest
{
    Slot slot;
    slot.init();
    slot.currentLine = 1;
    assert(slot.get() == "\n");
    slot.currentLine = 1;
    slot.set("1");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    assert(slot.get() == "1\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 2;
    slot.set("2");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    assert(slot.get() == "1\n");
    assert(slot.get() == "2\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    slot.set("A");
    assert(slot.get() == "2\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    assert(slot.get() == "A\n");
    assert(slot.get() == "2\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 2;
    slot.set("I\nJ");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");

    //PRGINS
    slot.currentLine = 1;
    slot.insert("3", false);
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    assert(slot.get() == "3\n");
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");

    slot.currentLine = 1;
    slot.insert("4", true);
    assert(slot.get() == "4\n");
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    assert(slot.get() == "3\n");
    assert(slot.get() == "4\n");
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");

    slot.currentLine = 2;
    slot.insert("5", false);
    assert(slot.get() == "4\n");
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    assert(slot.get() == "3\n");
    assert(slot.get() == "5\n");
    assert(slot.get() == "4\n");
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");

    slot.currentLine = 2;
    slot.insert("6", true);
    assert(slot.get() == "6\n");
    assert(slot.get() == "4\n");
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");
    slot.currentLine = 1;
    assert(slot.get() == "3\n");
    assert(slot.get() == "5\n");
    assert(slot.get() == "6\n");
    assert(slot.get() == "4\n");
    assert(slot.get() == "A\n");
    assert(slot.get() == "I\n");
    assert(slot.get() == "J\n");
    assert(slot.get() == "\n");
    assert(slot.get() == "");

}

struct Slot
{
    DList!wstring program;
    DList!wstring.Range range;
    void init()
    {
        program = make!(DList!wstring)(["\n"w]);
        range = program[];
    }

    void load(wstring data)
    {
        program.clear();
        foreach(l; splitter(data, "\n"))
        {
            program.insertBack(l ~ '\n');
        }
        if (program.back == "\n")
        {
            program.removeBack();
        }
        range = program[];
    }
    wchar[] text()
    {
        import std.algorithm.iteration, std.outbuffer;
        auto buffer = new OutBuffer();
        buffer.reserve(program.opSlice.map!"(a.length + 1) * 2".sum);

        foreach(line; program)
        {
            buffer.write(line);
        }
        ubyte[] progbuff = buffer.toBytes;
        wchar[] progbuff2 = (cast(wchar*)progbuff.ptr)[0..progbuff.length / 2];
        return progbuff2;
    }
    import otya.smilebasic.token;
    wstring getLine(SourceLocation loc)
    {
        import std.string;
        int i;
        foreach (line; program)
        {
            i++;
            if (i == loc.line)
                return line;
        }
        return "";
    }
    void currentLine(int line)
    {
        if (line == -1)
        {
            //range = DList!wstring.Range(program._last);
            range = program[];
            while (!range.empty)
            {
                auto old = range.save;
                range.popFront();
                if (range.empty)
                {
                    range = old;
                    break;
                }
            }
            return;
        }
        range = program[];
        range.popFrontN(line - 1);
    }
    wstring get()
    {
        if (range.empty)
        {
            return "";
        }
        auto a = range.front;
        range.popFront();
        return a;
    }
    void set(wstring v)
    {
        int i;
        foreach(l; splitter(v, "\n"))
        {
            if (!range.empty && i == 0)
            {
                auto b = range.save;
                b.popFront();
                if (b.empty)
                {
                    program.insertBefore(range, l ~ "\n");
                }
                else
                {
                    range.put(l ~ "\n");
                }
            }
            else
            {
                if (range.empty)
                {
                    program.insertAfter(range, l ~ "\n");
                }
                else
                {
                    program.insertBefore(range, l ~ "\n");
                }
            }
            i++;
        }
    }
    void insert(wstring line, bool isBack)
    {
        typeof(range) backup;
        if (isBack)
        {
            backup = range.save;
            range.popFront();
        }
        foreach(l; splitter(line, "\n"))
        {
            if (isBack)
            {
                //backup = range.save;
                program.insertBefore(range, l ~ "\n");
            }
            else
            {
                program.insertBefore(range, l ~ "\n");
            }
        }
        if (isBack)
        {
            range = backup;
            range.popFront();
        }
    }
}

class Program
{
    Slot[] slot;
    bool PRGEDITused;
    int currentSlot;
    int slotSize = 4;
    this()
    {
        slot = new Slot[5];
        foreach(ref s; slot)
        {
            s.init();
        }
    }
    void edit(int slot, int line)
    {
        PRGEDITused = true;
        this.currentSlot = slot;
        this.slot[slot].currentLine = line;
    }
    wstring get()
    {
        if (!PRGEDITused)
            throw new UsePRGEDITBeforeAnyPRGFunction("PRGGET$");
        return this.slot[currentSlot].get;
    }
    void set(wstring v)
    {
        if (!PRGEDITused)
            throw new UsePRGEDITBeforeAnyPRGFunction("PRGSET");
        this.slot[currentSlot].set = v;
    }
    void insert(wstring line, bool isBack)
    {
        if (!PRGEDITused)
            throw new UsePRGEDITBeforeAnyPRGFunction("PRGINS");
        this.slot[currentSlot].insert(line, isBack);
    }
}
