#include "campservice.h"
#include <settingsservice.h>
#include <seasonservice.h>
#include <numberseriesservice.h>
#include "campshopitem.h"
#include "campseller.h"
#include <math.h>

#ifdef _WIN32
double round(double value) { return value < 0 ? -std::floor(0.5 - value) : std::floor(0.5 + value); }
#endif

CampService::CampService()
{
    m_pluginId = "CAMP";
    m_seller = new CampSeller(this);
}

void CampService::addPerson(CampDataPtr data, AddressbookDataPtr address)
{
    AddressItemPtr addrItem(new AddressItem);

    addrItem->setAdbItem(address);
    addrItem->setAddress(address->toString());
    addrItem->setFirstName(address->firstName());
    addrItem->setLastName(address->lastName());
    addrItem->setCampData(data);

    if (data->people().isEmpty())
    {
        setOwner(data, addrItem);
    }

    data->addPerson(addrItem);
}

void CampService::addService(CampDataPtr data, AccServicePtr service)
{
    ServiceItemPtr serviceItem(new ServiceItem);

    serviceItem->setName(service->accServiceName());
    serviceItem->setCode(service->accServiceCode());
    serviceItem->setPrice(service->price());
    serviceItem->setSalePossible(service->salePossible());
    serviceItem->setType(service->serviceType());

    data->addServiceItem(serviceItem);
}

void CampService::addService(CampDataPtr data, AccServicePtr service, QDecDouble price, QString description)
{
    ServiceItemPtr item = addServiceInt(data, service);
    item->setPrice(price);
    item->setDescription(description);
}

void CampService::setOwner(CampDataPtr data, AddressItemPtr person)
{
    foreach (AddressItemPtr p, data->people()) {
        p->setOwner(false);
    }

    person->setOwner(true);

    data->setOwnerFirstame(person->firstName());
    data->setOwnerLastname(person->lastName());
    data->setOwnerAddress(person->address());
}

CampDataPtr CampService::create()
{
    CampDataPtr data(new CampData);
    data->setStart(QDate::currentDate());
    data->setEnd(QDate::currentDate());

    return data;
}

void CampService::calculate(CampDataPtr data)
{
    SettingsService srv("CAMP");
    m_settings = srv.loadSettings<CampSettings>();

    calcServices(data);
    calcPeople(data);
    calcPrice(data);
}

void CampService::saveCamp(CampDataPtr data)
{
    if (!checkPermission(PERM_ADD))
    {
        return;
    }

    SeasonService seasonSrv;
    SeasonPtr season = seasonSrv.active();
    data->setSeason(season);

    Transaction tr;
    try
    {
        odb::database *db = Context::instance().db();

        NumberSeriesService numSrv;
        data->setNumSer(numSrv.nextStrForPlugin("CAMP"));

        addDateAndUser(data, true);

        db->persist(data);

        foreach (ServiceItemPtr item, data->services()) {
            item->setCampData(data.toWeakRef());
            db->persist(item);
        }

        foreach (AddressItemPtr item, data->people()) {
            item->setCampData(data.toWeakRef());
            db->persist(item);
        }

        tr.commit();
    }
    catch (const odb::exception &ex)
    {
        emit dbError(ex.what());
        emit dbErrorInsert(ex.what());
        return;
    }
}

void CampService::updateCamp(CampDataPtr data)
{
    if (!checkPermission(PERM_EDIT))
    {
        return;
    }

    Transaction tr;

    try
    {
        odb::database *db = Context::instance().db();

        db->execute(QString("DELETE FROM ServiceItem WHERE campData = %1").arg(data->id()).toStdString());
        db->execute(QString("DELETE FROM AddressItem WHERE campData = %1").arg(data->id()).toStdString());

        foreach (ServiceItemPtr item, data->services()) {
            item->setCampData(data.toWeakRef());
            db->persist(item);
        }

        foreach (AddressItemPtr item, data->people()) {
            item->setCampData(data.toWeakRef());
            db->persist(item);
        }

        addDateAndUser(data, false);

        db->update(data);
        tr.commit();
    }
    catch (const odb::exception &ex)
    {
        emit dbError(ex.what());
        emit dbErrorUpdate(ex.what());
        return;
    }
}

