/*
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "stdafx.h"
#include "Subtitle.h"
#include "Split.h"
#include <math.h>

namespace ssf
{
struct Subtitle::n2n_t Subtitle::m_n2n;

Subtitle::Subtitle(File* pFile)
    : m_pFile(pFile)
    , m_animated(false)
{
    if(m_n2n.align[0].IsEmpty())
    {
        m_n2n.align[0][L"left"] = 0;
        m_n2n.align[0][L"center"] = 0.5;
        m_n2n.align[0][L"middle"] = 0.5;
        m_n2n.align[0][L"right"] = 1;
    }

    if(m_n2n.align[1].IsEmpty())
    {
        m_n2n.align[1][L"top"] = 0;
        m_n2n.align[1][L"middle"] = 0.5;
        m_n2n.align[1][L"center"] = 0.5;
        m_n2n.align[1][L"bottom"] = 1;
    }

    if(m_n2n.weight.IsEmpty())
    {
        m_n2n.weight[L"thin"] = FW_THIN;
        m_n2n.weight[L"normal"] = FW_NORMAL;
        m_n2n.weight[L"bold"] = FW_BOLD;
    }

    if(m_n2n.transition.IsEmpty())
    {
        m_n2n.transition[L"start"] = 0;
        m_n2n.transition[L"stop"] = 1e10;
        m_n2n.transition[L"linear"] = 1;
    }
}

Subtitle::~Subtitle()
{
}

bool Subtitle::Parse(Definition* pDef, float start, float stop, float at)
{
    ASSERT(m_pFile && pDef);

    m_name = pDef->m_name;

    m_text.RemoveAll();

    m_time.start = start;
    m_time.stop = stop;

    at -= start;

    Fill::gen_id = 0;

    m_pFile->Commit();

    try
    {
        Definition& frame = (*pDef)[L"frame"];

        m_frame.reference = frame[L"reference"];
        m_frame.resolution.cx = frame[L"resolution"][L"cx"];
        m_frame.resolution.cy = frame[L"resolution"][L"cy"];

        Definition& direction = (*pDef)[L"direction"];

        m_direction.primary = direction[L"primary"];
        m_direction.secondary = direction[L"secondary"];

        m_wrap = (*pDef)[L"wrap"];

        m_layer = (*pDef)[L"layer"];

        Style style;
        GetStyle(&(*pDef)[L"style"], style);

        StringMapW<float> offset;
        Definition& block = (*pDef)[L"@"];
        Parse(WCharInputStream((LPCWSTR)block), style, at, offset, dynamic_cast<Reference*>(block.m_parent));

        // TODO: trimming should be done by the renderer later, after breaking the words into lines

        while(!m_text.IsEmpty() && (m_text.GetHead().str == Text::SP || m_text.GetHead().str == Text::LSEP))
            m_text.RemoveHead();

        while(!m_text.IsEmpty() && (m_text.GetTail().str == Text::SP || m_text.GetTail().str == Text::LSEP))
            m_text.RemoveTail();

        for(POSITION pos = m_text.GetHeadPosition(); pos; m_text.GetNext(pos))
        {
            if(m_text.GetAt(pos).str == Text::LSEP)
            {
                POSITION prev = pos;
                m_text.GetPrev(prev);

                while(prev && m_text.GetAt(prev).str == Text::SP)
                {
                    POSITION tmp = prev;
                    m_text.GetPrev(prev);
                    m_text.RemoveAt(tmp);
                }

                POSITION next = pos;
                m_text.GetNext(next);

                while(next && m_text.GetAt(next).str == Text::SP)
                {
                    POSITION tmp = next;
                    m_text.GetNext(next);
                    m_text.RemoveAt(tmp);
                }
            }
        }
    }
    catch(Exception& e)
    {
        UNREFERENCED_PARAMETER(e);
        TRACE(_T("%s"), e.ToString());
        return false;
    }

    m_pFile->Rollback();

    return true;
}

void Subtitle::GetStyle(Definition* pDef, Style& style)
{
    style.placement.pos.x = 0;
    style.placement.pos.y = 0;
    style.placement.pos.auto_x = true;
    style.placement.pos.auto_y = true;

    style.placement.org.x = 0;
    style.placement.org.y = 0;
    style.placement.org.auto_x = true;
    style.placement.org.auto_y = true;

    Rect frame = {0, m_frame.resolution.cx, m_frame.resolution.cy, 0};

    style.placement.clip.t = -1;
    style.placement.clip.r = -1;
    style.placement.clip.b = -1;
    style.placement.clip.l = -1;

    //

    style.linebreak = (*pDef)[L"linebreak"];

    Definition& placement = (*pDef)[L"placement"];

    Definition& clip = placement[L"clip"];

    if(clip.IsValue(Definition::string))
    {
        CStringW str = clip;

        if(str == L"frame") style.placement.clip = frame;
        // else ?
    }
    else
    {
        if(clip[L"t"].IsValue()) style.placement.clip.t = clip[L"t"];
        if(clip[L"r"].IsValue()) style.placement.clip.r = clip[L"r"];
        if(clip[L"b"].IsValue()) style.placement.clip.b = clip[L"b"];
        if(clip[L"l"].IsValue()) style.placement.clip.l = clip[L"l"];
    }

    StringMapW<float> n2n_margin[2];

    n2n_margin[0][L"top"] = 0;
    n2n_margin[0][L"right"] = 0;
    n2n_margin[0][L"bottom"] = frame.b - frame.t;
    n2n_margin[0][L"left"] = frame.r - frame.l;
    n2n_margin[1][L"top"] = frame.b - frame.t;
    n2n_margin[1][L"right"] = frame.r - frame.l;
    n2n_margin[1][L"bottom"] = 0;
    n2n_margin[1][L"left"] = 0;

    placement[L"margin"][L"t"].GetAsNumber(style.placement.margin.t, &n2n_margin[0]);
    placement[L"margin"][L"r"].GetAsNumber(style.placement.margin.r, &n2n_margin[0]);
    placement[L"margin"][L"b"].GetAsNumber(style.placement.margin.b, &n2n_margin[1]);
    placement[L"margin"][L"l"].GetAsNumber(style.placement.margin.l, &n2n_margin[1]);

    placement[L"align"][L"h"].GetAsNumber(style.placement.align.h, &m_n2n.align[0]);
    placement[L"align"][L"v"].GetAsNumber(style.placement.align.v, &m_n2n.align[1]);

    if(placement[L"pos"][L"x"].IsValue())
    {
        style.placement.pos.x = placement[L"pos"][L"x"];
        style.placement.pos.auto_x = false;
    }
    if(placement[L"pos"][L"y"].IsValue())
    {
        style.placement.pos.y = placement[L"pos"][L"y"];
        style.placement.pos.auto_y = false;
    }

    placement[L"offset"][L"x"].GetAsNumber(style.placement.offset.x);
    placement[L"offset"][L"y"].GetAsNumber(style.placement.offset.y);

    style.placement.angle.x = placement[L"angle"][L"x"];
    style.placement.angle.y = placement[L"angle"][L"y"];
    style.placement.angle.z = placement[L"angle"][L"z"];

    if(placement[L"org"][L"x"].IsValue())
    {
        style.placement.org.x = placement[L"org"][L"x"];
        style.placement.org.auto_x = false;
    }
    if(placement[L"org"][L"y"].IsValue())
    {
        style.placement.org.y = placement[L"org"][L"y"];
        style.placement.org.auto_y = false;
    }

    style.placement.path = placement[L"path"];

    Definition& font = (*pDef)[L"font"];

    style.font.face = font[L"face"];
    style.font.size = font[L"size"];
    font[L"weight"].GetAsNumber(style.font.weight, &m_n2n.weight);
    style.font.color.a = font[L"color"][L"a"];
    style.font.color.r = font[L"color"][L"r"];
    style.font.color.g = font[L"color"][L"g"];
    style.font.color.b = font[L"color"][L"b"];
    style.font.underline = font[L"underline"];
    style.font.strikethrough = font[L"strikethrough"];
    style.font.italic = font[L"italic"];
    style.font.spacing = font[L"spacing"];
    style.font.scale.cx = font[L"scale"][L"cx"];
    style.font.scale.cy = font[L"scale"][L"cy"];
    style.font.kerning = font[L"kerning"];

    Definition& background = (*pDef)[L"background"];

    style.background.color.a = background[L"color"][L"a"];
    style.background.color.r = background[L"color"][L"r"];
    style.background.color.g = background[L"color"][L"g"];
    style.background.color.b = background[L"color"][L"b"];
    style.background.size = background[L"size"];
    style.background.type = background[L"type"];
    style.background.blur = background[L"blur"];

    Definition& shadow = (*pDef)[L"shadow"];

    style.shadow.color.a = shadow[L"color"][L"a"];
    style.shadow.color.r = shadow[L"color"][L"r"];
    style.shadow.color.g = shadow[L"color"][L"g"];
    style.shadow.color.b = shadow[L"color"][L"b"];
    style.shadow.depth = shadow[L"depth"];
    style.shadow.angle = shadow[L"angle"];
    style.shadow.blur = shadow[L"blur"];

    Definition& fill = (*pDef)[L"fill"];

    style.fill.color.a = fill[L"color"][L"a"];
    style.fill.color.r = fill[L"color"][L"r"];
    style.fill.color.g = fill[L"color"][L"g"];
    style.fill.color.b = fill[L"color"][L"b"];
    style.fill.width = fill[L"width"];
}

float Subtitle::GetMixWeight(Definition* pDef, float at, StringMapW<float>& offset, int default_id)
{
    float t = 1;

    try
    {
        StringMapW<float> n2n;

        n2n[L"start"] = 0;
        n2n[L"stop"] = m_time.stop - m_time.start;

        Definition::Time time;
        if(pDef->GetAsTime(time, offset, &n2n, default_id) && time.start.value < time.stop.value)
        {
            t = (at - time.start.value) / (time.stop.value - time.start.value);

            float u = t;

            if(t < 0) t = 0;
            else if(t >= 1) t = 0.99999f; // doh

            if((*pDef)[L"loop"].IsValue()) t *= (float)(*pDef)[L"loop"];

            CStringW direction = (*pDef)[L"direction"].IsValue() ? (*pDef)[L"direction"] : L"fw";
            if(direction == L"fwbw" || direction == L"bwfw") t *= 2;

            float n;
            t = modff(t, &n);

            if(direction == L"bw"
               || direction == L"fwbw" && ((int)n & 1)
               || direction == L"bwfw" && !((int)n & 1))
                t = 1 - t;

            float accel = 1;

            if((*pDef)[L"transition"].IsValue())
            {
                Definition::Number<float> n;
                (*pDef)[L"transition"].GetAsNumber(n, &m_n2n.transition);
                if(n.value >= 0) accel = n.value;
            }

            if(t == 0.99999f) t = 1;

            if(u >= 0 && u < 1)
            {
                t = accel == 0 ? 1 :
                    accel == 1 ? t :
                    accel >= 1e10 ? 0 :
                    pow(t, accel);
            }
        }
    }
    catch(Exception&)
    {
    }

    return t;
}

template<class T>
bool Subtitle::MixValue(Definition& def, T& value, float t)
{
    StringMapW<T> n2n;
    return MixValue(def, value, t, &n2n);
}

template<>
bool Subtitle::MixValue(Definition& def, float& value, float t)
{
    StringMapW<float> n2n;
    return MixValue(def, value, t, &n2n);
}

template<class T>
bool Subtitle::MixValue(Definition& def, T& value, float t, StringMapW<T>* n2n)
{
    if(!def.IsValue()) return false;

    if(t >= 0.5)
    {
        if(n2n && def.IsValue(Definition::string))
        {
            if(StringMapW<T>::CPair* p = n2n->Lookup(def))
            {
                value = p->m_value;
                return true;
            }
        }

        value = (T)def;
    }

    return true;
}

template<>
bool Subtitle::MixValue(Definition& def, float& value, float t, StringMapW<float>* n2n)
{
    if(!def.IsValue()) return false;

    if(t > 0)
    {
        if(n2n && def.IsValue(Definition::string))
        {
            if(StringMap<float, CStringW>::CPair* p = n2n->Lookup(def))
            {
                value += (p->m_value - value) * t;
                return true;
            }
        }

        value += ((float)def - value) * t;
    }

    return true;
}

template<>
bool Subtitle::MixValue(Definition& def, Path& src, float t)
{
    if(!def.IsValue(Definition::string)) return false;

    if(t >= 1)
    {
        src = (LPCWSTR)def;
    }
    else if(t > 0)
    {
        Path dst = (LPCWSTR)def;

        if(src.GetCount() == dst.GetCount())
        {
            for(size_t i = 0, j = src.GetCount(); i < j; i++)
            {
                Point& s = src[i];
                const Point& d = dst[i];
                s.x += (d.x - s.x) * t;
                s.y += (d.y - s.y) * t;
            }
        }
    }

    return true;
}

void Subtitle::MixStyle(Definition* pDef, Style& dst, float t)
{
    const Style src = dst;

    if(t <= 0) return;
    else if(t > 1) t = 1;

    MixValue((*pDef)[L"linebreak"], dst.linebreak, t);

    Definition& placement = (*pDef)[L"placement"];

    MixValue(placement[L"clip"][L"t"], dst.placement.clip.t, t);
    MixValue(placement[L"clip"][L"r"], dst.placement.clip.r, t);
    MixValue(placement[L"clip"][L"b"], dst.placement.clip.b, t);
    MixValue(placement[L"clip"][L"l"], dst.placement.clip.l, t);
    MixValue(placement[L"align"][L"h"], dst.placement.align.h, t, &m_n2n.align[0]);
    MixValue(placement[L"align"][L"v"], dst.placement.align.v, t, &m_n2n.align[1]);
    dst.placement.pos.auto_x = !MixValue(placement[L"pos"][L"x"], dst.placement.pos.x, dst.placement.pos.auto_x ? 1 : t);
    dst.placement.pos.auto_y = !MixValue(placement[L"pos"][L"y"], dst.placement.pos.y, dst.placement.pos.auto_y ? 1 : t);
    MixValue(placement[L"offset"][L"x"], dst.placement.offset.x, t);
    MixValue(placement[L"offset"][L"y"], dst.placement.offset.y, t);
    MixValue(placement[L"angle"][L"x"], dst.placement.angle.x, t);
    MixValue(placement[L"angle"][L"y"], dst.placement.angle.y, t);
    MixValue(placement[L"angle"][L"z"], dst.placement.angle.z, t);
    dst.placement.org.auto_x = !MixValue(placement[L"org"][L"x"], dst.placement.org.x, dst.placement.org.auto_x ? 1 : t);
    dst.placement.org.auto_y = !MixValue(placement[L"org"][L"y"], dst.placement.org.y, dst.placement.org.auto_y ? 1 : t);
    MixValue(placement[L"path"], dst.placement.path, t);

    Definition& font = (*pDef)[L"font"];

    MixValue(font[L"face"], dst.font.face, t);
    MixValue(font[L"size"], dst.font.size, t);
    MixValue(font[L"weight"], dst.font.weight, t, &m_n2n.weight);
    MixValue(font[L"color"][L"a"], dst.font.color.a, t);
    MixValue(font[L"color"][L"r"], dst.font.color.r, t);
    MixValue(font[L"color"][L"g"], dst.font.color.g, t);
    MixValue(font[L"color"][L"b"], dst.font.color.b, t);
    MixValue(font[L"underline"], dst.font.underline, t);
    MixValue(font[L"strikethrough"], dst.font.strikethrough, t);
    MixValue(font[L"italic"], dst.font.italic, t);
    MixValue(font[L"spacing"], dst.font.spacing, t);
    MixValue(font[L"scale"][L"cx"], dst.font.scale.cx, t);
    MixValue(font[L"scale"][L"cy"], dst.font.scale.cy, t);
    MixValue(font[L"kerning"], dst.font.kerning, t);

    Definition& background = (*pDef)[L"background"];

    MixValue(background[L"color"][L"a"], dst.background.color.a, t);
    MixValue(background[L"color"][L"r"], dst.background.color.r, t);
    MixValue(background[L"color"][L"g"], dst.background.color.g, t);
    MixValue(background[L"color"][L"b"], dst.background.color.b, t);
    MixValue(background[L"size"], dst.background.size, t);
    MixValue(background[L"type"], dst.background.type, t);
    MixValue(background[L"blur"], dst.background.blur, t);

    Definition& shadow = (*pDef)[L"shadow"];

    MixValue(shadow[L"color"][L"a"], dst.shadow.color.a, t);
    MixValue(shadow[L"color"][L"r"], dst.shadow.color.r, t);
    MixValue(shadow[L"color"][L"g"], dst.shadow.color.g, t);
    MixValue(shadow[L"color"][L"b"], dst.shadow.color.b, t);
    MixValue(shadow[L"depth"], dst.shadow.depth, t);
    MixValue(shadow[L"angle"], dst.shadow.angle, t);
    MixValue(shadow[L"blur"], dst.shadow.blur, t);

    Definition& fill = (*pDef)[L"fill"];

    MixValue(fill[L"color"][L"a"], dst.fill.color.a, t);
    MixValue(fill[L"color"][L"r"], dst.fill.color.r, t);
    MixValue(fill[L"color"][L"g"], dst.fill.color.g, t);
    MixValue(fill[L"color"][L"b"], dst.fill.color.b, t);
    MixValue(fill[L"width"], dst.fill.width, t);

    if(fill.m_priority >= PNormal) // this assumes there is no way to set low priority inline overrides
    {
        if(dst.fill.id > 0) throw Exception(_T("cannot apply fill more than once on the same text"));
        dst.fill.id = ++Fill::gen_id;
    }
}

void Subtitle::Parse(InputStream& s, Style style, float at, StringMapW<float> offset, Reference* pParentRef)
{
    Text text;
    text.style = style;

    for(ptrdiff_t c = s.PeekChar(); c != Stream::EOS; c = s.PeekChar())
    {
        s.GetChar();

        if(c == '[')
        {
            AddText(text);

            style = text.style;

            StringMapW<float> inneroffset = offset;

            int default_id = 0;

            do
            {
                Definition* pDef = m_pFile->CreateDef(pParentRef);

                m_pFile->ParseRefs(s, pDef, L",;]");

                ASSERT(pDef->IsType(L"style") || pDef->IsTypeUnknown());

                if((*pDef)[L"time"][L"start"].IsValue() && (*pDef)[L"time"][L"stop"].IsValue())
                {
                    m_animated = true;
                }

                float t = GetMixWeight(pDef, at, offset, ++default_id);
                MixStyle(pDef, style, t);

                if((*pDef)[L"@"].IsValue())
                {
                    Parse(WCharInputStream((LPCWSTR)(*pDef)[L"@"]), style, at, offset, pParentRef);
                }

                s.SkipWhiteSpace();
                c = s.GetChar();
            }
            while(c == ',' || c == ';');

            if(c != ']') s.ThrowError(_T("unterminated override"));

            bool fWhiteSpaceNext = s.IsWhiteSpace(s.PeekChar());
            c = s.SkipWhiteSpace();

            if(c == '{')
            {
                s.GetChar();
                Parse(s, style, at, inneroffset, pParentRef);
            }
            else
            {
                if(fWhiteSpaceNext) text.str += (WCHAR)Text::SP;
                text.style = style;
            }
        }
        else if(c == ']')
        {
            s.ThrowError(_T("unexpected ] found"));
        }
        else if(c == '{')
        {
            AddText(text);
            Parse(s, text.style, at, offset, pParentRef);
        }
        else if(c == '}')
        {
            break;
        }
        else
        {
            if(c == '\\')
            {
                c = s.GetChar();
                if(c == Stream::EOS) break;
                else if(c == 'n')
                {
                    AddText(text);
                    text.str = (WCHAR)Text::LSEP;
                    AddText(text);
                    continue;
                }
                else if(c == 'h') c = Text::NBSP;
            }

            AddChar(text, (WCHAR)c);
        }
    }

    AddText(text);
}

void Subtitle::AddChar(Text& t, WCHAR c)
{
    bool f1 = !t.str.IsEmpty() && Stream::IsWhiteSpace(t.str[t.str.GetLength()-1]);
    bool f2 = Stream::IsWhiteSpace(c);
    if(f2) c = Text::SP;
    if(!f1 || !f2) t.str += (WCHAR)c;
}

void Subtitle::AddText(Text& t)
{
    if(t.str.IsEmpty()) return;

    Split sa(' ', t.str, 0, Split::Max);

    for(size_t i = 0, n = sa; i < n; i++)
    {
        CStringW str = sa[i];

        if(!str.IsEmpty())
        {
            t.str = str;
            m_text.AddTail(t);
        }

        if(i < n - 1 && (m_text.IsEmpty() || m_text.GetTail().str != Text::SP))
        {
            t.str = (WCHAR)Text::SP;
            m_text.AddTail(t);
        }
    }

    t.str.Empty();
}

//

unsigned int Fill::gen_id = 0;

Color::operator DWORD()
{
    DWORD c =
        (min(max((DWORD)b, 0), 255) <<  0) |
        (min(max((DWORD)g, 0), 255) <<  8) |
        (min(max((DWORD)r, 0), 255) << 16) |
        (min(max((DWORD)a, 0), 255) << 24);

    return c;
}

Path& Path::operator = (LPCWSTR str)
{
    Split s(' ', str);

    SetCount(s / 2);

    for(size_t i = 0, j = GetCount(); i < j; i++)
    {
        Point p;
        p.x = s.GetAtFloat(i * 2 + 0);
        p.y = s.GetAtFloat(i * 2 + 1);
        SetAt(i, p);
    }

    return *this;
}

CStringW Path::ToString()
{
    CStringW ret;

    for(size_t i = 0, j = GetCount(); i < j; i++)
    {
        const Point& p = GetAt(i);

        CStringW str;
        str.Format(L"%f %f ", p.x, p.y);
        ret += str;
    }

    return ret;
}

bool Style::IsSimilar(const Style& s)
{
    return
        font.color.r == s.font.color.r
        && font.color.g == s.font.color.g
        && font.color.b == s.font.color.b
        && font.color.a == s.font.color.a
        && background.color.r == s.background.color.r
        && background.color.g == s.background.color.g
        && background.color.b == s.background.color.b
        && background.color.a == s.background.color.a
        && background.size == s.background.size
        && background.type == s.background.type
        && background.blur == s.background.blur
        && shadow.color.r == s.shadow.color.r
        && shadow.color.g == s.shadow.color.g
        && shadow.color.b == s.shadow.color.b
        && shadow.color.a == s.shadow.color.a
        && shadow.depth == s.shadow.depth
        && shadow.angle == s.shadow.angle
        && shadow.blur == s.shadow.blur
        && fill.id == s.fill.id;
}
}
