#!/usr/bin/perl -wT
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Testopia System.
#
# The Initial Developer of the Original Code is Greg Hendricks.
# Portions created by Greg Hendricks are Copyright (C) 2006
# Novell. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
#                 Michael Hight <mjhight@gmail.com>
#                 Garrett Braden <gbraden@novell.com>
#                 Scott Sudweeks <ssudweeks@novell.com>
#                 Brian Kramer <bkramer@novell.com>

use strict;
use lib ".";

use Bugzilla;
use Bugzilla::Util;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::TestRun;
use Bugzilla::Testopia::Product;
use Bugzilla::Testopia::Classification;
use Bugzilla::Testopia::Environment;
use Bugzilla::Testopia::Environment::Element;
use Bugzilla::Testopia::Environment::Property;
use Bugzilla::Testopia::Environment::Category;

use JSON;
use Data::Dumper;

Bugzilla->login(LOGIN_REQUIRED);

local our $cgi = Bugzilla->cgi;
local our $template = Bugzilla->template;
local our $vars = {};

print $cgi->header;

ThrowUserError('testopia-permission-denied') unless Bugzilla->user->in_group('Testers');

my $action = $cgi->param('action') || '';
my $env_id = $cgi->param('env_id') || 0;

if($action eq 'getChildren'){
    my $json = new JSON;
    my $data = $json->jsonToObj($cgi->param('data'));
    
    my $node = $data->{'node'};
    
    my $id = $node->{'objectId'};
    my $type = $node->{'widgetId'};
    
    detaint_natural($id);
    trick_taint($type);
    
    
    for ($type){
        /classification/ && do { get_products($id);          };
        /product/        && do { get_categories($id);        };
        /category/       && do { get_category_element($id)   };
        /element/        && do { get_element_children($id);  };
        /property/       && do { get_validexp_json($id)      };
    }
}

elsif($action eq 'edit'){
    my $type = $cgi->param('type');
    my $id = $cgi->param('id');
    
    for ($type){
        /category/      && do { edit_category($id); };
        /element/       && do { edit_element($id);  };
        /property/      && do { edit_property($id); };
        /validexp/      && do { edit_validexp($id); };
    }
}

elsif($action eq 'do_edit'){
    my $type = $cgi->param('type');
    my $id = $cgi->param('id');
    
    for ($type){
        /category/      && do { do_edit_category($id); };
        /element/       && do { do_edit_element($id);  };
        /property/      && do { do_edit_property($id); };
        /validexp/      && do { do_edit_validexp($id); };
    }
}

elsif($action eq 'createChild'){
    my $json = new JSON;
    my $data = $json->jsonToObj($cgi->param('data'));
    
    my $node = $data->{'parent'};
    
    my $id = $node->{'objectId'};
    my $type = $node->{'widgetId'};
    
    my $create_type = $data->{'data'}->{'createType'};
    $type = 'child' if ($create_type && $type !~ /category/);
    
    detaint_natural($id);
    trick_taint($type);
    
    
    for ($type){
        /product/       && do { add_category($id);   };
        /category/      && do { add_element($id);    };
        /child/         && do { add_element($id,1);  };
        /element/       && do { add_property($id);   };
        /property/      && do { add_validexp($id);   };
    }
    
}

elsif($action eq 'removeNode'){
    my $json = new JSON;
    my $data = $json->jsonToObj($cgi->param('data'));
    my $node = $data->{'node'};
    
    my $id = $node->{'objectId'};
    my $type = $node->{'widgetId'};
    
    detaint_natural($id) unless $type =~ /validexp/;
    trick_taint($type);
    
    for ($type){
        /category/      && do { delete_category($id); };
        /element/       && do { delete_element($id);  };
        /property/      && do { delete_property($id); };
        /validexp/      && do { delete_validexp($id); };
    }
}

elsif ($action eq 'getcategories'){
    my $product_id = $cgi->param('product_id');
    detaint_natural($product_id);
    my $product = Bugzilla::Testopia::Product->new($product_id);
    exit unless $product && Bugzilla->user->can_see_product($product->name);
    my $cat = Bugzilla::Testopia::Environment::Category->new({});
    my $categories = $cat->get_element_categories_by_product($product_id);
    my $ret;
    foreach my $c (@{$categories}){
        my $name = $c->name;
        $name =~ s/<span style='color:blue'>|<\/span>//g;
        $ret .= $c->id.'||'.$name.'|||';
    }
    chop($ret);
    print $ret;
}

