/*
 * (c) Copyright Ascensio System SIA 2010-2016
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia,
 * EU, LV-1021.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

"use strict";

// Import
var History = AscCommon.History;

var fieldtype_UNKNOWN    = 0x0000;
var fieldtype_MERGEFIELD = 0x0001;
var fieldtype_PAGENUM    = 0x0002;

/**
 *
 * @param FieldType
 * @param Arguments
 * @param Switches
 * @constructor
 * @extends {CParagraphContentWithParagraphLikeContent}
 */
function ParaField(FieldType, Arguments, Switches)
{
    ParaField.superclass.constructor.call(this);

    this.Id = AscCommon.g_oIdCounter.Get_NewId();

    this.Type    = para_Field;

    this.FieldType = (undefined === FieldType ? fieldtype_UNKNOWN : FieldType);
    this.Arguments = (undefined === Arguments ? []                : Arguments);
    this.Switches  = (undefined === Switches  ? []                : Switches);

    this.TemplateContent = this.Content;

    this.Bounds = {};

    // Добавляем данный класс в таблицу Id (обязательно в конце конструктора)
    AscCommon.g_oTableId.Add( this, this.Id );
}

AscCommon.extendClass(ParaField, CParagraphContentWithParagraphLikeContent);

ParaField.prototype.Get_Id = function()
{
    return this.Id;
};
ParaField.prototype.Copy = function(Selected)
{
    var NewField = ParaField.superclass.Copy.apply(this, arguments);

    // TODO: Сделать функциями с иторией
    NewField.FieldType = this.FieldType;
    NewField.Arguments = this.Arguments;
    NewField.Switches  = this.Switches;

    if (editor)
        editor.WordControl.m_oLogicDocument.Register_Field(NewField);

    return NewField;
};
ParaField.prototype.Get_SelectedElementsInfo = function(Info)
{
    Info.Set_Field(this);
    ParaField.superclass.Get_SelectedElementsInfo.apply(this, arguments);
};
ParaField.prototype.Get_Bounds = function()
{
    var aBounds = [];
    for (var Place in this.Bounds)
    {
        aBounds.push(this.Bounds[Place]);
    }

    return aBounds;
};
ParaField.prototype.Add_ToContent = function(Pos, Item, UpdatePosition)
{
    History.Add( this, { Type : AscDFH.historyitem_Field_AddItem, Pos : Pos, EndPos : Pos, Items : [ Item ] } );
    ParaField.superclass.Add_ToContent.apply(this, arguments);
};
ParaField.prototype.Remove_FromContent = function(Pos, Count, UpdatePosition)
{
    // Получим массив удаляемых элементов
    var DeletedItems = this.Content.slice( Pos, Pos + Count );
    History.Add( this, { Type : AscDFH.historyitem_Field_RemoveItem, Pos : Pos, EndPos : Pos + Count - 1, Items : DeletedItems } );

    ParaField.superclass.Remove_FromContent.apply(this, arguments);
};
ParaField.prototype.Add = function(Item)
{
    switch (Item.Type)
    {
        case para_Run      :
        case para_Hyperlink:
        {
            var TextPr = this.Get_FirstTextPr();
            Item.Select_All();
            Item.Apply_TextPr(TextPr);
            Item.Selection_Remove();

            var CurPos = this.State.ContentPos;
            var CurItem = this.Content[CurPos];
            if (para_Run === CurItem.Type)
            {
                var NewRun = CurItem.Split2(CurItem.State.ContentPos);
                this.Add_ToContent(CurPos + 1, Item);
                this.Add_ToContent(CurPos + 2, NewRun);

                this.State.ContentPos = CurPos + 2;
                this.Content[this.State.ContentPos].Cursor_MoveToStartPos();
            }
            else
                CurItem.Add(Item);

            break;
        }
        case para_Math :
        {
            var ContentPos = new CParagraphContentPos();
            this.Get_ParaContentPos(false, false, ContentPos);
            var CurPos = ContentPos.Get(0);

            // Ран формула делит на части, а в остальные элементы добавляется целиком
            if (para_Run === this.Content[CurPos].Type)
            {
                // Разделяем текущий элемент (возвращается правая часть)
                var NewElement = this.Content[CurPos].Split(ContentPos, 1);

                if (null !== NewElement)
                    this.Add_ToContent(CurPos + 1, NewElement, true);

                var Elem = new ParaMath();
                Elem.Root.Load_FromMenu(Item.Menu, this.Get_Paragraph());
                Elem.Root.Correct_Content(true);
                this.Add_ToContent(CurPos + 1, Elem, true);

                // Перемещаем кусор в конец формулы
                this.State.ContentPos = CurPos + 1;
                this.Content[this.State.ContentPos].Cursor_MoveToEndPos(false);
            }
            else
                this.Content[CurPos].Add(Item);

            break;
        }
        case para_Field:
        {
            // Вместо добавления самого элемента добавляем его содержимое
            var Count = Item.Content.length;

            if (Count > 0)
            {
                var CurPos  = this.State.ContentPos;
                var CurItem = this.Content[CurPos];

                var CurContentPos = new CParagraphContentPos();
                CurItem.Get_ParaContentPos(false, false, CurContentPos);

                var NewItem = CurItem.Split(CurContentPos, 0);
                for (var Index = 0; Index < Count; Index++)
                {
                    this.Add_ToContent(CurPos + Index + 1, Item.Content[Index], false);
                }
                this.Add_ToContent(CurPos + Count + 1, NewItem, false);
                this.State.ContentPos = CurPos + Count;
                this.Content[this.State.ContentPos].Cursor_MoveToEndPos();
            }

            break;
        }
        default :
        {
            this.Content[this.State.ContentPos].Add(Item);
            break;
        }
    }
};
ParaField.prototype.Split = function (ContentPos, Depth)
{
    // Не даем разделять поле
    return null;
};
ParaField.prototype.CanSplit = function()
{
	return false;
};
ParaField.prototype.Recalculate_Range_Spaces = function(PRSA, _CurLine, _CurRange, _CurPage)
{
    var CurLine  = _CurLine - this.StartLine;
    var CurRange = (0 === _CurLine ? _CurRange - this.StartRange : _CurRange);

    if (0 === CurLine && 0 === CurRange && true !== PRSA.RecalcFast)
        this.Bounds = {};

    var X0 = PRSA.X;
    var Y0 = PRSA.Y0;
    var Y1 = PRSA.Y1;

    ParaField.superclass.Recalculate_Range_Spaces.apply(this, arguments);

    var X1 = PRSA.X;
    this.Bounds[((CurLine << 16) & 0xFFFF0000) | (CurRange & 0x0000FFFF)] = {X0 : X0, X1 : X1, Y0: Y0, Y1 : Y1, PageIndex : _CurPage + PRSA.Paragraph.Get_StartPage_Absolute()};
};
ParaField.prototype.Draw_HighLights = function(PDSH)
{
    var X0 = PDSH.X;
    var Y0 = PDSH.Y0;
    var Y1 = PDSH.Y1;

    ParaField.superclass.Draw_HighLights.apply(this, arguments);

    var X1 = PDSH.X;

    if (Math.abs(X0 - X1) > 0.001 && true === PDSH.DrawMMFields)
    {
        PDSH.MMFields.Add(Y0, Y1, X0, X1, 0, 0, 0, 0  );
    }
};
//----------------------------------------------------------------------------------------------------------------------
// Работа с данными поля
//----------------------------------------------------------------------------------------------------------------------
ParaField.prototype.Get_Argument = function(Index)
{
    return this.Arguments[Index];
};
ParaField.prototype.Get_FieldType = function()
{
    return this.FieldType;
};
ParaField.prototype.Map_MailMerge = function(_Value)
{
    // Пока у нас в Value может быть только текст, в будущем планируется, чтобы могли быть картинки.
    var Value = _Value;
    if (undefined === Value || null === Value)
        Value = "";

    History.TurnOff();

    var oRun = this.private_GetMappedRun(Value);

    // Подменяем содержимое поля
    this.Content = [];
    this.Content[0] = oRun;

    this.Cursor_MoveToStartPos();

    History.TurnOn();
};
ParaField.prototype.Restore_StandardTemplate = function()
{
    // В любом случае сначала восстанавливаем исходное содержимое.
    this.Restore_Template();

    if (fieldtype_MERGEFIELD === this.FieldType && true === AscCommon.CollaborativeEditing.Is_SingleUser() && 1 === this.Arguments.length)
    {
        var oRun = this.private_GetMappedRun("«" + this.Arguments[0] + "»");
        this.Remove_FromContent(0, this.Content.length);
        this.Add_ToContent(0, oRun);
        this.Cursor_MoveToStartPos();

        this.TemplateContent = this.Content;
    }
};
ParaField.prototype.Restore_Template = function()
{
    // Восстанавливаем содержимое поля.
    this.Content = this.TemplateContent;
    this.Cursor_MoveToStartPos();
};
ParaField.prototype.Is_NeedRestoreTemplate = function()
{
    if (1 !== this.TemplateContent.length)
        return true;

    var oRun = this.TemplateContent[0];
    if (fieldtype_MERGEFIELD === this.FieldType)
    {
        var sStandardText = "«" + this.Arguments[0] + "»";

        var oRunText = new CParagraphGetText();
        oRun.Get_Text(oRunText);

        if (sStandardText === oRunText.Text)
            return false;

        return true;
    }

    return false;
};
ParaField.prototype.Replace_MailMerge = function(_Value)
{
    // Пока у нас в Value может быть только текст, в будущем планируется, чтобы могли быть картинки.
    var Value = _Value;
    if (undefined === Value || null === Value)
        Value = "";

    var Paragraph = this.Paragraph;

    if (!Paragraph)
        return false;

    // Получим ран, на который мы подменяем поле
    var oRun = this.private_GetMappedRun(Value);

    // Ищем расположение данного поля в параграфе
    var ParaContentPos = Paragraph.Get_PosByElement(this);

    if (null === ParaContentPos)
        return false;

    var Depth    = ParaContentPos.Get_Depth();
    var FieldPos = ParaContentPos.Get(Depth);

    if (Depth < 0)
        return false;

    ParaContentPos.Decrease_Depth(1);
    var FieldContainer = Paragraph.Get_ElementByPos(ParaContentPos);
    if (!FieldContainer || !FieldContainer.Content || FieldContainer.Content[FieldPos] !== this)
        return false;

    FieldContainer.Remove_FromContent(FieldPos, 1);
    FieldContainer.Add_ToContent(FieldPos, oRun);

    return true;
};
ParaField.prototype.private_GetMappedRun = function(Value)
{
    // Создаем ран и набиваем в него заданный текст.
    var oRun = new ParaRun();

    for (var Index = 0, Count = Value.length; Index < Count; Index++)
    {
        var Char = Value[Index], oText;
        if (0x20 === Char)
            oText = new ParaSpace();
        else
            oText = new ParaText(Value[Index]);

        oRun.Add_ToContent(Index, oText);
    }

    oRun.Set_Pr(this.Get_FirstTextPr());

    return oRun;
};
//----------------------------------------------------------------------------------------------------------------------
// Undo/Redo функции
//----------------------------------------------------------------------------------------------------------------------
ParaField.prototype.Undo = function(Data)
{
    var Type = Data.Type;
    switch(Type)
    {
        case AscDFH.historyitem_Field_AddItem :
        {
            this.Content.splice( Data.Pos, Data.EndPos - Data.Pos + 1 );
            this.protected_UpdateSpellChecking();
            this.private_UpdateTrackRevisions();
            break;
        }

        case AscDFH.historyitem_Field_RemoveItem :
        {
            var Pos = Data.Pos;

            var Array_start = this.Content.slice( 0, Pos );
            var Array_end   = this.Content.slice( Pos );

            this.Content = Array_start.concat( Data.Items, Array_end );
            this.protected_UpdateSpellChecking();
            this.private_UpdateTrackRevisions();
            break;
        }
    }
};
ParaField.prototype.Redo = function(Data)
{
    var Type = Data.Type;
    switch(Type)
    {
        case AscDFH.historyitem_Field_AddItem :
        {
            var Pos = Data.Pos;

            var Array_start = this.Content.slice( 0, Pos );
            var Array_end   = this.Content.slice( Pos );

            this.Content = Array_start.concat( Data.Items, Array_end );
            this.private_UpdateTrackRevisions();
            this.protected_UpdateSpellChecking();
            break;
        }

        case AscDFH.historyitem_Field_RemoveItem :
        {
            this.Content.splice( Data.Pos, Data.EndPos - Data.Pos + 1 );
            this.private_UpdateTrackRevisions();
            this.protected_UpdateSpellChecking();
            break;
        }
    }
};
//----------------------------------------------------------------------------------------------------------------------
// Функции совместного редактирования
//----------------------------------------------------------------------------------------------------------------------
ParaField.prototype.Save_Changes = function(Data, Writer)
{
    // Сохраняем изменения из тех, которые используются для Undo/Redo в бинарный файл.
    // Long : тип класса
    // Long : тип изменений

    Writer.WriteLong(AscDFH.historyitem_type_Field);

    var Type = Data.Type;

    // Пишем тип
    Writer.WriteLong( Type );

    switch(Type)
    {
        case AscDFH.historyitem_Field_AddItem :
        {
            // Long     : Количество элементов
            // Array of :
            //  {
            //    Long     : Позиция
            //    Variable : Id элемента
            //  }

            var bArray = Data.UseArray;
            var Count  = Data.Items.length;

            Writer.WriteLong( Count );

            for ( var Index = 0; Index < Count; Index++ )
            {
                if ( true === bArray )
                    Writer.WriteLong( Data.PosArray[Index] );
                else
                    Writer.WriteLong( Data.Pos + Index );

                Writer.WriteString2( Data.Items[Index].Get_Id() );
            }

            break;
        }

        case AscDFH.historyitem_Field_RemoveItem :
        {
            // Long          : Количество удаляемых элементов
            // Array of Long : позиции удаляемых элементов

            var bArray = Data.UseArray;
            var Count  = Data.Items.length;

            var StartPos = Writer.GetCurPosition();
            Writer.Skip(4);
            var RealCount = Count;

            for ( var Index = 0; Index < Count; Index++ )
            {
                if ( true === bArray )
                {
                    if ( false === Data.PosArray[Index] )
                        RealCount--;
                    else
                        Writer.WriteLong( Data.PosArray[Index] );
                }
                else
                    Writer.WriteLong( Data.Pos );
            }

            var EndPos = Writer.GetCurPosition();
            Writer.Seek( StartPos );
            Writer.WriteLong( RealCount );
            Writer.Seek( EndPos );

            break;
        }
    }
};
ParaField.prototype.Load_Changes = function(Reader)
{
    // Сохраняем изменения из тех, которые используются для Undo/Redo в бинарный файл.
    // Long : тип класса
    // Long : тип изменений

    var ClassType = Reader.GetLong();
    if ( AscDFH.historyitem_type_Field != ClassType )
        return;

    var Type = Reader.GetLong();

    switch ( Type )
    {
        case AscDFH.historyitem_Field_AddItem :
        {
            // Long     : Количество элементов
            // Array of :
            //  {
            //    Long     : Позиция
            //    Variable : Id Элемента
            //  }

            var Count = Reader.GetLong();

            for ( var Index = 0; Index < Count; Index++ )
            {
                var Pos     = this.m_oContentChanges.Check( AscCommon.contentchanges_Add, Reader.GetLong() );
                var Element = AscCommon.g_oTableId.Get_ById( Reader.GetString2() );

                if ( null != Element )
                {
                    this.Content.splice( Pos, 0, Element );
                    AscCommon.CollaborativeEditing.Update_DocumentPositionsOnAdd(this, Pos);
                }
            }
            this.private_UpdateTrackRevisions();
            this.protected_UpdateSpellChecking();
            break;
        }

        case AscDFH.historyitem_Field_RemoveItem:
        {
            // Long          : Количество удаляемых элементов
            // Array of Long : позиции удаляемых элементов

            var Count = Reader.GetLong();

            for ( var Index = 0; Index < Count; Index++ )
            {
                var ChangesPos = this.m_oContentChanges.Check( AscCommon.contentchanges_Remove, Reader.GetLong() );

                // действие совпало, не делаем его
                if ( false === ChangesPos )
                    continue;

                this.Content.splice( ChangesPos, 1 );
                AscCommon.CollaborativeEditing.Update_DocumentPositionsOnRemove(this, ChangesPos, 1);
            }
            this.private_UpdateTrackRevisions();
            this.protected_UpdateSpellChecking();
            break;
        }
    }
};
ParaField.prototype.Write_ToBinary2 = function(Writer)
{
    Writer.WriteLong(AscDFH.historyitem_type_Field);

    // String : Id
    // Long   : FieldType
    // Long   : Количество аргументов
    // Array of Strings : массив аргументов
    // Long   : Количество переключателей
    // Array of Strings : массив переключателей
    // Long   : Количество элементов
    // Array of Strings : массив с Id элементов

    Writer.WriteString2(this.Id);
    Writer.WriteLong(this.FieldType);

    var ArgsCount = this.Arguments.length;
    Writer.WriteLong(ArgsCount);
    for (var Index = 0; Index < ArgsCount; Index++)
        Writer.WriteString2(this.Arguments[Index]);

    var SwitchesCount = this.Switches.length;
    Writer.WriteLong(SwitchesCount);
    for (var Index = 0; Index < SwitchesCount; Index++)
        Writer.WriteString2(this.Switches[Index]);

    var Count = this.Content.length;
    Writer.WriteLong(Count);
    for (var Index = 0; Index < Count; Index++)
        Writer.WriteString2(this.Content[Index].Get_Id());
};
ParaField.prototype.Read_FromBinary2 = function(Reader)
{
    // String : Id
    // Long   : FieldType
    // Long   : Количество аргументов
    // Array of Strings : массив аргументов
    // Long   : Количество переключателей
    // Array of Strings : массив переключателей
    // Long   : Количество элементов
    // Array of Strings : массив с Id элементов

    this.Id = Reader.GetString2();
    this.FieldType = Reader.GetLong();

    var Count = Reader.GetLong();
    this.Arguments = [];
    for (var Index = 0; Index < Count; Index++)
        this.Arguments.push(Reader.GetString2());

    Count = Reader.GetLong();
    this.Switches = [];
    for (var Index = 0; Index < Count; Index++)
        this.Switches.push(Reader.GetString2());

    Count = Reader.GetLong();
    this.Content = [];
    for (var Index = 0; Index < Count; Index++)
    {
        var Element = AscCommon.g_oTableId.Get_ById(Reader.GetString2());
        if (null !== Element)
            this.Content.push(Element);
    }

    this.TemplateContent = this.Content;

    if (editor)
        editor.WordControl.m_oLogicDocument.Register_Field(this);
};

//--------------------------------------------------------export----------------------------------------------------
window['AscCommonWord'] = window['AscCommonWord'] || {};
window['AscCommonWord'].ParaField = ParaField;