/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <MvList.hpp>

#include <iostream>

#include <ctype.h>

//
//  CONSTRUCTORS
//

MvList ::MvList(int protect)
{
    setListTypes(SequentialList, NoSortBy, NoSortType, NoCase);
    _first = _last = nullptr;
    _n = 0;
    _protect = protect;
    _curpos = -1;
}

MvList ::MvList(ListType lst, ListSortBy sb, ListSortType st, ListCase lc, int protect, MvElement* delem)
{
    setListTypes(lst, sb, st, lc);
    if (delem)
        insertLastPrivate(delem);
    else {
        _first = _last = nullptr;
        _n = 0;
    }
    first();
    _protect = protect;
}

//
// AUXILIARY FUNCTIONS
//

void MvList ::setListTypes(ListType lst, ListSortBy sb, ListSortType st, ListCase c)
{
    _list_type = lst;
    _list_case = c;
    _current = nullptr;

    if (_list_type == SortedList) {
        switch (sb) {
            case SortListByName:
                _sort_by = sb;
                break;

            case SortListByCode:
                _sort_by = sb;
                break;

            default:
                break;
        }
        _sort_type = st;
    }
    else {
        _sort_by = NoSortBy;
        _sort_type = NoSortType;
    }
}


void MvList::removeAll()
{
    MvLink *curr, *outro;

    curr = _first;
    for (int i = 0; i < _n; i++) {
        outro = curr->_next;
        MvElement* elem = curr->_self;

        delete curr;
        curr = outro;

        if ((!_protect) && elem->count() == 0)
            delete elem;
    }

    _n = 0;
    _first = _last = nullptr;
}

void MvList::print(const char* titulo)
{
    int i;
    MvLink* curr;

    if (titulo)
        printf("%s\n", titulo);
    for (curr = _first, i = 0; curr; curr = curr->_next, i++) {
        printf("    %d) ", i);
        curr->_self->print();
    }
    if (i != _n)
        printf("MvList::print - Error in the list\n");
}


//
// EDIT LIST
//

void MvList::insertLast(MvElement* v)
{
    if (v == nullptr)
        return;

    if (_list_type == SortedList) {
        std::cout << "Error in MvList.insertLast: invalid function for sorted list";
        return;
    }

    insertLastPrivate(v);
}

void MvList::insertLastPrivate(MvElement* v)
{
    MvLink* last_last;

    if (v == nullptr)
        return;

    _n++;
    last_last = _last;

    // create new element

    _last = new MvLink(v, last_last, nullptr);

    if (_n == 1)
        _first = _last;
    else
        last_last->_next = _last;
}

void MvList::insert(int posit, MvElement* new_elem)
{
    if (_list_type == SortedList) {
        std::cout << "Error in MvList.insert: invalid function for sorted list\n";
        return;
    }

    insertPrivate(posit, new_elem);
}

void MvList::insertPrivate(int posit, MvElement* new_elem)
{
    if (posit < 0 || posit > _n) {
        std::cout << "Error in MvList.insert: invalid position "
                  << posit << "\n";
        return;
    }

    if (new_elem == nullptr)
        return;

    // Insert last element

    if (posit == _n) {
        insertLastPrivate(new_elem);
        return;
    }

    // It is not the last element...

    else {
        int i;
        MvLink *post, *ant = _first;
        MvLink* new_entry;

        if (posit == 0) {
            post = _first;

            new_entry = new MvLink(new_elem, nullptr, post);

            _first = new_entry;

            post->_previous = new_entry;
        }
        else {
            for (i = 1; i < posit; i++)
                ant = ant->_next;
            post = ant->_next;

            new_entry = new MvLink(new_elem, ant, post);

            ant->_next = new_entry;

            post->_previous = new_entry;
        }
    }
    _n++;
}

MvElement* MvList ::insertByType(MvElement* newelem)
{
    MvElement* elem;
    int same = 0;

    if (newelem == nullptr)
        return nullptr;

    if (_list_type != SortedList) {
        std::cout << "Error in MvList.insertByType: invalid function for sequential list ";
        return newelem;
    }

    if (isEmpty()) {
        insertFirstPrivate(newelem);
        return newelem;
    }

    if (_list_type == SortedList) {
        if (_sort_by == SortListByCode) {
            elem = findNearestCode(newelem->_elem_code);
            if (elem && elem->_elem_code == newelem->_elem_code)
                same = 1;
        }
        else if (_sort_by == SortListByName) {
            elem = findNearestName(newelem->_elem_name);
            if (elem && !strcmp(newelem->_elem_name, elem->_elem_name))
                same = 1;
        }

        // If singular sort, return nothing if we cannot insert
        // Program can use curr which points to the right place

        if (_sort_type == SortListSingular && same)
            return nullptr;
    }

    // Insert before the current item or last

    if (_current)
        insertPrivate(_curpos, newelem);
    else
        insertLastPrivate(newelem);

    return newelem;
}

