package GCData;

###################################################
#
#  Copyright 2005-2007 Tian
#
#  This file is part of GCstar.
#
#  GCstar 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 of the License, or
#  (at your option) any later version.
#
#  GCstar 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 GCstar; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
###################################################

use strict;


{
    package GCItems;
    
    use XML::Parser;
    use Storable;
    use File::Copy;
    use File::Path;
    use File::Basename;

    use GCModel;
    use GCBackend::GCBackendXmlParser;

    sub new
    {
        my ($proto, $parent) = @_;
        my $class = ref($proto) || $proto;
        my $self  = {};

        $self->{parent} = $parent;

        $self->{imagesToBeRemoved} = [];
        $self->{imagesToBeAdded} = [];
        $self->{loaded} = {};

        $self->{currentItem} = -1;
        $self->{hasBeenDeleted} = 0;
        $self->{block} = 0;
        $self->{previousFile} = 0;
        #$self->{filterSearch} = new GCFilterSearch;

        bless ($self, $class);
        return $self;
    }

    sub initModel
    {
        my ($self, $model) = @_;
        $self->{model} = $model;
        $self->{parent}->notifyModelChange;
    }

    sub setPanel
    {
        my ($self, $panel) = @_;
        $self->{panel} = $panel;
    }

    sub unselect
    {
        my ($self) = @_;
        
        $self->{currentItem} = -1;
    }

    sub updateCurrentFromPanel
    {
        my ($self, $panel) = @_;
        my $previousPanel = $self->{panel};
        $self->{panel} = $panel;
        $self->updateCurrent(1);
        $self->{panel} = $previousPanel;
    }

    sub updateCurrent
    {
        my ($self, $withSelect, $forced) = @_;
        my $selectedChanged = 0;
        my $filtered = 0;
        return $selectedChanged if $self->{currentItem} == -1;
        my $info;
        if ($self->{multipleMode})
        {
            $info = {};
        }
        else
        {
            $info = ($self->getItemsList)->[$self->{currentItem}];
        }

        my $idField = $self->{model}->{commonFields}->{id};
        my $panelId = $self->{panel}->$idField;
        my $previousId = $info->{$idField};
        
        my $changed = 0;
        if ($panelId &&
           ($panelId != $previousId))
        {
            $info->{$idField} = $self->{panel}->$idField;
            $self->{loaded}->{information}->{maxId} = $panelId
                if ($panelId > $self->{loaded}->{information}->{maxId});
            $self->findMaxId if $previousId == $self->{loaded}->{information}->{maxId};
            $changed = 1;
        }
        $self->{panel}->$idField($info->{$idField}) if $self->{panel}->$idField;

        my $previous = {$idField => $previousId};

        for my $field (@{$self->{model}->{fieldsNames}})
        {
            next if $field eq $idField;
            $previous->{$field} = $info->{$field};
            next if !$self->{panel}->{$field}->hasChanged;
            $self->{panel}->{$field}->addHistory if ($self->{model}->{fieldsInfo}->{$field}->{hasHistory});
            $changed = 1;
            $info->{$field} = $self->{panel}->$field;
            $self->{parent}->{menubar}->checkFilter($field);
        }

        if ($forced)
        {
            $previous->{$_} = 'GCS_FORCED' foreach @$forced;
        }

        $self->{panel}->dataChanged($info);
        if ($changed)
        {
            if ($self->{multipleMode})
            {
                my $newIdx;
                # Propagate the changes to all the items
                foreach (@{$self->{multipleCurrentItems}})
                {
                    my $previous = Storable::dclone(($self->getItemsList)->[$_]);
                    my $item = ($self->getItemsList)->[$_];
                    for my $field (keys %$info)
                    {
                        $item->{$field} = $info->{$field};
                    }
                    $newIdx = $self->{parent}->{itemsView}->changeItem($_, $previous, $item);
                    if ($newIdx != $_)
                    {
                        $selectedChanged = 1;
                    }
                }
                if ($selectedChanged)
                {
                    $self->{currentItem} = $self->{parent}->{itemsView}->getCurrentIdx;
                    $self->{multipleMode} = 0;
                    $self->displayCurrent;
                }
            }
            else
            {
                my $current = $self->{parent}->{itemsView}->changeCurrent($previous,
                                                                          $info,
                                                                          $self->{currentItem},
                                                                          $withSelect);
                # If we didn't selected the same, the selection didn't change
                if ($current != $self->{currentItem})
                {
                    $self->{currentItem} = $current;
                    $self->displayCurrent if $withSelect;
                    $selectedChanged = 1;
                }
                if ($selectedChanged)
                {
                    $filtered = 1;
                    $selectedChanged = 0 if !$withSelect;
                }
            }
            $self->{parent}->checkPanelVisibility;
            $self->{parent}->markAsUpdated;
        }
        return ($selectedChanged, $filtered);
    }
    
    sub getTitle
    {
        my ($self, $idx) = @_;

        my $realIdx = $idx;
        $realIdx = $self->{currentItem} if ! defined $idx;
        return ($self->getItemsList)->[$realIdx]->{$self->{model}->{commonFields}->{title}};
    }

    sub getCurrent
    {
        my ($self) = @_;
        return $self->{currentItem};
    }

    sub displayCurrent
    {
        my ($self) = @_;
        $self->displayInPanel($self->{panel}, undef);
    }
    
    sub displayInPanel
    {
        my ($self, $panel, $idx) = @_;
        my $info;
        if ($self->{multipleMode})
        {
            # We merge all the items here
            my %fields = map {$_ => 1} @{$self->{model}->{fieldsNotFormatted}};
            foreach (@{$self->{multipleCurrentItems}})
            {
                my $item = ($self->getItemsList)->[$_];
                for my $field (keys %fields)
                {
                    if (exists $info->{$field})
                    {
                        if ($self->transformValue($info->{$field}, $field)
                            ne
                            $self->transformValue($item->{$field}, $field))
                        {
                            $info->{$field} = '';
                            delete $fields{$field};
                            # TODO store the information also elsewhere to mark
                            # the fields in panel. Or mark it immediately with something
                            # such as
                            # panel->markAsDirty($field);
                        }
                    }
                    else
                    {
                        $info->{$field} = $item->{$field};
                    }
                }
            }
        }
        else
        {
            $idx = $self->{currentItem} if ! defined $idx;
            return if $self->{currentItem} < 0;
            $info = ($self->getItemsList)->[$idx];
        }
        for my $field (@{$self->{model}->{fieldsNotFormatted}})
        {
            $panel->$field($info->{$field});
            $panel->{$field}->resetChanged
                if $panel->{$field};
        }
        
        $panel->dataChanged;
    }

    sub display
    {
        my $self = shift;
        return if (! $self->{itemArray}) || (! scalar @{$self->{itemArray}});
        my @numbers = @_;
        my $number;
        my $multipleMode;
        my $withSelect = 0;
        if ($#numbers > 0)
        {
            $multipleMode = 1;
            $self->{multipleCurrentItems} = \@numbers;
        }
        else
        {
            $multipleMode = 0;
            $number = $numbers[0];
            $number = 0 if $number == -1;
            # We want a selection if user clicked on the same one
            $withSelect = ($number == $self->{currentItem});
        }
        my ($selectedHasChanged, $filtered) = (0, 0);

        if (($self->{currentItem} > -1) && !($self->{hasBeenDeleted}))
        {
            ($selectedHasChanged, $filtered) = $self->updateCurrent($withSelect);
        }
        else
        {
            $self->{currentItem} = $number
                if !$multipleMode;
        }
        $self->{multipleMode} = $multipleMode;
        $self->{hasBeenDeleted} = 0;

        $self->{currentItem} = $number if !$selectedHasChanged && !$multipleMode;
        $self->displayCurrent if !$selectedHasChanged;
        return ($selectedHasChanged || $filtered);
    }

    sub valueToDisplayed
    {
        my ($self, $value, $field) = @_;
        return $self->{model}->getDisplayedValue($self->{model}->{fieldsInfo}->{$field}->{values}, $value);
    }
                
    sub transformValue
    {
        my ($self, $value, $field, $type) = @_;
        
        $type ||= $self->{model}->{fieldsInfo}->{$field}->{type};
        $value = $self->{parent}->transformTitle($value) if $field eq $self->{model}->{commonFields}->{title};
        #$value = GCPreProcess::reverseDate($value) if $type eq 'date';
        $value = GCUtils::timeToStr($value, $self->{parent}->{options}->dateFormat)
            if $type eq 'date';
        $value = $self->valueToDisplayed($value, $field) if $type eq 'options';
        $value = GCPreProcess::multipleList($value, $type) if $type =~ /list$/o;
        return $value;
    }

    sub getValue
    {
        my ($self, $idx, $field) = @_;
        
        return ($self->getItemsList)->[$idx]->{$field};
    }
    
    sub setValue
    {
        my ($self, $idx, $field, $value) = @_;

        ($self->getItemsList)->[$idx]->{$field} = $value;
        if ($idx == $self->{currentItem})
        {
            $self->{panel}->$field($value);
            $self->{panel}->dataChanged;
        }
    }

    sub getItemsList
    {
        my ($self, $filter) = @_;
        
        return $self->{itemArray} if ! $filter;
        my @results = ();
        foreach (@{$self->{itemArray}})
        {
            if ($self->{parent}->{filterSearch}->test($_))
            {
                push @results, $_;
            }
        }
        return \@results;
    }
    
    # Should only be used by GCCommandExecution
    sub setItemsList
    {
        my ($self, $itemsList) = @_;

        $self->{itemArray} = $itemsList;
    }

    sub getInformation
    {
        my $self = shift;
        $self->{loaded}->{information} ||= {};
        return $self->{loaded}->{information};
    }

   sub setInformation
    {
        my ($self, $info) = @_;
        $self->{loaded}->{information} = $info;
    }

    sub reloadList
    {
        my ($self, $splash, $fullProgress, $filtering) = @_;
        return if $self->{block};
        if ($splash)
        {
            $splash->initProgress if $fullProgress;
            $splash->setItemsTotal(scalar @{$self->{itemArray}})
                if $self->{itemArray};
        }
        $self->{parent}->{itemsView}->reset if $self->{parent}->{itemsView};
        
        my $lastDisplayed = -1;
        my $hasId = 0;
        my $j = 0;
        my $idField = $self->{model}->{commonFields}->{id};
        my $currentId;

        # If we don't get an history from BE, we will have to initialize it now
        my $historyNeeded = ! $self->{loaded}->{gotHistory};
        my %histories;

        foreach (@{$self->{itemArray}})
        {
            if ($historyNeeded)
            {
                foreach my $field (@{$self->{model}->{fieldsHistory}})
                {
                    #push @{$histories{$field}}, $_->{$field};
                    $self->{panel}->addHistory($_->{$field}, 1);
                }
            }

            $currentId = $_->{$idField};

            $self->{parent}->{itemsView}->addItem($_, 0);               
            $lastDisplayed = $j;
            $splash->setProgressForItemsDisplay($j) if $splash;
            $j++;
        }

        if ($historyNeeded)
        {
            for my $hfield(@{$self->{model}->{fieldsHistory}})
            {
                $self->{panel}->{$hfield}->setDropDown;
            }
            # Now we are sure we got one
            $self->{loaded}->{gotHistory} = 2;
        }

        $self->{panel}->show if $j;

        #if ($splash && $fullProgress)
        #{
        #    $splash->endProgress;
        #}

        if (! $self->{parent}->{initializing})
        {
            $self->{parent}->reloadDone(0, $splash);
            $self->select($self->{currentItem}, 0);
        }
    }

    sub select
    {
        my ($self, $value, $init) = @_;
        return if !$self->{parent}->{itemsView};
        return $self->{parent}->{itemsView}->select($value, $init) unless  $value < -1;
    }

    sub removeCurrentItems
    {
        my $self = shift;

        my $numbers = $self->{parent}->{itemsView}->getCurrentItems;

        my $nbRemoved = 0;
        foreach my $number(sort @$numbers)
        {
            # We need to adjust it because we already removed other ones.
            my $actualNumber = $number - $nbRemoved;
            foreach (@{$self->{model}->{managedImages}})
            {
                my $image = $self->{itemArray}->[$actualNumber]->{$_};
                my $imagePrefix = $self->{parent}->{imagePrefix};
                if ($image =~ /(\/|\\)$imagePrefix[0-9]*\./)
                {
                    $self->markToBeRemoved($image);
                }
            }

            splice @{$self->{itemArray}}, $actualNumber, 1;
            $nbRemoved++;
        }
        my $newIdx = $self->{parent}->{itemsView}->removeCurrentItems; #($number);

        $self->{currentItem} = $newIdx;
        $self->{multipleMode} = 0;
        $self->{hasBeenDeleted} = 1;
        $self->displayCurrent;
    }

    sub addItem
    {
        my ($self, $info, $keepId, $noSelect) = @_;
        my $nbItems = scalar @{$self->{itemArray}};
#        $self->{panel}->show if ! $nbItems;

        my $currentId;
        if ($keepId)
        {
            $currentId = $self->{itemArray}->[$nbItems]->{$self->{model}->{commonFields}->{id}};
        }
        else
        {
            $self->{loaded}->{information}->{maxId}++;        
            $currentId = $self->{loaded}->{information}->{maxId};
            $self->{itemArray}->[$nbItems]->{$self->{model}->{commonFields}->{id}} = $currentId;
        }
        
        for my $field (@{$self->{model}->{fieldsNames}})
        {
            next if $field eq $self->{model}->{commonFields}->{id};
            if ($self->{model}->{fieldsInfo}->{$field}->{hasHistory})
            {
                $self->{panel}->{$field}->addHistory($info->{$field});
            }
            $self->{itemArray}->[$nbItems]->{$field} = $info->{$field};
        }

        $self->{parent}->{itemsView}->addItem($self->{itemArray}->[$nbItems], 1);

        $self->{multipleMode} = 0;
        $self->{currentItem} = $nbItems;
        $self->displayCurrent;
        $self->select($nbItems, 0)
            if !$noSelect;
        
        $self->{parent}->{itemsView}->showCurrent;
        
        return $currentId;
    }
    
    sub setOptions
    {
        my ($self, $options) = @_;

        $self->{options} = $options;

        #return $self->load($options->file, $splash, 0);
    }

    sub markToBeRemoved
    {
        my ($self, $image) = @_;

        push @{$self->{imagesToBeRemoved}}, $image;
    }

    sub markToBeAdded
    {
        my ($self, $image) = @_;
        push @{$self->{imagesToBeAdded}}, $image;        
    }
    
    sub removeMarkedPictures
    {
        my $self = shift;
        my $image;
        foreach $image(@{$self->{imagesToBeRemoved}})
        {
            unlink $image;
        }

        $self->{imagesToBeRemoved} = [];
    }

    sub addMarkedPictures
    {
        my $self = shift;

        $self->{imagesToBeAdded} = [];
    }
    
    sub clean
    {
        my $self = shift;
        my $image;
        foreach (@{$self->{imagesToBeAdded}})
        {
            unlink $_;
        }
        $self->{oldImagesDirectory} = {};
        $self->{newImagesDirectory} = undef;
        $self->{copyImagesWhenChangingDir} = 0;
    }

    sub setNewImagesDirectory
    {
        # $prev is also a parameter because we didn't store it here
        my ($self, $new, $prev, $withCopy) = @_;
        $new =~ s|/$||;
        $self->{newImagesDirectory} = $new;
        $self->{copyImagesWhenChangingDir} = $withCopy;
        # We stored the previous one as a hash so it will be easier for tests
        $prev =~ s|/$||;
        $self->{oldImagesDirectory}->{$prev} = 1;
    }

    sub setPreviousFile
    {
        my ($self, $prev) = @_;
        
        $self->{previousFile} = $prev;
    }

    sub queryReplace
    {
        my ($self, $field, $old, $new, $caseSensitive) = @_;
        foreach (@{$self->{itemArray}})
        {
            if (ref($_->{$field}) eq 'ARRAY')
            {
                foreach my $subval(@{$_->{$field}})
                {
                    foreach my $val(@$subval)
                    {
                        if ($caseSensitive)
                        {
                            $val =~ s/$old/$new/g;
                        }
                        else
                        {
                            $val =~ s/$old/$new/gi;
                        }
                    }
                }
            }
            else
            {
                if ($caseSensitive)
                {
                   $_->{$field} =~ s/$old/$new/g;
                }
                else
                {
                   $_->{$field} =~ s/$old/$new/gi;
                }
            }
        }
        $self->displayCurrent;
        $self->reloadList;
    }
    
    sub findMaxId
    {
        my $self = shift;

        $self->{loaded}->{information}->{maxId} = -1;
        foreach (@{$self->{itemArray}})
        {
            $self->{loaded}->{information}->{maxId} = $_->{$self->{model}->{commonFields}->{id}}
                if $_->{$self->{model}->{commonFields}->{id}} > $self->{loaded}->{information}->{maxId};
        }
    }
    
    sub clearList
    {
        my $self = shift;
        $self->{currentItem} = -1;
        $self->{loaded} = {};
        $self->{itemArray} = [];
        $self->{panel}->hide if $self->{panel};
        $self->{parent}->{itemsView}->clearCache if $self->{parent}->{itemsView};
        $self->{parent}->{itemsView}->reset if $self->{parent}->{itemsView};
        #$self->{parent}->reloadDone(1) if ! $self->{parent}->{initializing};
        #$self->reloadList if ! $self->{parent}->{initializing};
    }

    sub getNbItems
    {
        my $self = shift;
        return 0 if ! $self->{itemArray};
        return scalar @{$self->{itemArray}};
    }

    sub setLock
    {
        my ($self, $value) = @_;
        $self->{loaded}->{information}->{locked} = $value;
    }

    sub getLock
    {
        my $self = shift;
        return $self->{loaded}->{information}->{locked};
    }

    sub getBackend
    {
        my ($self, $file) = @_;
        $self->{backend} = new GCBackend::GCBeXmlParser($self->{parent})
           if !$self->{backend};
        $self->{backend}->setParameters(file => $file,
                                        version => $self->{parent}->{version});
        return $self->{backend};
    }

    sub getVersion
    {
        my ($self, $file) = @_;
        
        return $self->getBackend($file)->getVersion;
    }

    sub load
    {
        my ($self, $file, $splash, $fullProgress, $noReload) = @_;

        my $initTime;
        if ($ENV{GCS_PROFILING} > 0)
        {
            eval 'use Time::HiRes';
            eval '$initTime = [Time::HiRes::gettimeofday()]';
        }

        $self->clean;
        if (!$file)
        {
            $self->{parent}->setCurrentModel;
            return 0;
        }

        my $collection;

        $self->{block} = 1;
        $self->clearList;
        $self->{block} = 0;
        $self->{splash} = $splash;
        my $backend;
        eval
        {
            $backend = $self->getBackend($file);
            
            $self->{loaded} = $backend->load($splash);
            # We keep a direct access to this one
            $self->{itemArray} = $self->{loaded}->{data};
        };
        if ($@)
        {
            my @error = ('Fatal error while reading file', $@);
            return (0, \@error);
        }
        elsif ($self->{loaded}->{error})
        {
            return (0, $self->{loaded}->{error});
        }

        # Hide the panel if no item
        if (! scalar @{$self->{itemArray}})
        {
            $self->{panel}->hide;
        }

        # gotHistory = 1 means we got one but it has not been set in components
        if ($self->{loaded}->{gotHistory} == 1)
        {
            for my $hfield(@{$self->{model}->{fieldsHistory}})
            {
                if (exists $self->{loaded}->{histories}->{$hfield})
                {
                    $self->{panel}->{$hfield}->setValues($self->{loaded}->{histories}->{$hfield}, 1);
                }
                $self->{panel}->{$hfield}->setDropDown;
            }
#            $self->{loaded}->{gotHistory} = 2;
        }
        elsif ($self->{loaded}->{gotHistory} == 2)
        {
            for my $hfield(@{$self->{model}->{fieldsHistory}})
            {
                $self->{panel}->{$hfield}->setDropDown;
            }
        }

        $self->reloadList($splash, $fullProgress) unless $noReload;
                
#        $self->{parent}->refreshFilters;

        if ($ENV{GCS_PROFILING} > 0)
        {
            my $elapsed;
            eval '$elapsed = Time::HiRes::tv_interval($initTime)';
            print "Load time : $elapsed\n";
        }

        return 1;
    }

    sub movePictures
    {
        my ($self) = @_;
        eval {
            mkpath $self->{newImagesDirectory};
            my $file;
            my $dataFile = $self->{previousFile} ? $self->{previousFile}  : $self->{options}->file;
            foreach (@{$self->getItemsList})
            {
                foreach my $pic(@{$self->{model}->{fieldsImage}})
                {
                    $file = GCUtils::getDisplayedImage($_->{$pic},
                                                       '',
                                                       $dataFile);
                    # Not moving picture if it is not in the previous directory
                    next if !$file;
                    next if ! exists $self->{oldImagesDirectory}->{Cwd::realpath(dirname($file))};
                    (my $suffix = $file) =~ s/.*?(\.[^.]*)$/$1/;
                    my $new = 
                        $self->{parent}->getUniqueImageFileName(
                            $suffix, 
                            $_->{$self->{model}->{commonFields}->{title}});
                    
                    if ($self->{copyImagesWhenChangingDir})
                    {
                        copy $file, $new;
                    }
                    else
                    {
                        move $file, $new;
                    }
                    $_->{$pic} = $new;
                }
            }
        };
        $self->{previousFile} = 0;
        return $@ if $@;
        $self->displayCurrent;
        $self->{newImagesDirectory} = undef;
        $self->{copyImagesWhenChangingDir} = 0;
        $self->{oldImagesDirectory} = {};
        return 0;
    }

    sub save
    {
        my ($self, $splash) = @_;

        my $initTime;
        if ($ENV{GCS_PROFILING} > 0)
        {
            eval 'use Time::HiRes';
            eval '$initTime = [Time::HiRes::gettimeofday()]';
        }

        $self->updateCurrent if ($self->{currentItem} > -1);

        # TODO : Use progress bar for this operation also
        my $moveError = $self->movePictures
            if $self->{newImagesDirectory};
        return (0, ['SaveError', $moveError])
            if $moveError;
        

        if ($splash)
        {
            $splash->initProgress($self->{parent}->{lang}->{StatusSave});
            $splash->setItemsTotal(scalar @{$self->{itemArray}});
        }
        $self->addMarkedPictures;
        $self->removeMarkedPictures;

        my $backend = $self->getBackend($self->{options}->file);

        # We re-generate histories to give it to backend
        # Deactivated for the moment
#        if ($self->{panel})
#        {
#            my %histories;
#            for my $hfield(@{$self->{model}->{fieldsHistory}})
#            {
#                $histories{$hfield} = $self->{panel}->{$hfield}->getValues;
#            }
#            $backend->setHistories(\%histories);
#        }

        my $result = $backend->save($self->{itemArray},
                                    $self->{loaded}->{information},
                                    $splash);

        $self->{parent}->endProgress;
        if ($result->{error})
        {
            return (0, $result->{error});
        }
        $self->{parent}->removeUpdatedMark;

        if ($ENV{GCS_PROFILING} > 0)
        {
            my $elapsed;
            eval '$elapsed = Time::HiRes::tv_interval($initTime)';
            print "Save time : $elapsed\n";
        }
        return 1;
    }

    
}

1;