elsif ($action eq 'getelements'){
    my $cat_name = $cgi->param('cat_name');
    my $cat = Bugzilla::Testopia::Environment::Category->new($cat_name);
    
    my $elements = $cat->get_elements_by_category();
    my $ret;
    foreach my $e (@{$elements}){
        my $elem = Bugzilla::Testopia::Environment::Element->new(@$e{'element_id'});
        $elem->{'name'} =~ s/<span style='color:blue'>|<\/span>//g;
        $ret .= $elem->{'element_id'}.'||'.$elem->{'name'}.'|||';
    }
    $ret = substr($ret, 0, length($ret) - 3);
    print $ret;
}

else{
    &display;
}

sub display{
    my $category = Bugzilla::Testopia::Environment::Category->new({'id' => 0});
    if (Bugzilla->params->{'useclassification'}){
        $vars->{'allhaschild'} = $category->get_all_child_count;
        $vars->{'toplevel'} = Bugzilla->user->get_selectable_classifications;
        $vars->{'type'} = 'classification';
    }
    else {
        $vars->{'toplevel'} = $category->get_env_product_list;
        $vars->{'type'} = 'product';
    }
    
    $template->process("testopia/environment/admin/show.html.tmpl", $vars)
        || print $template->error();
}

###########################
### Tree Helper Methods ###
###########################
sub get_products{
    my ($class_id) = (@_);
    my $class = Bugzilla::Testopia::Classification->new($class_id);
    return unless scalar(grep {$class->id eq $class_id} @{Bugzilla->user->get_selectable_classifications});
    print $class->products_to_json;
}

sub get_categories{
    my ($product_id) = (@_);
    if ($product_id){
        my $product = Bugzilla::Testopia::Product->new($product_id);
        return unless $product->canedit;
    }
    my $category = Bugzilla::Testopia::Environment::Category->new({});
    print $category->product_categories_to_json($product_id);
}

sub get_category_element{
    my ($id) = (@_);
    my $category = Bugzilla::Testopia::Environment::Category->new($id);
    return unless $category->canedit;
    print $category->elements_to_json;
} 

sub get_element_children {
    my ($id) = (@_);
    my $element = Bugzilla::Testopia::Environment::Element->new($id);
    return unless $element->canedit;
    print $element->children_to_json;
}

sub get_validexp_json {
    my ($id) = (@_);
    my $property = Bugzilla::Testopia::Environment::Property->new($id);
    return unless $property->canedit;
    print $property->valid_exp_to_json;
}


###########################
### Edit Helper Methods ###
###########################
sub edit_category{
    my ($id) = (@_);
    my $category = Bugzilla::Testopia::Environment::Category->new($id);
    return unless $category->canedit;
    
    my $product = Bugzilla::Testopia::Product->new($category->product_id());
    
    $category->{'name'} =~ s/<span style='color:blue'>|<\/span>//g;

    $vars->{'category'} = $category;
    $vars->{'products'} = $category->get_env_product_list;
    $vars->{'currentproduct'} = $product ? $product : {'id' => 0, 'name' => '--ANY PRODUCT--'};
    $template->process("testopia/environment/admin/category.html.tmpl", $vars)
        || print $template->error();
}

sub edit_element{
    my ($id) = (@_);
    my $element = Bugzilla::Testopia::Environment::Element->new($id);
    return unless $element->canedit;
    
    my $category = Bugzilla::Testopia::Environment::Category->new($element->env_category_id());
    my $product = Bugzilla::Testopia::Product->new($category->product_id());
    
    $element->{'name'} =~ s/<span style='color:blue'>|<\/span>//g;
    
    $vars->{'element'} = $element;
    $vars->{'products'} = $category->get_env_product_list;
    $vars->{'currentproduct'} = $product ? $product : {'id' => 0, 'name' => '--ANY PRODUCT--'};
    $vars->{'currentcategory'} = $category;
    $vars->{'categories'} = $category->get_element_categories_by_product($product->{'id'});
    unless($element->parent_id() == 0){
        my $parent = Bugzilla::Testopia::Environment::Element->new($element->parent_id());
        $vars->{'currentelement'} = $parent;
    }
    $vars->{'elements'} = $category->get_elements_by_category(); 
    
    $template->process("testopia/environment/admin/element.html.tmpl", $vars)
        || print $template->error();
}

sub edit_property{
    my ($id) = (@_);
    my $property = Bugzilla::Testopia::Environment::Property->new($id);
    return unless $property->canedit;
    
    my $element = Bugzilla::Testopia::Environment::Element->new($property->element_id());
    my $cat_id = $element->env_category_id();
    my $elmnts = Bugzilla::Testopia::Environment::Category->new($cat_id)->get_elements_by_category();
    
    $vars->{'property'} = $property;
    $vars->{'elements'} = $elmnts;
    $vars->{'currentelement'} = $element;
    $template->process("testopia/environment/admin/property.html.tmpl", $vars);
}