int MvList::remove(int posit)
{
    MvElement* elem = extract(posit);
    if (!elem)
        return 0;
    if ((!_protect) && elem->count() == 0)
        delete elem;

    return 1;
}

MvElement* MvList::extract(int posit)
{
    int i;
    MvLink *tokill, *tmp;

    if (posit == -1)
        return nullptr;

    if (_n == 0) {
        std::cout << "Error in MvList.out: empty list"
                  << "\n";
        return nullptr;
    }

    if (posit < 0 || posit >= _n) {
        std::cout << "Error in MvList.out: invalid position "
                  << posit << "\n";
        return nullptr;
    }

    if (posit == 0) {
        tokill = _first;
        if (_n == 1)
            _first = _last = nullptr;  // Last element deleted
        else {
            _first = _first->_next;
            _first->_previous = nullptr;
        }

        if (_n == 2)
            _last = _first;
    }

    else if (posit == _n - 1) {
        tokill = _last;
        _last = _last->_previous;
        _last->_next = nullptr;
    }
    else {
        tokill = _first;
        for (i = 0; i < posit; i++)
            tokill = tokill->_next;
        tmp = tokill->_previous;
        tmp->_next = tokill->_next;
        tmp = tokill->_next;
        tmp->_previous = tokill->_previous;
    }

    _n--;

    /* if removing _current reset */
    if (tokill == _current) {
        _current = _current->_next;
        if (!_current)
            _curpos = -1;
    }

    MvElement* elem = tokill->_self;
    delete tokill;
    return elem;
}
void MvList ::sort()
{
    // execute sort algorithm

    MvLink *current = _first, *next;
    int i;
    for (i = 1; i <= _n; i++) {
        next = current->_next;
        sort(i, current);
        current = next;
    }

    // update _curpos and _last

    _curpos = (_first ? 0 : -1);
    current = _last = _first;

    if (!_last)
        return;
    for (i = 1; i < _n; i++)
        current = current->_next;
    current->_next = nullptr;
    _last = current;
    _first->_previous = nullptr;
}

// Bubble sort. This function will finish
// with a sorted list of size pos+2

/* void MvList :: sort(int pos, MvLink* toinsert)
{
    if (pos <= 1) return;

    MvLink *current = _first, *previous = nullptr;
    for (int i = 1; i < pos; i++)
    {
        if (compare(toinsert, current) < 0)
        {
            if (current == _first)
            {
                _first = toinsert;
                toinsert->_next = current;
            }
            else
            {
                previous->_next = toinsert;
                toinsert->_next = current;
            }
            break;

        }
        toinsert->_previous = previous;
        current->_previous = toinsert;
        previous = current;
        current = current->_next;
    }
}
*/


void MvList ::sort(int pos, MvLink* toinsert)
{
    if (pos <= 1)
        return;

    MvLink* current = _first;
    MvLink *cnext = nullptr,
           *tprev = nullptr;

    if (toinsert == nullptr)
        return;

    for (int i = 1; i < pos; i++) {
        if (compare(toinsert, current) < 0) {
            if (current == _first)
                _first = toinsert;

            cnext = current->_next;
            tprev = toinsert->_previous;

            toinsert->_previous = current->_previous;
            current->_next = toinsert->_next;
            toinsert->_next = (cnext == toinsert) ? (current) : (cnext);
            current->_previous = (tprev == current) ? (toinsert) : (tprev);

            if (cnext != nullptr && cnext != toinsert)
                cnext->_previous = toinsert;
            if (tprev != nullptr && tprev != current)
                tprev->_next = current;
            if (current->_next != nullptr)
                current->_next->_previous = current;
            if (toinsert->_previous != nullptr)
                toinsert->_previous->_next = toinsert;
            break;
        }
        current = current->_next;
    }
}


//
// INQUIRIES
//