void CampService::eraseCamp(CampDataPtr data)
{
    if (!checkPermission(PERM_DELETE))
    {
        return;
    }

    Transaction tr;

    try
    {
        odb::database *db = Context::instance().db();

        db->execute(QString("DELETE FROM ServiceItem WHERE campData = %1").arg(data->id()).toStdString());
        db->execute(QString("DELETE FROM AddressItem WHERE campData = %1").arg(data->id()).toStdString());

        db->erase(data);
        tr.commit();
    }
    catch (const odb::exception &ex)
    {
        emit dbError(ex.what());
        emit dbErrorDelete(ex.what());
        return;
    }
}

void CampService::loadItems(CampDataPtr data)
{
    Service<AddressItem> srv;
    data->setPeople(srv.all(QString("campData = %1").arg(data->id())));

    Service<ServiceItem> srvService;
    data->setServices(srvService.all(QString("campData = %1").arg(data->id())));
}

QList<CampDataPtr> CampService::allForSeason()
{
    SeasonService seasonSrv;
    SeasonPtr season = seasonSrv.active();

    return all(QString("season = %1").arg(QString::number(season->id())));
}

void CampService::calcPeople(CampDataPtr data)
{
    foreach (ServiceItemPtr service, data->services()) {
        if (service->type() == AccService::ACCFEE)
        {
            data->removeServiceItem(service);
        }
    }

    Service<PersonPrice> srvPrices;
    QList<PersonPricePtr> prices = srvPrices.all("active = 1");
    int days = data->start().daysTo(data->end());
    QDecDouble sale = data->sale();
    bool fixedSale = data->fixedSale();

    foreach (AddressItemPtr item, data->people()) {
        QDate first(1,1,1);
        qint64 daysStart = item->adbItem()->birthDate().daysTo(data->start());
        first = first.addDays(daysStart);
        int startAge = first.year() - 1;

        first = QDate(1,1,1);
        qint64 daysEnd = item->adbItem()->birthDate().daysTo(data->end());
        first = first.addDays(daysEnd);
        int endAge = first.year() - 1;

        auto setTotal = [&](){
            if (sale != QDecDouble(0) && !fixedSale)
            {
                QDecDouble itemSale = (item->price() * sale) / 100;
                item->setSale(itemSale);
                item->setTotalPrice(item->price() - itemSale);
            }
            else
            {
                item->setSale(0);
                item->setTotalPrice(item->price());
            }

            addAccFee(data, item, startAge, endAge, days);
        };

        if (!item->personPrice().isNull())
        {
            item->setPrice(item->personPrice()->price() * days);
            setTotal();
            continue;
        }
        else
        {
            item->setPrice(0);
        }

        foreach (PersonPricePtr price, prices) {
            if (price->fromAge() <= endAge && price->toAge() >= endAge)
            {
                item->setPersonPrice(price);
                item->setPrice(price->price() * days);
                break;
            }
        }

        setTotal();
    }
}

void CampService::calcServices(CampDataPtr data)
{
    QDecDouble sale = data->sale();
    bool fixedSale = data->fixedSale();
    int days = data->start().daysTo(data->end());

    foreach (ServiceItemPtr item, data->services()) {
        item->setFullPrice(item->price() * days);

        if (sale != QDecDouble(0) && !fixedSale && item->salePossible())
        {
            QDecDouble itemSale = (item->fullPrice() * sale) / 100;
            item->setSale(itemSale);
            item->setTotalPrice(item->fullPrice() - itemSale);
        }
        else
        {
            item->setSale(0);
            item->setTotalPrice(item->fullPrice());
        }
    }
}