sub edit_validexp{
    my ($id) = (@_);
    $id =~ /^(\d+)~/;
    
    my $property = Bugzilla::Testopia::Environment::Property->new($1);
    return unless $property->canedit; 
    
    my @expressions = split /\|/, $property->validexp(); 

    $vars->{'property'} = $property;
    $vars->{'expressions'} = \@expressions;
    $template->process("testopia/environment/admin/valid_exp.html.tmpl", $vars);
}


##############################
### Do_Edit Helper Methods ###
##############################
sub do_edit_category{
    my $name = $cgi->param('name');
    my $product_id = $cgi->param('product');
    my ($id) = (@_);
    my $category = Bugzilla::Testopia::Environment::Category->new($id);
    return unless $category->canedit;
    
    trick_taint($name);
    detaint_natural($product_id);
    my $error_mode_cache = Bugzilla->error_mode;
    Bugzilla->error_mode(ERROR_MODE_DIE);
    eval{
        validate_selection($product_id, 'id', 'products');
    };
    Bugzilla->error_mode($error_mode_cache);
    if ($@ && $product_id != 0){
        print '{error:"Invalid product"}';
        exit;
    }
    $category->set_product($product_id);
    
    unless ($category->set_name($name)) {
        print '{error:"Name already used. Please choose another"}';
        exit;
    } 
     
    print "{name:\"$name\", product:\"product$product_id\", widget:\"category$id\"}";
    
}

sub do_edit_element{
    my ($id) = (@_);

    #
    # CGI params
    #   productCombo    -> id of product (does not matter what this is, only used to find categories)
    #   categoryCombo   -> id of category (if zero, leave the same)
    #   elementCombo    -> id of parent element (id of 0 means no parent)
    #   name            -> name of the element
    #
    
    my $element = Bugzilla::Testopia::Environment::Element->new($id);
    return unless $element->canedit;
    
    my $cat_id = $cgi->param('categoryCombo');
    my $parent_id = $cgi->param('elementCombo');
    my $name = $cgi->param('name');
    my $parent;
    
    detaint_natural($cat_id);
    detaint_natural($parent_id);
    trick_taint($name);
    
    if ($cat_id){
        $element->update_element_category($cat_id);
        $parent = 'category' . $cat_id;
    }
    else {
        print '{error: "Category does not exist"}';
        exit;
    }
    if ($parent_id){
        $element->update_element_parent($parent_id);
        $parent = 'element' . $parent_id;
    }
    elsif (!$cat_id) {
        print '{error: "Parent element does not exist"}';
        exit;
    }
    unless ($element->update_element_name($name)){
        print '{error: "Name already taken. Please choose another."}';
        exit;
    }
    
    print "{name:\"$name\", parent:\"$parent\", widget:\"element$id\"}";
}

sub do_edit_property{
    my ($id) = (@_);
    my $name = $cgi->param('name');
    my $element_id = $cgi->param('element');
    my $property = Bugzilla::Testopia::Environment::Property->new($id);
    return unless $property->canedit;
    
    trick_taint($name);
    detaint_natural($element_id);
    my $error_mode_cache = Bugzilla->error_mode;
    Bugzilla->error_mode(ERROR_MODE_DIE);
    eval{
        validate_selection($element_id, 'element_id', 'test_environment_element');
    };
    Bugzilla->error_mode($error_mode_cache);
    if ($@){
        print '{error:"Invalid element"}';
        exit;
    }
    $property->set_element($element_id);
    
    unless ($property->set_name($name)) {
        print '{error:"Name already used. Please choose another"}';
        exit;
    } 
     
    print "{name:\"$name\", element:\"element$element_id\", widget:\"property$id\"}"; 
}

sub do_edit_validexp{
    my ($id) = (@_);
    
    my $property =  Bugzilla::Testopia::Environment::Property->new($id);
    return unless $property->canedit;
    my @expressions =  $cgi->param('valid_exp');
    
    my $exp = join('|', @expressions);
    trick_taint($exp);
    
    $property->update_property_validexp($exp);
    display;
}