MvElement* MvList::get(int posit)
{
    MvElement* elem;
    int i = 0;

    if (_n == 0 || (posit < 0 || posit >= _n))
        return nullptr;

    for (elem = first(), i = 0; elem; elem = next(), i++)
        if (posit == i)
            return elem;

    return nullptr;
}

MvElement* MvList::first()
{
    _current = _first;
    _curpos = 0;
    return (_current ? _current->_self : nullptr);
}

MvElement* MvList::next()
{
    if (_current == nullptr)
        return (nullptr);

    _current = _current->_next;
    _curpos++;
    return (_current ? _current->_self : nullptr);
}

MvElement* MvList::previous()
{
    if (_current == nullptr)
        return (nullptr);

    _current = _current->_previous;
    _curpos--;
    return (_current ? _current->_self : nullptr);
}

int MvList ::positionOf(MvElement* elem)
{
    MvElement* curr;
    int i;

    for (curr = first(), i = 0; curr; curr = next(), i++)
        if (elem == curr)
            break;

    return ((elem == curr) ? i : -1);
}

MvElement* MvList ::findByCode(int code)
{
    MvElement* elem;

    if (!_first)
        return nullptr;

    for (elem = first(); elem; elem = next()) {
        if (elem->_elem_code == code)
            return elem;
        if (_sort_by == SortListByCode && elem->_elem_code > code)
            return nullptr;
    }
    return nullptr;
}

MvElement* MvList ::findByName(const char* name)
{
    if (!name)
        return nullptr;
    if (!_first)
        return nullptr;

    MvElement* elem = nullptr;
    char* n = new char[strlen(name) + 1];
    strcpy(n, name);
    convertCase(n);
    for (elem = first(); elem; elem = next()) {
        if (!elem->_elem_name)
            continue;
        int cmp = strcmp(n, elem->_elem_name);

        if (!cmp)
            break;
        if (_sort_by == SortListByName && cmp < 0) {
            elem = nullptr;
            break;
        }
    }
    delete[] n;
    return elem;
}

MvElement* MvList ::findNearestCode(int code)
{
    MvElement* elem;

    if (!_first)
        return nullptr;
    if (_sort_by != SortListByCode)
        return nullptr;

    for (elem = first(); elem; elem = next()) {
        if (elem->_elem_code > code)
            return elem;
    }
    return nullptr;
}

MvElement* MvList ::findNearestName(const char* name)
{
    if (!name)
        return nullptr;
    if (!_first)
        return nullptr;
    if (_sort_by != SortListByName)
        return nullptr;

    MvElement* elem = nullptr;
    char* n = new char[strlen(name) + 1];
    strcpy(n, name);
    convertCase(n);

    for (elem = first(); elem; elem = next()) {
        if (strcmp(n, elem->_elem_name) < 0)
            break;
    }
    delete[] n;
    return elem;
}

int MvList ::compare(MvLink* link1, MvLink* link2)
{
    if (link1 == nullptr) {
        if (link2 == nullptr)
            return 0;
        else
            return -1;
    }
    else {
        if (link2 == nullptr)
            return 1;
    }
    if (_list_type != SortedList || _sort_by == SortListByCode) {
        int code1 = link1->_self->getElemCode();
        int code2 = link2->_self->getElemCode();
        return (code1 == code2 ? 0 : (code1 < code2 ? -1 : 1));
    }
    else
        return strcmp(link1->_self->getElemName(), link2->_self->getElemName());
}

// REPLACE, REMOVE FROM LIST

// Replace current link
void MvList ::replaceCurr(MvElement* newelem)
{
    if (remove(_curpos))
        insertPrivate(_curpos, newelem);
}

// Remove	- but don't free - current link
// 			- return ptr to link for delete

void MvList ::removeCurr()
{
    remove(_curpos);
}


void MvList ::removeByCode(int code)
{
    MvElement* elem = findByCode(code);
    if (elem)
        removeCurr();
}

void MvList ::removeByName(const char* name)
{
    MvElement* elem = findByName(name);
    if (elem)
        removeCurr();
}


void MvList ::convertCase(char* data)
{
    size_t i;

    if (data == nullptr)
        return;

    switch (_list_case) {
        case NoCase:
            break;

        case LowerCase:
            for (i = 0; i < strlen(data); i++)
                data[i] = tolower(data[i]);
            break;

        case UpperCase:
            for (i = 0; i < strlen(data); i++)
                data[i] = toupper(data[i]);
            break;

        default:
            break;
    }
}