void CampService::calcPrice(CampDataPtr data)
{
    QDecDouble totalPrice(0);
    QDecDouble sale(0);

    foreach (ServiceItemPtr service, data->services()) {
        totalPrice += service->totalPrice();
        sale += service->sale();
    }

    foreach (AddressItemPtr addr, data->people()) {
        totalPrice += addr->totalPrice();
        sale += addr->sale();
    }

    if (data->fixedSale())
    {
        totalPrice -= data->sale();
        sale = data->sale();
    }

    switch (m_settings->rounding()) {
    case Enums::R_UP:
        totalPrice = QDecDouble(ceil(totalPrice.toDouble() * pow(10, m_settings->decimalPlaces())) / pow(10, m_settings->decimalPlaces()));
        break;
    case Enums::R_DOWN:
        totalPrice = QDecDouble(floor(totalPrice.toDouble() * pow(10, m_settings->decimalPlaces())) / pow(10, m_settings->decimalPlaces()));
        break;
    case Enums::R_MATH:
        totalPrice = QDecDouble(round(totalPrice.toDouble() * pow(10, m_settings->decimalPlaces())) / pow(10, m_settings->decimalPlaces()));
        break;
    case Enums::R_NONE:
        break;
    }

    data->setTotalPrice(totalPrice);
    data->setTotalSale(sale);
}

void CampService::addAccFee(CampDataPtr data, AddressItemPtr item, int startAge, int endAge, int days)
{
    if (item->adbItem()->ztp())
    {
        return;
    }

    auto addAccService = [this, item, data](int count){
        ServiceItemPtr srvItem(new ServiceItem);

        srvItem->setName(item->firstName() + " " + item->lastName());
        srvItem->setDescription(m_settings->accFeeText());
        srvItem->setPrice(m_settings->accFee());
        srvItem->setTotalPrice(m_settings->accFee() * count);
        srvItem->setFullPrice(srvItem->totalPrice());
        srvItem->setType(AccService::ACCFEE);
        srvItem->setSalePossible(false);
        srvItem->setCampData(data);
        data->addServiceItem(srvItem);
    };

    if (startAge == endAge || (startAge >= m_settings->accFeeStartAge() && startAge <= m_settings->accFeeEndAge()))
    {
        if (endAge >= m_settings->accFeeStartAge() && endAge <= m_settings->accFeeEndAge())
        {
            addAccService(days);
        }
    }
    else
    {
        if (endAge >= m_settings->accFeeStartAge() && endAge <= m_settings->accFeeEndAge())
        {
            QDate tmp(data->end().year(), item->adbItem()->birthDate().month(), item->adbItem()->birthDate().day());
            int count = tmp.daysTo(data->end());

            addAccService(count);
        }
    }
}

QList<ShopItemPtr> CampService::shopItems()
{
    CampShopItemPtr item(new CampShopItem);

    QList<ShopItemPtr> items;
    items.append(item);

    return items;
}

ShopItemPtr CampService::shopItem(int )
{
    return CampShopItemPtr(new CampShopItem);
}

void CampService::addedToVoucher(int itemId, int countAdded)
{
    Transaction tx;
    CampDataPtr data = loadById(itemId);

    if (countAdded > 0)
    {
        data->setOnVoucher(true);
    }
    else
    {
        data->setOnVoucher(false);
    }

    this->update(data);
    tx.commit();
}

ISeller *CampService::seller()
{
    return m_seller;
}

ServiceItemPtr CampService::addServiceInt(CampDataPtr data, AccServicePtr service)
{
    ServiceItemPtr serviceItem(new ServiceItem);

    serviceItem->setName(service->accServiceName());
    serviceItem->setCode(service->accServiceCode());
    serviceItem->setPrice(service->price());
    serviceItem->setSalePossible(service->salePossible());
    serviceItem->setType(service->serviceType());
    serviceItem->setCampData(data);

    data->addServiceItem(serviceItem);

    return serviceItem;
}