###################################
### Create Child Helper Methods ###
###################################
sub add_category{
    my ($id) = (@_);
    my $category = Bugzilla::Testopia::Environment::Category->new({});
    if ($id){
        my $product = Bugzilla::Testopia::Product->new($id);
        return unless $product->canedit;
    }
    $category->{'product_id'} = $id;
    $category->{'name'} = 'New category ' . $category->new_category_count;
    
    my $new_cid = $category->store();
    
    my $json = '{title:"<span style=\'color:blue\'>' . $category->{'name'} . '</span>",';
    $json   .=  'objectId:"'. $new_cid . '",';
    $json   .=  'widgetId:"category' . $new_cid . '",';
    $json   .=  'childIconSrc:"testopia/img/square.gif",';
    $json   .=   'actionsDisabled:["addCategory","addValue","addProperty"]}';
    
    print $json; 
}

sub add_element{
    my ($id, $ischild) = (@_);
    my $element = Bugzilla::Testopia::Environment::Element->new({});
    # If we are adding this element as a child, $id is the parent element's id
    if ($ischild) {
        my $parent = Bugzilla::Testopia::Environment::Element->new($id);
        return unless $parent->canedit;
        $element->{'env_category_id'} = $parent->env_category_id;
    }
    # Otherwise $id is the catagory id
    else {
        my $parent = Bugzilla::Testopia::Environment::Category->new($id);
        return unless $parent->canedit;
        $element->{'env_category_id'} = $id;
    }
    $element->{'name'} = "New element " . $element->new_element_count;
    $element->{'parent_id'} = $ischild ? $id : 0;
    $element->{'isprivate'} = 0; 
    
    my $new_eid = $element->store();
    
    my $json =  '{title:"<span style=\'color:blue\'>' . $element->{'name'} .'</span>",';
    $json   .=   'objectId:"'. $new_eid .'",';
    $json   .=   'widgetId:"element'. $new_eid .'",';
    $json   .=   'childIconSrc:"testopia/img/circle.gif",';
    $json   .=   'actionsDisabled:["addCategory","addValue"]}';
    
    print $json;
}

sub add_property{
    my ($id) = (@_);
    #add new property to element with id=$id
    my $property = Bugzilla::Testopia::Environment::Property->new({});

    my $parent = Bugzilla::Testopia::Environment::Element->new($id);
    return unless $parent->canedit;

    $property->{'element_id'} = $id;
    $property->{'name'} = "New property " . $property->new_property_count;
    $property->{'validexp'} = "";
    
    my $new_pid = $property->store();
    
    my $json = '{title:"<span style=\'color:blue\'>' . $property->{'name'} . '</span>",';
    $json   .=  'objectId:"' . $new_pid . '",';
    $json   .=  'widgetId:"property' . $new_pid . '",';
    $json   .=  'childIconSrc:"testopia/img/triangle.gif",';
    $json   .=  'actionsDisabled:["addCategory","addElement","addProperty"]}'; 
 
    print $json;   
}

sub add_validexp{
    my ($id) = (@_);
    my $property = Bugzilla::Testopia::Environment::Property->new($id);
    return unless $property->canedit;
    
    my $exp = $property->validexp;
    $exp ? $property->update_property_validexp($exp . "|New value") : $property->update_property_validexp("New value");
    
    my $json .= '{title: "<span style=\'color:blue\'>New value</span>",';
    $json    .=  'objectId:"' . $id . '~New Value",';
    $json    .=  'widgetId:"validexp' . $id . '~New Value",';
    $json    .=  'actionsDisabled:["addCategory","addElement","addProperty","addValue","remove"]}';

    print $json;
}

#############################
### Delete Helper Methods ###
#############################
sub delete_category{
    my ($id) = (@_);
    my $category = Bugzilla::Testopia::Environment::Category->new($id);
    return unless $category->candelete;
    my $success = $category->obliterate;
    print $success == 1 ? "true" : "false";
}

sub delete_element{
    my ($id) = (@_);
    my $element = Bugzilla::Testopia::Environment::Element->new($id);
    return unless $element->candelete;
    my $success = $element->obliterate;
    print $success == 1 ? "true" : "false";
}

sub delete_property{
    my ($id) = (@_);
    my $property = Bugzilla::Testopia::Environment::Property->new($id);
    return unless $property->candelete;
    my $success = $property->obliterate;
    print $success == 1 ? "true" : "false";
}

sub delete_validexp{
    # $id, $type
    my ($id) = (@_);
    $id =~ /^(\d+)~/;
    my $property = Bugzilla::Testopia::Environment::Property->new($1);
    return unless $property->candelete;
    my %values;
    foreach my $v (split /\|/, $property->validexp){
        $values{$v} = 1;
    }
    $id =~ /~(.+)$/;
    my $value = $1;
    my $deleted = delete $values{$value};
    my $exp = join("|", keys %values);
    $property->update_property_validexp($exp);
    print $deleted ? "true" : "false";
}
